Compare commits

...

195 Commits

Author SHA1 Message Date
TIP Automation User
9ae67c751c Chg: update image tag in helm values to v2.5.0 2022-03-30 13:48:24 +00:00
TIP Automation User
9e23b51dd0 Chg: update image tag in helm values to v2.5.0-RC1 2022-02-11 16:02:49 +00:00
Charles
39158b0d1e Merge pull request #80 from stephb9959/main
2.5.44
2022-02-10 18:59:48 +00:00
Charles
acc264534e Merge pull request #160 from stephb9959/dev
2.5.44: allowing for the * symbol to be used on the device search bar
2022-02-10 18:58:38 +00:00
Charles
48dcb4acbf 2.5.44: allowing for the * symbol to be used on the device search bar 2022-02-10 15:11:04 +00:00
Charles
1c40f9eb4c Merge pull request #158 from stephb9959/dev
2.5.43
2022-02-09 18:18:19 +00:00
Charles
9e418eb423 New wifiscan icon 2022-02-09 18:08:33 +00:00
Charles
e4ff3a87a7 2.5.43: download wifi scan button added to wifi scan modal 2022-02-09 13:59:07 +00:00
Charles
e82551c97f Extension fix for wifi scan download 2022-02-08 19:12:24 +00:00
Charles
d877a4aecf 2.5.42: wifi scan download filename fix 2022-02-08 19:06:40 +00:00
Charles
5c50a40bdb 2.5.41: download wifi scan button 2022-02-08 18:45:52 +00:00
Charles
9caf0f375c 2.5.40: interface statistics negative deltas are now displayed as 0, fix on device page for when provisioning cannot be contacted 2022-02-08 17:06:28 +00:00
Johann Hoffmann
1ed8285452 Use docker-image-build composite action
Signed-off-by: Johann Hoffmann <johann.hoffmann@mailbox.org>
2022-02-08 17:45:14 +01:00
Charles
16170e613c Merge pull request #151 from stephb9959/dev
2.5.39
2022-02-07 22:16:07 +00:00
Charles
b3fb45dd36 2.5.39: statistics for v1 interfaces fix 2022-02-07 22:05:05 +00:00
Charles
54f5912da6 2.5.38: fix for v1 interface stats 2022-02-07 15:53:02 +00:00
Charles
8542ded488 2.5.37: memory graph fix, wifi scan results now contain the meshid when applicable 2022-02-07 15:35:59 +00:00
Charles
e6561faf8c Merge pull request #79 from stephb9959/main
2.5.36
2022-02-03 20:49:45 +01:00
Charles
f291b7b0fc Merge pull request #144 from stephb9959/dev
2.5.36
2022-02-03 20:45:02 +01:00
Charles
d9ea2abf1a 2.5.36: fix for device statistics version 1 2022-02-03 20:30:02 +01:00
Charles
60a072809b 2.5.35: now displaying 'waiting for update' when lastStats arent fetched yet on device status card 2022-01-28 14:58:01 +01:00
Dmitry Dunaev
9828d6457d [WIFI-6837] Chg: helm service type to NodePort
Signed-off-by: Dmitry Dunaev <dmitry@opsfleet.com>
2022-01-28 16:19:58 +03:00
Charles
d6f390d7d4 2.5.34: wifi scan can now use the override_dfs option, shows alert if error code = 1 2022-01-27 20:28:12 +01:00
Charles
6ddafc8de0 Merge pull request #78 from stephb9959/main
2.5.33
2022-01-25 16:31:00 +01:00
Charles
ed5c83cf66 Merge pull request #137 from stephb9959/dev
2.5.33: login mfa fix, new copy button
2022-01-25 16:02:57 +01:00
Charles
bf227b5e6f 2.5.33: login mfa fix, new copy button 2022-01-25 15:11:33 +01:00
Charles
8bc1350e2e Merge pull request #77 from stephb9959/main
2.5.32
2022-01-21 19:38:04 +01:00
Charles
3aa0dd2f51 Merge pull request #136 from stephb9959/dev
2.5.32
2022-01-21 19:35:49 +01:00
Charles
6c1f1e1db7 2.5.32: Dependencies fix 2022-01-21 18:32:42 +01:00
Charles
0c615fcb3b 2.5.31: added security retries display on error code 13 2022-01-21 17:56:41 +01:00
Charles
3ca900af6c 2.5.30: fix for device statistics 2022-01-18 19:42:08 +01:00
Charles
1481626b1b 2.5.29: fixes for statistics graphs 2022-01-18 14:36:42 +01:00
Charles
a0ba5aeca4 2.5.28: added deltas as possible source of tx/rx from associations 2022-01-18 11:10:13 +01:00
Charles
f48a922b4c Merge pull request #76 from stephb9959/main
Version 2.5.27
2022-01-17 22:08:46 +01:00
Charles
2418273191 Merge pull request #129 from stephb9959/dev
Version 2.5.25
2022-01-17 22:04:47 +01:00
Charles
09a10d7838 2.5.27: standardized command history, health and logs, wifi analysis table fixes 2022-01-17 19:16:36 +01:00
Charles
40ed1dd612 2.5.26: fixing for empty vendors in wifi analysis 2022-01-17 15:38:48 +01:00
Charles
2aa38f1117 Version 2.5.25: added refresh to logs/health/wifi analysis, added vendors to wifi analysis, changed configure feedback to use toast 2022-01-17 15:30:45 +01:00
Charles
5d81ad9830 Merge pull request #75 from stephb9959/main
Version 2.5.24
2022-01-14 20:12:16 +01:00
Charles
dffb45e233 Merge pull request #122 from stephb9959/dev
Version 2.5.24
2022-01-14 19:57:12 +01:00
Charles
5606c7b29a Version 2.5.24 2022-01-14 15:58:54 +01:00
Charles
9d5b4f63d3 Custom time on device stats 2022-01-14 15:17:10 +01:00
Charles
2c353023ab Merge pull request #74 from stephb9959/main
Version 2.5.18
2022-01-14 14:32:48 +01:00
Charles
6992cdbaa4 2.5.21: datepicker fix and added datepicker to device stats 2022-01-13 16:47:38 +01:00
Charles
9576079bfa 2.5.20: memory graph addded and can choose between interfaces 2022-01-13 15:53:29 +01:00
Charles
1e08ccaae3 Version 2.5.19: new memory display 2022-01-12 15:44:07 +01:00
Charles
80e07eb53a Merge pull request #73 from Telecominfraproject/release/v2.5.0
Version 2.5.18
2022-01-12 15:06:20 +01:00
Charles
54b7a27e65 Version 2.5.18 2022-01-12 14:49:09 +01:00
bourquecharles
5dc6100e8e Version 2.5.18 2022-01-12 14:20:11 +01:00
bourquecharles
a5c1a7122d Version 2.5.18: user fixes, blink only now, fix for connect loading 2022-01-10 09:48:54 +01:00
bourquecharles
61442462c7 Version 2.5.17 2021-12-28 14:38:03 -05:00
Dmitry Dunaev
917c31bef4 Merge pull request #71 from Telecominfraproject/feature/wifi-4977--introduce-revisionHistoryLimit
[WIFI-4977] Add: helm add revisionHistoryLimit support
2021-12-23 16:27:19 +03:00
Dmitry Dunaev
989439587f [WIFI-4977] Add: helm add revisionHistoryLimit support
Signed-off-by: Dmitry Dunaev <dmitry@opsfleet.com>
2021-12-23 16:10:27 +03:00
bourquecharles
e74130733e Version 2.5.16 2021-12-22 09:48:09 -05:00
bourquecharles
2cce9a4f4c Added all device images 2021-12-09 12:46:41 -05:00
bourquecharles
b721dfeb71 New labels 2021-12-07 17:18:30 -05:00
bourquecharles
645b9b2c37 New labels 2021-12-07 15:23:49 -05:00
bourquecharles
b419ebfe5d Version 2.5.14 2021-12-07 09:58:48 -05:00
bourquecharles
96d40a2946 Version 2.5.13 2021-12-03 09:07:34 -05:00
bourquecharles
e9b40573c7 Version 2.5.12 2021-12-02 16:37:05 -05:00
bourquecharles
ad316dfeac Image for devices 2021-12-02 16:16:43 -05:00
bourquecharles
62cbaf3c04 New labels 2021-12-01 14:29:27 -05:00
bourquecharles
cdb7eb3da9 Version 2.5.10 2021-12-01 12:07:50 -05:00
bourquecharles
671e0bbf71 Version 2.5.9 2021-11-25 17:07:38 -05:00
bourquecharles
50704b7b6a Version 2.5.8 2021-11-25 16:55:47 -05:00
bourquecharles
c198d1f593 Device page bugfix 2021-11-25 11:00:15 -05:00
bourquecharles
c91cd2eecf Dashboard fixes, other bugfixes 2021-11-25 09:59:03 -05:00
Charles
31e47f4a04 New labels 2021-11-22 17:02:17 -05:00
Charles
470a9c4afa Device list fix 2021-11-22 09:28:21 -05:00
Charles
98692be3ba Merge pull request #69 from Telecominfraproject/revert-68-main
Revert "Version 2.4.2"
2021-11-19 16:31:50 -05:00
Charles
5c5077d7ec Revert "Version 2.4.2" 2021-11-19 16:31:41 -05:00
Charles
8cbecc20bc Offset fixes 2021-11-19 16:30:43 -05:00
Charles
c13cae9ab3 Version 2.5.3 2021-11-19 15:01:07 -05:00
Charles
74de687b90 Label fix 2021-11-19 14:44:47 -05:00
Charles
ed3aca7d0c Version 2.5.2 2021-11-19 14:41:04 -05:00
Charles
c9467f31c8 Version 2.5.1 2021-11-19 12:20:02 -05:00
Charles
ad08632809 Merge pull request #68 from stephb9959/main
Version 2.4.2
2021-11-19 11:15:18 -05:00
Charles
e9562dcf82 Newer ucentral-libs version 2021-11-19 11:14:02 -05:00
Charles
bea47b2640 Login page fix 2021-11-19 09:34:48 -05:00
Charles
242078ec15 Dependency fix 2021-11-19 09:12:41 -05:00
Charles
5ca140df46 Reverting ucentral-libs version 2021-11-19 09:06:37 -05:00
Charles
1259212cb2 Merge branch 'main' into dev 2021-11-19 09:00:00 -05:00
Charles
750fa5be5e Hotfix for device table 2021-11-19 08:58:23 -05:00
Charles
299c43e10d Version 2.5 2021-11-19 08:42:54 -05:00
Dmitry Dunaev
969450cad3 [WIFI-4860] Chg: apply enforce-jira-issue-key only to PRs to release branches 2021-11-19 16:25:41 +03:00
Dmitry Dunaev
3da330b637 Merge pull request #67 from Telecominfraproject/feature/wifi-4860--add-ensure-jira-issue-key-workflow
[WIFI-4860] Add: enforce-jira-issue-key workflow
2021-11-19 15:49:57 +03:00
Dmitry Dunaev
86bd64e887 [WIFI-4860] Add: enforce-jira-issue-key workflow
Signed-off-by: Dmitry Dunaev <dmitry@opsfleet.com>
2021-11-19 13:19:48 +03:00
Charles
e02f939cb8 Merge pull request #66 from stephb9959/main
Version 2.4.1
2021-11-15 17:27:26 -05:00
Charles
2deebea6c8 Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-11-15 17:22:42 -05:00
Charles
fc2b1bb23c Merge branch 'dev' into main 2021-11-15 17:21:17 -05:00
Charles
848d94bad1 Version 2.4.1 2021-11-15 17:20:32 -05:00
Charles
4bde6e2d1f Merge pull request #65 from stephb9959/main
Version 2.4.0
2021-11-15 16:58:40 -05:00
Charles
e7694b644f Merge pull request #99 from stephb9959/dev
Version 2.4.0
2021-11-15 16:58:18 -05:00
Charles
07b3ac967a Version 2.4.0 2021-11-15 16:49:41 -05:00
Charles
c955bd9126 Version 2.3.21 2021-11-15 10:45:12 -05:00
Charles
ee69783a66 Merge pull request #64 from stephb9959/main
Version 2.3.20
2021-11-13 07:43:45 -05:00
Charles
b43c86520f Merge branch 'main' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into main 2021-11-13 07:31:29 -05:00
Charles
0952f62bf0 Merge branch 'dev' into main 2021-11-13 07:31:16 -05:00
Charles
44be7ec634 Closing edit user modal when 404 2021-11-13 07:30:30 -05:00
Charles
cddb0e94fa Merge pull request #63 from stephb9959/main
Version 2.3.18
2021-11-12 16:41:07 -05:00
Charles
1a9fb77361 Merge pull request #92 from stephb9959/dev
Login page changes, ucentral-libs upgrade
2021-11-12 13:52:06 -05:00
Charles
f7392461ad Login page changes, ucentral-libs upgrade 2021-11-12 12:01:42 -05:00
Charles
b567dc26f8 Merge pull request #90 from stephb9959/dev
Dashboard bugfixes, user modal fixes
2021-11-11 16:03:35 -05:00
Charles
a08f84f5b3 Dashboard bugfixes, user modal fixes 2021-11-11 15:58:59 -05:00
Charles
b1277ff2ac Merge pull request #62 from stephb9959/main
Version 2.3.16
2021-11-09 11:42:40 -05:00
Charles
aea1550a77 Merge pull request #83 from stephb9959/dev
Version 2.3.16
2021-11-09 11:34:29 -05:00
Max
262c1fe1e2 allow to set pod annotations (#61) 2021-11-09 13:29:16 +01:00
Charles
2d1e684c69 UI fixes 2021-11-08 14:25:25 -05:00
Charles
5c6fb8b9ec Merge pull request #81 from stephb9959/dev
Version 2.3.15
2021-11-08 11:55:19 -05:00
Charles
a6be8b08c3 Version 2.3.15 2021-11-08 11:44:23 -05:00
Charles
6503c1b84d Merge pull request #79 from stephb9959/dev
Version 2.3.14
2021-11-04 17:35:51 -04:00
Charles
1fc1588303 Version 2.3.14 2021-11-04 17:29:24 -04:00
Charles
ae03c5c33e Version 2.3.13 2021-11-04 17:21:04 -04:00
Charles
2d2603ff27 Merge pull request #60 from stephb9959/main
Version 2.3.12
2021-11-02 16:42:57 -04:00
Charles
55881b0c5e Merge pull request #73 from stephb9959/dev
Version 2.3.12
2021-11-02 16:42:32 -04:00
Charles
adf752db85 UI fixes 2021-11-02 11:47:45 -04:00
Charles
d211669244 Branding fix 2021-11-01 17:43:26 -04:00
Charles
99af39db69 Livinglab branding 2021-11-01 17:42:48 -04:00
Charles
e707239d12 Label 2021-11-01 17:38:24 -04:00
Charles
d349e43523 Merge pull request #59 from stephb9959/main
Version 2.3.11
2021-11-01 17:29:31 -04:00
Charles
d52df89ab3 Merge pull request #68 from stephb9959/dev
2.3.11
2021-11-01 17:29:01 -04:00
Charles
ae48518271 UI/Bugfixes, storage for device table 2021-11-01 15:31:24 -04:00
Charles
d7238881dc New labels 2021-10-29 08:31:22 -04:00
Charles
ebf2d7d5c6 Merge pull request #58 from stephb9959/main
Version 2.3.9
2021-10-28 14:27:24 -04:00
Charles
259087aa95 Merge pull request #63 from stephb9959/dev
Version 2.3.9
2021-10-28 14:24:50 -04:00
Charles
0d7e2056f0 Version 2.3.9 2021-10-28 14:21:17 -04:00
Charles
c35178bcbb Version 2.3.8 2021-10-28 14:04:31 -04:00
Charles
5da3fb6c19 Upping ucentral-libs version 2021-10-28 13:37:06 -04:00
Charles
1961aa62da UI fixes, using new notes in my profile 2021-10-28 12:08:15 -04:00
Charles
8678b454e3 User and Firmware tables now using modals 2021-10-28 10:00:19 -04:00
Charles
b26408ade2 Tables now more compact 2021-10-27 16:09:15 -04:00
Charles
e60c000aad UI fixes, my profile page rework 2021-10-27 15:49:35 -04:00
Charles
411c618be1 Merge pull request #57 from stephb9959/main
Version 2.3.2
2021-10-26 17:20:45 -04:00
Charles
04fe9e18b6 Merge pull request #58 from stephb9959/dev
Version 2.3.1
2021-10-26 17:16:05 -04:00
Charles
b5772ce7f4 Can now edit/view my profile 2021-10-26 15:56:49 -04:00
Charles
1c3a5232c7 UI fixes 2021-10-26 15:05:50 -04:00
Charles
35caed4b07 Upgrading ucentral-libs version 2021-10-26 13:27:55 -04:00
Charles
6fb6e92382 UI fixes 2021-10-26 12:10:55 -04:00
Charles
83a4493a61 Firmware and device page now using tabs 2021-10-25 16:19:50 -04:00
Charles
5cedcf1ebf Version 2.2.14 2021-10-25 16:04:18 -04:00
Charles
2ebc649fdf Device page now uses tabs 2021-10-25 15:58:24 -04:00
Charles
996e9c2e4b Device page now uses tabs 2021-10-25 15:55:02 -04:00
bourquecharles
01f27da4b2 New translations 2021-10-21 09:41:08 -04:00
Charles
6151dcb8ff Merge pull request #56 from stephb9959/main
Version 2.2.12
2021-10-20 13:55:26 -04:00
Charles
26e4dd9859 Merge pull request #53 from stephb9959/dev
Version 2.2.12
2021-10-20 13:52:44 -04:00
Charles
531b240990 Version 2.2.12 2021-10-20 13:48:03 -04:00
Charles
3e82403d41 Update index.js 2021-10-20 13:28:08 -04:00
Charles
3564abfa29 Merge pull request #55 from stephb9959/main
Version 2.2.11
2021-10-19 11:50:48 -04:00
Charles
cd87fb4500 Merge pull request #48 from stephb9959/dev
Version 2.2.11
2021-10-19 11:40:52 -04:00
Charles
65ffc7a656 WifiAnalysis fix 2021-10-19 11:11:40 -04:00
Charles
dab7aa77c9 Upgrading ucentralibs 2021-10-19 10:05:56 -04:00
Charles
6847b5180a Other checks for empty values in wifianalysis 2021-10-19 09:52:21 -04:00
Charles
e0ba4e4b20 Wifi Analysis fix for empty rssi/tx values 2021-10-19 09:51:41 -04:00
Dmitry Dunaev
955becdb46 Merge pull request #54 from Telecominfraproject/fix/wifi-4923--helm-git-readme
[WIFI-4923] Fix: helm-git link in chart README
2021-10-19 11:50:52 +03:00
Dmitry Dunaev
daba3a3f28 [WIFI-4923] Fix: helm-git link in chart README 2021-10-19 11:40:47 +03:00
Charles
33b8d1a1f5 Merge pull request #53 from stephb9959/main
Version 2.2.8
2021-10-14 14:29:45 -04:00
Charles
0a4a5b392c Merge pull request #45 from stephb9959/dev
New labels
2021-10-14 11:00:51 -04:00
Charles
4422d54b74 New labels 2021-10-14 11:00:10 -04:00
Charles
e4883bf588 Good favicon 2021-10-14 10:58:53 -04:00
Charles
961243eefd Merge pull request #42 from stephb9959/dev
Version 2.2.8
2021-10-13 16:37:34 -04:00
Charles
c9c3e003eb Upgrading ucentral-libs 2021-10-13 16:09:06 -04:00
Charles
0faa9f63d2 Fix for getting gw ui during login 2021-10-13 16:02:40 -04:00
Charles
a44932d4f8 V1 of MFA 2021-10-13 15:54:44 -04:00
Charles
5e35c23883 Merge pull request #52 from stephb9959/main
Version 2.2.5
2021-10-12 12:02:18 -04:00
Charles
550f5ad299 Logos fix 2021-10-12 11:46:06 -04:00
Charles
dd19ed8bcd New labels 2021-10-12 11:44:30 -04:00
Charles
ff34e1098c Update index.js 2021-10-12 11:42:57 -04:00
Charles
763922b349 Update config.json 2021-10-12 11:40:40 -04:00
Charles
757844d3ac Merge branch 'lindsaybb' into main 2021-10-12 11:39:42 -04:00
Charles
e5775e548f Merge branch 'joindigital' into main 2021-10-12 11:38:03 -04:00
Charles
477e686806 Merge pull request #37 from stephb9959/dev
Fix for RSSI/Noise parse
2021-10-12 11:36:49 -04:00
Charles
8d5f912adc Merge branch 'dev' of https://github.com/stephb9959/wlan-cloud-ucentralgw-ui into dev 2021-10-12 11:12:59 -04:00
Charles
45f206d947 Fix for converted/non-converted rssi values 2021-10-12 11:12:47 -04:00
Charles
2a271e9dea Merge pull request #35 from stephb9959/main
Dashboard fix when getting empty age value
2021-10-11 18:23:38 -04:00
Charles
a007903ea2 Merge pull request #34 from stephb9959/main
Dashboard fix when getting empty age value
2021-10-11 18:23:10 -04:00
Charles
bb55498285 Merge pull request #33 from stephb9959/main
Making dev up to date
2021-10-11 18:22:38 -04:00
Charles
39b482bab9 Dashboard fix when getting empty age value 2021-10-11 18:21:34 -04:00
Charles
8b91d3c7e5 JoinDigital branding 2021-10-11 15:20:15 -04:00
Charles
f56611b0e1 Merge pull request #32 from stephb9959/viasat
Branding
2021-10-11 15:18:46 -04:00
Charles
760a6f50b2 New labels 2021-10-11 15:16:52 -04:00
Charles
c5f629d761 Fix for dark logos 2021-10-07 16:38:29 -04:00
Charles
b754dfff73 New dark logo for viasat 2021-10-07 15:31:52 -04:00
Charles
3d4937144f New logo used 2021-10-06 15:08:52 -04:00
Charles
f7f01f4c90 Typo correction 2021-10-06 15:01:57 -04:00
Charles
8ce89f1621 Viasat branding 2021-10-06 14:57:13 -04:00
Charles
00cdb0bf1e Corrected host 2021-10-06 14:35:32 -04:00
Charles
3ce188333e Viasat branch creation 2021-10-06 14:28:51 -04:00
Charles
7ddf82cf1b Update index.js 2021-10-05 16:28:04 -04:00
Charles
1e17da594d Merge pull request #31 from stephb9959/main
Version 2.2.4
2021-10-05 16:09:57 -04:00
Charles
299922a38a Merge pull request #30 from stephb9959/dev
Version 2.2.4
2021-09-30 15:25:35 -04:00
Charles
8360644864 Bugfix for system page 2021-09-30 13:00:30 -04:00
Charles
d445766f01 Now using notification system for device commands 2021-09-30 10:02:14 -04:00
Charles
e36c682fb1 Merge pull request #28 from stephb9959/main
Fixing device list if we receive 0 devices
2021-09-28 11:26:57 -04:00
Charles
a6ba03c33b Lindsay BB branding 2021-09-28 09:59:12 -04:00
Charles
b7c18bd320 Merge pull request #26 from stephb9959/main
Version 2.2
2021-09-28 09:54:24 -04:00
128 changed files with 12109 additions and 9187 deletions

View File

@@ -24,45 +24,16 @@ jobs:
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
DOCKER_REGISTRY_USERNAME: ucentral DOCKER_REGISTRY_USERNAME: ucentral
steps: steps:
- uses: actions/checkout@v2 - name: Checkout actions repo
uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t owgw-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 owgw-ui:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/owgw-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: with:
registry: ${{ env.DOCKER_REGISTRY_URL }} repository: Telecominfraproject/.github
username: ${{ env.DOCKER_REGISTRY_USERNAME }} path: github
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Push Docker images - name: Build and push Docker image
if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' uses: ./github/composite-actions/docker-image-build
run: | with:
docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/owgw-ui | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {} image_name: owgw-ui
registry: tip-tip-wlan-cloud-ucentral.jfrog.io
registry_user: ucentral
registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}

View File

@@ -0,0 +1,24 @@
name: Ensure Jira issue is linked
on:
pull_request:
types: [opened, edited, reopened, synchronize]
branches:
- 'release/*'
jobs:
check_for_issue_key:
runs-on: ubuntu-latest
steps:
- name: Checkout actions repo
uses: actions/checkout@v2
with:
repository: Telecominfraproject/.github
path: github
- name: Run JIRA check
uses: ./github/composite-actions/enforce-jira-issue-key
with:
jira_base_url: ${{ secrets.TIP_JIRA_URL }}
jira_user_email: ${{ secrets.TIP_JIRA_USER_EMAIL }}
jira_api_token: ${{ secrets.TIP_JIRA_API_TOKEN }}

View File

@@ -20,7 +20,7 @@ Currently this chart is not assembled in charts archives, so [helm-git](https://
To install the chart with the release name `my-release`: To install the chart with the release name `my-release`:
```bash ```bash
$ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui@helm?ref=main $ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui@helm/owgwui-0.1.0.tgz?ref=main
``` ```
The command deploys the Web UI on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. The command deploys the Web UI on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.

View File

@@ -11,6 +11,7 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/managed-by: {{ .Release.Service }}
spec: spec:
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
selector: selector:
matchLabels: matchLabels:
app.kubernetes.io/name: {{ include "owgwui.name" . }} app.kubernetes.io/name: {{ include "owgwui.name" . }}
@@ -26,6 +27,12 @@ spec:
{{- with .Values.services.owgwui.labels }} {{- with .Values.services.owgwui.labels }}
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- if .Values.podAnnotations }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
spec: spec:
containers: containers:

View File

@@ -1,5 +1,6 @@
# System # System
replicaCount: 1 replicaCount: 1
revisionHistoryLimit: 2
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""
@@ -7,12 +8,12 @@ fullnameOverride: ""
images: images:
owgwui: owgwui:
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
tag: main tag: v2.5.0
pullPolicy: Always pullPolicy: Always
services: services:
owgwui: owgwui:
type: ClusterIP type: NodePort
ports: ports:
http: http:
servicePort: 80 servicePort: 80
@@ -69,6 +70,8 @@ tolerations: []
affinity: {} affinity: {}
podAnnotations: {}
# Application # Application
public_env_variables: public_env_variables:
DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001 DEFAULT_UCENTRALSEC_URL: https://ucentral.dpaas.arilia.com:16001

10648
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "2.2.2", "version": "2.5.44",
"dependencies": { "dependencies": {
"@coreui/coreui": "^3.4.0", "@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1", "@coreui/icons": "^2.0.1",
@@ -17,6 +17,7 @@
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-apexcharts": "^1.3.9", "react-apexcharts": "^1.3.9",
"react-csv": "^2.2.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-flow-renderer": "^9.6.6", "react-flow-renderer": "^9.6.6",
"react-i18next": "^11.11.0", "react-i18next": "^11.11.0",
@@ -26,7 +27,7 @@
"react-tooltip": "^4.2.21", "react-tooltip": "^4.2.21",
"react-widgets": "^5.1.1", "react-widgets": "^5.1.1",
"sass": "^1.35.1", "sass": "^1.35.1",
"ucentral-libs": "^0.9.41", "ucentral-libs": "^1.0.60",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"main": "index.js", "main": "index.js",
@@ -82,7 +83,6 @@
"husky": "^4.3.8", "husky": "^4.3.8",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"mini-css-extract-plugin": "^1.6.1", "mini-css-extract-plugin": "^1.6.1",
"node-sass": "^5.0.0",
"path": "^0.12.7", "path": "^0.12.7",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"react-refresh": "^0.9.0", "react-refresh": "^0.9.0",
@@ -91,7 +91,7 @@
"terser-webpack-plugin": "^5.1.4", "terser-webpack-plugin": "^5.1.4",
"webpack": "^5.40.0", "webpack": "^5.40.0",
"webpack-bundle-analyzer": "^4.4.2", "webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },

View File

@@ -5,7 +5,7 @@
<link rel="icon" href="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" />
<title>uCentralGW</title> <title>Gateway</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>

View File

@@ -17,11 +17,13 @@
"blink": "LEDs Blinken", "blink": "LEDs Blinken",
"device_leds": "LEDs", "device_leds": "LEDs",
"execute_now": "Möchten Sie dieses Muster jetzt einstellen?", "execute_now": "Möchten Sie dieses Muster jetzt einstellen?",
"explanation": "Welches Muster möchten Sie auf diesem Gerät für 30 Sekunden einstellen?",
"pattern": "Wählen Sie das Muster, das Sie verwenden möchten:", "pattern": "Wählen Sie das Muster, das Sie verwenden möchten:",
"set_leds": "LEDs einstellen", "set_leds": "LEDs einstellen",
"when_blink_leds": "Wann möchten Sie die LEDs blinken lassen?" "when_blink_leds": "Wann möchten Sie die LEDs blinken lassen?"
}, },
"commands": { "commands": {
"command_success": "Befehl erfolgreich übermittelt",
"error": "Fehler beim Senden des Befehls!", "error": "Fehler beim Senden des Befehls!",
"error_delete_log": "Fehler beim Versuch zu löschen: {{error}}", "error_delete_log": "Fehler beim Versuch zu löschen: {{error}}",
"event_queue": "Ereigniswarteschlange", "event_queue": "Ereigniswarteschlange",
@@ -32,41 +34,53 @@
"common": { "common": {
"access_policy": "Zugangsrichtlinien", "access_policy": "Zugangsrichtlinien",
"add": "Hinzufügen", "add": "Hinzufügen",
"add_items": "Füge Artikel hinzu",
"add_note": "Notiz hinzufügen",
"add_note_explanation": "Schreiben Sie unten Ihre neue Notiz und klicken Sie auf die Schaltfläche \"+\", wo Sie fertig sind",
"adding_ellipsis": "Hinzufügen ...", "adding_ellipsis": "Hinzufügen ...",
"all": "Alles",
"are_you_sure": "Bist du sicher?", "are_you_sure": "Bist du sicher?",
"back_to_login": "Zurück zur Anmeldung", "back_to_login": "Zurück zur Anmeldung",
"back_to_start": "Zurück zum Start", "back_to_start": "Zurück zum Start",
"blacklist": "Schwarze Liste",
"by": "Durch", "by": "Durch",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"certificate": "Zertifikat", "certificate": "Zertifikat",
"certificates": "Zertifikate", "certificates": "Zertifikate",
"claim": "Anspruch",
"clear": "Löschen", "clear": "Löschen",
"close": "Schließen", "close": "Schließen",
"code": "Code",
"command": "Befehl", "command": "Befehl",
"commands": "Befehle", "commands": "Befehle",
"commands_executed": "Ausgeführte Befehle", "commands_executed": "Ausgeführte Befehle",
"compatible": "kompatibel", "compatible": "kompatibel",
"completed": "Abgeschlossen", "completed": "Abgeschlossen",
"concurrent_devices": "Gleichzeitige Geräte",
"config_id": "Konfigurations ID", "config_id": "Konfigurations ID",
"confirm": "Bestätigen", "confirm": "Bestätigen",
"confirm_stop_editing": "Möchten Sie die Bearbeitung wirklich beenden? Dadurch werden alle nicht gespeicherten Änderungen, die Sie vorgenommen haben, verworfen.",
"connected": "Verbindung wurde hergestellt", "connected": "Verbindung wurde hergestellt",
"copied": "kopiert!", "copied": "kopiert!",
"copied_to_clipboard": "In die Zwischenablage kopiert!",
"copy_to_clipboard": "In die Zwischenablage kopieren", "copy_to_clipboard": "In die Zwischenablage kopieren",
"create": "Erstellen", "create": "Erstellen",
"created": "Erstellt", "created": "Erstellt",
"created_by": "Erstellt von", "created_by": "Erstellt von",
"creator": "Schöpfer",
"current": "Aktuell", "current": "Aktuell",
"custom_date": "Benutzerdefiniertes Datum", "custom_date": "Benutzerdefiniertes Datum",
"dashboard": "Instrumententafel", "dashboard": "Instrumententafel",
"date": "Datum", "date": "Datum",
"day": "tag", "day": "tag",
"days": "tage", "days": "tage",
"default_map": "Standardkarte",
"delete": "Löschen", "delete": "Löschen",
"delete_device": "Gerät löschen", "delete_device": "Gerät löschen",
"details": "Einzelheiten", "details": "Einzelheiten",
"device": "Gerät #{{serialNumber}}", "device": "Gerät #{{serialNumber}}",
"device_dashboard": "Geräte-Dashboard", "device_dashboard": "Geräte-Dashboard",
"device_delete": "Gerät Nr.{{serialNumber}}löschen", "device_delete": "#{{serialNumber}}löschen",
"device_deleted": "Gerät erfolgreich gelöscht", "device_deleted": "Gerät erfolgreich gelöscht",
"device_health": "Gerätezustand", "device_health": "Gerätezustand",
"device_list": "Liste der Geräte", "device_list": "Liste der Geräte",
@@ -78,6 +92,7 @@
"dismiss": "entlassen", "dismiss": "entlassen",
"do_now": "Sofort", "do_now": "Sofort",
"download": "Herunterladen", "download": "Herunterladen",
"duplicate": "Duplikat",
"duration": "Dauer", "duration": "Dauer",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"edit_user": "Bearbeiten", "edit_user": "Bearbeiten",
@@ -86,6 +101,8 @@
"endpoints": "Endpunkte", "endpoints": "Endpunkte",
"error": "Fehler", "error": "Fehler",
"error_adding_note": "Fehler beim Hinzufügen einer Notiz", "error_adding_note": "Fehler beim Hinzufügen einer Notiz",
"error_code": "Fehlercode",
"errors": "Fehler",
"execute_now": "Möchten Sie diesen Befehl jetzt ausführen?", "execute_now": "Möchten Sie diesen Befehl jetzt ausführen?",
"executed": "Ausgeführt", "executed": "Ausgeführt",
"exit": "Ausgang", "exit": "Ausgang",
@@ -96,12 +113,22 @@
"forgot_password_title": "Passwort vergessen", "forgot_password_title": "Passwort vergessen",
"from": "Von", "from": "Von",
"general_error": "API-Fehler, wenden Sie sich bitte an Ihren Administrator", "general_error": "API-Fehler, wenden Sie sich bitte an Ihren Administrator",
"go_back": "Geh zurück",
"hide": "verbergen", "hide": "verbergen",
"hour": "stunde", "hour": "stunde",
"hours": "std", "hours": "std",
"id": "ID", "id": "ID",
"invalid_credentials": "Ungültiger Benutzername und / oder Passwort",
"invalid_date_explanation": "Ungültiges Datum, bitte verwenden Sie den Kalender, auf den Sie über die Schaltfläche rechts zugreifen können",
"invalid_file": "Die ausgewählte Datei war ungültig, bitte lesen Sie die Anweisungen und passen Sie Ihre Datei entsprechend an",
"invalid_password": "Dieses Passwort entspricht nicht den grundlegenden Passwortregeln. Bitte besuchen Sie unsere Seite Passwortrichtlinien, um mehr zu erfahren",
"invalid_pem": "Ihre PEM-Datei ist ungültig. Es sollte mit '-----BEGIN CERTIFICATE-----' ODER '-----BEGIN PRIVATE KEY-----' beginnen und mit '-----END CERTIFICATE--- enden. --' ODER '-----END PRIVATSCHLÜSSEL-----'",
"ip_address": "IP Adresse", "ip_address": "IP Adresse",
"ips": "IPs",
"item": "Artikel",
"items": "Artikel",
"items_per_page": "Objekte pro Seite:", "items_per_page": "Objekte pro Seite:",
"key": "Schlüssel",
"last_dashboard_refresh": "Letzte Dashboard-Aktualisierung", "last_dashboard_refresh": "Letzte Dashboard-Aktualisierung",
"later_tonight": "Später am Abend", "later_tonight": "Später am Abend",
"latest": "Neueste", "latest": "Neueste",
@@ -110,28 +137,35 @@
"loading_more_ellipsis": "Mehr laden ...", "loading_more_ellipsis": "Mehr laden ...",
"logout": "Ausloggen", "logout": "Ausloggen",
"mac": "MAC-Adresse", "mac": "MAC-Adresse",
"main": "Main",
"manufacturer": "Hersteller", "manufacturer": "Hersteller",
"memory_used": "Verwendeter Speicher", "memory_used": "Verwendeter Speicher",
"min_max": "Min: {{min}}, Max: {{max}}",
"minute": "Minute", "minute": "Minute",
"minutes": "protokoll", "minutes": "protokoll",
"modified": "Geändert", "modified": "Geändert",
"na": "(unbekannt)", "na": "(unbekannt)",
"need_date": "Du brauchst ein Datum...", "need_date": "Du brauchst ein Datum...",
"no": "Nein", "no": "Nein",
"no_addresses_found": "Keine Adressen gefunden",
"no_devices_found": "Keine Geräte gefunden", "no_devices_found": "Keine Geräte gefunden",
"no_items": "Keine Gegenstände", "no_items": "Keine Gegenstände",
"none": "Keiner", "none": "Keiner",
"not_connected": "Nicht verbunden", "not_connected": "Nicht verbunden",
"of_connected": "% der Geräte", "of_connected": "% der verbundenen Geräte",
"off": "Aus", "off": "Aus",
"on": "An", "on": "An",
"optional": "Wahlweise", "optional": "Wahlweise",
"overall_health": "Allgemeine Gesundheit", "overall_health": "Allgemeine Gesundheit",
"password_policy": "Kennwortrichtlinie", "password_policy": "Kennwortrichtlinie",
"preferences": "Einstellungen",
"preview": "Vorschau", "preview": "Vorschau",
"program": "Programm",
"reason": "Grund",
"recorded": "Verzeichnet", "recorded": "Verzeichnet",
"refresh": "Aktualisierung", "refresh": "Aktualisierung",
"refresh_device": "Gerät aktualisieren", "refresh_device": "Gerät aktualisieren",
"remove_claim": "Anspruch entfernen",
"required": "Erforderlich", "required": "Erforderlich",
"result": "Ergebnis", "result": "Ergebnis",
"save": "Sparen", "save": "Sparen",
@@ -142,16 +176,21 @@
"second": "zweite", "second": "zweite",
"seconds": "sekunden", "seconds": "sekunden",
"seconds_elapsed": "Sekunden verstrichen", "seconds_elapsed": "Sekunden verstrichen",
"see_details": "Siehe Einzelheiten",
"select": "wählen",
"serial_num": "Seriennummer",
"serial_number": "Seriennummer", "serial_number": "Seriennummer",
"show_all": "Zeige alles", "show_all": "Zeige alles",
"socket_connection_closed": "Verbindung geschlossen!", "socket_connection_closed": "Verbindung geschlossen!",
"start": "Start", "start": "Start",
"status": "Status",
"stop_editing": "Stoppen Sie die Bearbeitung", "stop_editing": "Stoppen Sie die Bearbeitung",
"submit": "Absenden", "submit": "Absenden",
"submitted": "Eingereicht", "submitted": "Eingereicht",
"success": "Erfolg", "success": "Erfolg",
"system": "System", "system": "System",
"table": "Tabelle", "table": "Tabelle",
"time_per_device": "Gerät/Sekunde",
"timestamp": "Zeit", "timestamp": "Zeit",
"to": "zu", "to": "zu",
"type": "Art", "type": "Art",
@@ -162,16 +201,22 @@
"unknown": "unbekannte", "unknown": "unbekannte",
"up_to_date": "Aktuelle Geräte", "up_to_date": "Aktuelle Geräte",
"uptimes": "Betriebszeiten", "uptimes": "Betriebszeiten",
"use_file": "Datei verwenden",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Anbieter", "vendors": "Anbieter",
"view_more": "Mehr anzeigen", "view_more": "Mehr anzeigen",
"visibility": "Sichtweite",
"waiting_for_update": "Warten auf Aktualisierung",
"yes": "Ja" "yes": "Ja"
}, },
"configuration": { "configuration": {
"add_configuration": "Konfiguration hinzufügen", "add_configuration": "Konfiguration hinzufügen",
"add_new_block": "Neuen Konfigurationsblock hinzufügen", "add_new_block": "Neuen Konfigurationsblock hinzufügen",
"add_or_link": "Verlinken oder hinzufügen", "add_or_link": "Verlinken oder hinzufügen",
"add_radio": "Radio hinzufügen",
"ca_cert_explanation": "Bitte verwenden Sie eine .pem-Datei, die mit \"-----BEGIN CERTIFICATE-----\" beginnt und mit \"-----END CERTIFICATE-----\" endet. Das Ergebnis wird im Feld darunter angezeigt. Sie können das Zertifikat, das Sie verwenden möchten, auch direkt kopieren und einfügen.",
"cannot_delete": "Diese Konfiguration kann nicht gelöscht werden, da sie von mindestens einer Entität, einem Veranstaltungsort oder einem Gerät verwendet wird", "cannot_delete": "Diese Konfiguration kann nicht gelöscht werden, da sie von mindestens einer Entität, einem Veranstaltungsort oder einem Gerät verwendet wird",
"choose_radio_band": "Welche Radioband möchten Sie gründen?",
"choose_section": "Welchen Abschnitt soll dieser Block enthalten?", "choose_section": "Welchen Abschnitt soll dieser Block enthalten?",
"configuration_browser": "Konfigurationsbrowser", "configuration_browser": "Konfigurationsbrowser",
"configurations": "Konfigurationen", "configurations": "Konfigurationen",
@@ -182,17 +227,22 @@
"creation_success": "Konfiguration erfolgreich erstellt!", "creation_success": "Konfiguration erfolgreich erstellt!",
"currently_associated": "Aktuell zugeordnete Konfiguration: {{config}}", "currently_associated": "Aktuell zugeordnete Konfiguration: {{config}}",
"currently_selected_config": "Derzeit ausgewählte Konfiguration: {{config}}", "currently_selected_config": "Derzeit ausgewählte Konfiguration: {{config}}",
"default_configs": "Standardkonfigurationen",
"default_configurations": "Standardkonfigurationen",
"delete_config": "Konfiguration löschen", "delete_config": "Konfiguration löschen",
"details": "Gerätedetails", "details": "Gerätedetails",
"device_password": "Passwort", "device_password": "Passwort",
"device_type": "Gerätetyp", "device_type": "Gerätetyp",
"device_types": "Gerätetypen", "device_types": "Gerätetypen",
"devices_affected": "Von dieser Konfiguration betroffene Geräte:",
"edit_configuration": "Konfiguration bearbeiten", "edit_configuration": "Konfiguration bearbeiten",
"error_delete": "Fehler beim Versuch zu löschen: {{error}}", "error_delete": "Fehler beim Versuch zu löschen: {{error}}",
"error_delete_blacklist": "Fehler beim Löschen aus der schwarzen Liste: {{error}}",
"error_fetching_config": "Fehler beim Abrufen der Konfiguration", "error_fetching_config": "Fehler beim Abrufen der Konfiguration",
"error_trying_delete": "Fehler beim Versuch zu löschen: {{error}}", "error_trying_delete": "Fehler beim Versuch zu löschen: {{error}}",
"error_update": "Fehler: {{error}}", "error_update": "Fehler: {{error}}",
"explanation": "Erläuterung", "explanation": "Erläuterung",
"key_pem_explanation": "Bitte .pem-Datei auswählen",
"last_configuration_change": "Konfigurationsänderung", "last_configuration_change": "Konfigurationsänderung",
"last_configuration_download": "Letzter Konfigurations-Download", "last_configuration_download": "Letzter Konfigurations-Download",
"location": "Ort", "location": "Ort",
@@ -213,6 +263,8 @@
"used_by": "Benutzt von", "used_by": "Benutzt von",
"used_by_details": "{{entities}} Entitäten, {{venues}} Veranstaltungsorte und {{devices}} Geräte", "used_by_details": "{{entities}} Entitäten, {{venues}} Veranstaltungsorte und {{devices}} Geräte",
"uuid": "Konfigurations-ID", "uuid": "Konfigurations-ID",
"view_affected_devices": "Betroffene Geräte anzeigen",
"view_config": "Konfiguration anzeigen",
"view_in_use": "In Verwendung anzeigen", "view_in_use": "In Verwendung anzeigen",
"view_json": "Rohe Konfiguration anzeigen" "view_json": "Rohe Konfiguration anzeigen"
}, },
@@ -226,6 +278,38 @@
"connect": { "connect": {
"error_trying_to_connect": "Fehler beim Versuch, eine Verbindung zum Gerät herzustellen: {{error}}" "error_trying_to_connect": "Fehler beim Versuch, eine Verbindung zum Gerät herzustellen: {{error}}"
}, },
"contact": {
"access_pin": "Zugangs-PIN",
"add_contact": "Kontakt hinzufügen",
"contact": "Kontakt",
"create_contact": "Kontakt erstellen",
"currently_selected_contact": "Aktuell ausgewählter Kontakt: {{contact}}",
"delete": "Kontakt löschen?",
"error_assign": "Fehler beim Versuch, Kontakt zuzuweisen: {{error}}",
"error_creation": "Fehler beim Versuch, einen Kontakt zu erstellen: {{error}}",
"error_delete": "Fehler beim Versuch, den Kontakt zu löschen: {{error}}",
"error_fetching_list": "Fehler beim Abrufen der Kontakte",
"error_fetching_single": "Fehler beim Abrufen des Kontakts: {{error}}",
"error_unassign": "Fehler beim Versuch, die Zuweisung des Kontakts aufzuheben: {{error}}",
"first_name": "Vorname",
"identifier": "Identifikator",
"initials": "Initialen",
"last_name": "Nachname",
"no_associated_contact": "Kein zugehöriger Kontakt",
"primary_email": "Erste Email",
"salutation": "Anrede",
"secondary_email": "Alternative Email",
"successful_assign": "Kontakt erfolgreich zugewiesen!",
"successful_creation": "Kontakt Erstellt!",
"successful_delete": "Kontakt erfolgreich gelöscht!",
"successful_unassign": "Erfolgreich nicht zugewiesener Kontakt",
"successful_update": "Kontakt erfolgreich aktualisiert",
"title": "Kontakte",
"type": "Art",
"update_error": "Fehler beim Aktualisieren des Kontakts: {{error}}",
"user_title": "Titel",
"visual": "Korrespondenzname"
},
"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"
@@ -237,8 +321,23 @@
"healthchecks_title": "Healthchecks löschen" "healthchecks_title": "Healthchecks löschen"
}, },
"device": { "device": {
"add_to_blacklist": "Gerät zur Blacklist hinzufügen",
"all_devices": "Alle Geräte",
"blacklisted_on": "Datum",
"capabilities": "Fähigkeiten",
"certificate_explanation": "Zertifikate der angeschlossenen Geräte",
"edit_blacklist": "Gerät auf der schwarzen Liste bearbeiten",
"error_adding_blacklist": "Fehler beim Hinzufügen des Geräts zur Blacklist: {{error}}",
"error_edit_blacklist": "Fehler beim Bearbeiten der schwarzen Liste: {{error}}",
"error_fetching_device": "Fehler beim Abrufen der Geräteinformationen: {{error}}", "error_fetching_device": "Fehler beim Abrufen der Geräteinformationen: {{error}}",
"error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}" "error_fetching_devices": "Fehler beim Abrufen von Geräten: {{error}}",
"health_explanation": "Zustand der verbundenen Geräte ((Geräte = 100 % * 100 + Geräte > 90 % * 95 + Geräte > 60 % * 75 + Geräte < 60 % * 35) / Verbundene Geräte)",
"memory_explanation": "Anzahl verbundener Geräte mit entsprechendem belegtem Speicher %",
"remove_from_blacklist": "Von der schwarzen Liste entfernen",
"success_added_blacklist": "Gerät erfolgreich zur Blacklist hinzugefügt!",
"success_edit_blacklist": "Blacklist erfolgreich bearbeitet!",
"success_removed_blacklist": "Gerät erfolgreich von Blacklist entfernt!",
"uptimes_explanation": "Anzahl der verbundenen Geräte basierend auf ihrer Betriebszeit"
}, },
"device_logs": { "device_logs": {
"log": "Protokoll", "log": "Protokoll",
@@ -248,22 +347,46 @@
"entity": { "entity": {
"add_child": "Untergeordnete Entität zu {{entityName}}hinzufügen", "add_child": "Untergeordnete Entität zu {{entityName}}hinzufügen",
"add_failure": "Fehler, der Server hat zurückgegeben: {{error}}", "add_failure": "Fehler, der Server hat zurückgegeben: {{error}}",
"add_ips": "IPs verwalten",
"add_root": "Root-Entität hinzufügen", "add_root": "Root-Entität hinzufügen",
"add_success": "Entität erfolgreich erstellt!", "add_success": "Entität erfolgreich erstellt!",
"assigned_inventory": "Zugewiesenes Inventar", "assigned_inventory": "Zugewiesenes Inventar",
"cannot_delete": "Entitäten mit untergeordneten Elementen können nicht gelöscht werden. Löschen Sie die untergeordneten Elemente dieser Entität, um sie löschen zu können.", "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.",
"confirm_map_delete": "Möchten Sie die Karte {{name}}wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden",
"currently_selected_entity": "Derzeit ausgewähltes Unternehmen: {{config}}", "currently_selected_entity": "Derzeit ausgewähltes Unternehmen: {{config}}",
"currently_selected_venue": "Aktuell ausgewählter Veranstaltungsort: {{config}}", "currently_selected_venue": "Aktuell ausgewählter Veranstaltungsort: {{config}}",
"delete_success": "Entität erfolgreich gelöscht", "delete_success": "Entität erfolgreich gelöscht",
"delete_warning": "Achtung: Dieser Vorgang kann nicht rückgängig gemacht werden", "delete_warning": "Achtung: Dieser Vorgang kann nicht rückgängig gemacht werden",
"duplicate_from_node": "Mit einem bestimmten Root-Knoten duplizieren",
"duplicate_map": "Karte duplizieren",
"duplicate_with_node": "Dupliziere {{mapName}} mit {{rootName}} als Root-Knoten",
"edit_failure": "Aktualisierung fehlgeschlagen : {{error}}", "edit_failure": "Aktualisierung fehlgeschlagen : {{error}}",
"enter_here": "Geben Sie hier die IP(s) ein, die Sie hinzufügen möchten",
"entire_tree": "Netzwerkkarte",
"entities": "Entitäten", "entities": "Entitäten",
"entity": "Entität", "entity": "Entität",
"error_deleting_map": "Fehler beim Löschen der Karte: {{error}}",
"error_fetch_entity": "Fehler beim Abrufen von Entitätsinformationen", "error_fetch_entity": "Fehler beim Abrufen von Entitätsinformationen",
"error_fetching": "Fehler beim Abrufen von Entitäten", "error_fetching": "Fehler beim Abrufen von Entitäten",
"error_fetching_map": "Fehler beim Abrufen der Karte: {{error}}",
"error_fetching_tree": "Fehler beim Abrufen des Baums: {{error}}",
"error_saving": "Fehler beim Speichern der Entität", "error_saving": "Fehler beim Speichern der Entität",
"error_saving_map": "Fehler beim Speichern der Karte: {{error}}",
"higher_priority": "Stellen Sie eine höhere Priorität ein",
"ip_detection": "IP-Erkennung",
"ip_formats": "Sie können IPv4- oder IPv6-Adressen in den folgenden Formaten hinzufügen:",
"lower_priority": "Niedrigere Priorität setzen",
"map": "Karte",
"map_delete_success": "Karte erfolgreich gelöscht!",
"need_select_entity": "sSie müssen eine Entität aus der folgenden Tabelle auswählen",
"no_ips": "Keine IPs ausgewählt",
"not_assigned": "Nicht zugeordnet", "not_assigned": "Nicht zugeordnet",
"only_unassigned": "Nur nicht zugewiesen", "only_unassigned": "Nur nicht zugewiesen",
"select_entity": "Wählen Sie diese Entität aus",
"selected_entity": "Ausgewählte Einheit",
"selected_map": "Ausgewählte Karte",
"tree_saved": "Karte erfolgreich gespeichert!",
"update_failure_error": "Fehler beim Versuch, die Entität zu aktualisieren: {{error}}",
"valid_serial": "Muss eine gültige Seriennummer sein (12 HEX-Zeichen)", "valid_serial": "Muss eine gültige Seriennummer sein (12 HEX-Zeichen)",
"venues": "Veranstaltungsorte" "venues": "Veranstaltungsorte"
}, },
@@ -275,6 +398,7 @@
"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": { "firmware": {
"age_explanation": "Durchschnittliche Anzahl der Tage für alle Geräte, von denen wir diesen Wert erhalten können",
"average_age": "Durchschnittliches Firmware-Alter", "average_age": "Durchschnittliches Firmware-Alter",
"choose_custom": "Wählen", "choose_custom": "Wählen",
"details_title": "Bild #{{image}} Details", "details_title": "Bild #{{image}} Details",
@@ -287,9 +411,11 @@
"image": "Bild", "image": "Bild",
"image_date": "Bilddatum", "image_date": "Bilddatum",
"installed_firmware": "Installierte Firmware", "installed_firmware": "Installierte Firmware",
"latest_explanation": "Geräte, auf denen erkannte Firmware in der neuesten Version ausgeführt wird",
"latest_version_installed": "Neueste Version installiert Version", "latest_version_installed": "Neueste Version installiert Version",
"newer_firmware_available": "Neuere Versionen verfügbar", "newer_firmware_available": "Neuere Versionen verfügbar",
"reinstall_latest": "Neu installieren", "reinstall_latest": "Neu installieren",
"release": "Veröffentlichung",
"revision": "Revision", "revision": "Revision",
"show_dev": "Dev-Releases anzeigen", "show_dev": "Dev-Releases anzeigen",
"size": "Größe", "size": "Größe",
@@ -318,6 +444,7 @@
"add_tag": "Tag erstellen", "add_tag": "Tag erstellen",
"add_tag_to": "Neues Gerät zu {{name}}hinzufügen", "add_tag_to": "Neues Gerät zu {{name}}hinzufügen",
"add_venue": "Veranstaltungsort hinzufügen", "add_venue": "Veranstaltungsort hinzufügen",
"assign_ent_ven": "Zu Entität oder Veranstaltungsort zuweisen",
"assign_entity_instructions": "Sie können die Entität, der dieses Tag zugewiesen werden soll, entweder über das Menü unten finden oder die UUID der Entität manuell in das Feld oben einfügen.", "assign_entity_instructions": "Sie können die Entität, der dieses Tag zugewiesen werden soll, entweder über das Menü unten finden oder die UUID der Entität manuell in das Feld oben einfügen.",
"assign_error": "Fehler beim Versuch, Tag zuzuweisen", "assign_error": "Fehler beim Versuch, Tag zuzuweisen",
"assign_to_entity": "Zu Entität zuweisen", "assign_to_entity": "Zu Entität zuweisen",
@@ -349,6 +476,7 @@
"error_create_venue": "Fehler beim Erstellen des Veranstaltungsortes", "error_create_venue": "Fehler beim Erstellen des Veranstaltungsortes",
"error_delete_tag": "Fehler beim Löschen des Inventar-Tags", "error_delete_tag": "Fehler beim Löschen des Inventar-Tags",
"error_get_venue": "Fehler beim Abrufen von Veranstaltungsorten", "error_get_venue": "Fehler beim Abrufen von Veranstaltungsorten",
"error_pushing_config": "Fehler beim Versuch, die Konfiguration auf das Gerät zu übertragen: {{error}}",
"error_retrieving": "Beim Abrufen von Inventar-Tags ist ein Fehler aufgetreten", "error_retrieving": "Beim Abrufen von Inventar-Tags ist ein Fehler aufgetreten",
"error_unassign": "Fehler beim Aufheben der Zuweisung", "error_unassign": "Fehler beim Aufheben der Zuweisung",
"error_update_venue": "Fehler beim Aktualisieren des Veranstaltungsorts", "error_update_venue": "Fehler beim Aktualisieren des Veranstaltungsorts",
@@ -394,15 +522,46 @@
"unassigned_deleted_devices": "{{number}} Geräte gelöscht und nicht zugewiesen", "unassigned_deleted_devices": "{{number}} Geräte gelöscht und nicht zugewiesen",
"unassigned_tags": "Nicht zugewiesene Tags", "unassigned_tags": "Nicht zugewiesene Tags",
"validating_import_file": "Importdatei und -daten werden validiert...", "validating_import_file": "Importdatei und -daten werden validiert...",
"venue": "Tagungsort" "venue": "Tagungsort",
"view_in_gateway": "Im Gateway anzeigen"
},
"location": {
"add": "Ort hinzufügen",
"building_name": "Gebäudename",
"city": "Stadt",
"country": "Land",
"create": "Standort erstellen",
"currently_selected": "Aktuell ausgewählter Standort: {{location}}",
"delete": "Ort löschen?",
"error_assign": "Fehler beim Versuch, den Standort zuzuweisen: {{error}}",
"error_creation": "Fehler beim Versuch, Standorte zu erstellen: {{error}}",
"error_delete": "Fehler beim Löschen des Standorts: {{error}}",
"error_fetching_single": "Fehler beim Versuch, den Standort abzurufen: {{error}}",
"geocode": "GeoCode",
"mobiles": "MOBILES",
"no_associated": "Kein zugeordneter Standort",
"phones": "Telefone",
"postal": "Postleitzahl",
"search": "Suchen Sie nach Standorten, um die Felder unten automatisch auszufüllen",
"state": "Zustand",
"street_address": "Adresse",
"successful_creation": "Standort erfolgreich erstellt!",
"successful_delete": "Standort erfolgreich gelöscht!",
"successful_update": "Standort erfolgreich aktualisiert!",
"successfully_assigned": "Standort erfolgreich zugewiesen!",
"title": "Standorte",
"update_error": "Fehler beim Aktualisieren des Standorts: {{error}}"
}, },
"login": { "login": {
"account_verification": "Bestätigung des Kontos",
"authentication_expired": "Authentifizierung abgelaufen, bitte starten Sie den Anmeldevorgang erneut",
"change_password": "Ändere das Passwort", "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_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", "change_password_instructions": "Geben Sie Ihr neues Passwort ein und bestätigen Sie es",
"changing_password": "Passwort ändern...", "changing_password": "Passwort ändern...",
"confirm_new_password": "Bestätige neues Passwort", "confirm_new_password": "Bestätige neues Passwort",
"different_passwords": "Sie müssen das gleiche Passwort zweimal eingeben", "different_passwords": "Sie müssen das gleiche Passwort zweimal eingeben",
"email_code_validation": "Bitte überprüfen Sie Ihr E-Mail-Postfach und geben Sie den Bestätigungscode, den wir Ihnen gerade gesendet haben, in das Feld unten ein",
"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_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_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", "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",
@@ -411,6 +570,7 @@
"login_error": "Anmeldefehler, stellen Sie sicher, dass die von Ihnen angegebenen Informationen gültig sind", "login_error": "Anmeldefehler, stellen Sie sicher, dass die von Ihnen angegebenen Informationen gültig sind",
"new_password": "Neues Kennwort", "new_password": "Neues Kennwort",
"password": "Passwort", "password": "Passwort",
"phone_validation_explanation": "Bitte überprüfen Sie Ihr Mobilgerät und geben Sie den Bestätigungscode, den wir Ihnen gerade gesendet haben, in das Feld unten ein",
"please_enter_gateway": "Bitte geben Sie eine uCentralSec-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",
@@ -419,7 +579,12 @@
"sending_ellipsis": "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",
"url": "uCentralSec-URL", "url": "uCentralSec-URL",
"username": "Benutzername" "username": "Benutzername",
"verification_code": "Geben Sie hier Ihre Bestätigung ein",
"wrong_code": "Der eingegebene Bestätigungscode ist ungültig."
},
"preferences": {
"provisioning": "Bereitstellung"
}, },
"reboot": { "reboot": {
"directions": "Wann möchten Sie dieses Gerät neu starten?", "directions": "Wann möchten Sie dieses Gerät neu starten?",
@@ -431,7 +596,7 @@
"channel": "Kanal", "channel": "Kanal",
"directions": "Starten Sie einen WiFi-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.",
"re_scan": "Erneut scannen", "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.", "result_directions": "Sie können oben rechts auf die Schaltfläche „Scannen“ klicken, um $t(scan.re_scan)",
"results": "Ergebnisse des WiFi-Scans", "results": "Ergebnisse des WiFi-Scans",
"scan": "Scan", "scan": "Scan",
"scanning": "Scannen... ", "scanning": "Scannen... ",
@@ -440,13 +605,69 @@
"settings": { "settings": {
"title": "die Einstellungen" "title": "die Einstellungen"
}, },
"simulation": {
"add": "Simulation hinzufügen",
"cancel": "Stornieren",
"cancel_success": "Simulationslauf erfolgreich abgebrochen!",
"check_ongoing_sims": "Folge laufender Sim",
"client_interval": "Kundenintervall",
"delete_simulation": "Sim löschen. {{name}}",
"end": "Endete",
"error_creating": "Fehler beim Erstellen der Simulation: {{error}}",
"error_delete": "Fehler beim Löschen der Simulation: {{error}}",
"error_devices": "Fehler Geräte",
"error_edit": "Fehler beim Versuch, die Simulation zu speichern: {{error}}",
"error_edit_run": "Fehler beim Versuch, den Ausführungsstatus zu ändern: {{error}}",
"error_fetching_simulations": "Fehler beim Abrufen der Simulationen: {{error}}",
"error_start_run": "Fehler beim Versuch, einen Simulationslauf zu starten: {{error}}",
"gateway": "Tor",
"healtcheck_interval": "Healthcheck-Intervall",
"keep_alive": "Bleib am Leben",
"last_refresh": "Letzte Aktualisierung",
"length": "Länge",
"live_devices": "Live-Geräte",
"mac_prefix": "MAC-Präfix",
"max_associations": "max. Verbände",
"max_clients": "Max. Kunden",
"messages_transmitted": "Nachricht TX",
"min_associations": "Mindest. Verbände",
"min_clients": "Mindest. Kunden",
"pause": "Pause",
"pause_success": "Lauf pausiert!",
"prefix_length": "Erforderlich, muss eine Länge von 6 Zeichen haben",
"previous_runs": "Vorherige Läufe",
"received": "empfangen",
"received_messages": "Nachricht RX",
"reconnect_interval": "Wiederverbindungsintervall",
"resume": "Fortsetzen",
"resume_success": "Lauf wieder aufgenommen!",
"run": "Simulationslauf",
"run_simulation": "Simulation ausführen",
"started": "gestartet",
"state_interval": "Zustandsintervall",
"stop": "Halt",
"stop_success": "Lauf gestoppt!",
"success_creating": "Simulation erfolgreich erstellt!",
"success_run_start": "Erfolgreich gestarteter Lauf!",
"successful_delete": "Simulation erfolgreich gelöscht!",
"successful_edit": "Erfolgreich bearbeitete Simulation!",
"threads": "Themen",
"time_full_devices": "Zeit für volle Geräte",
"title": "Simulationen",
"transmitted": "Übertragen",
"valid_cert": "Gültiges Zertifikat",
"valid_key": "Gültiger Schlüssel"
},
"statistics": { "statistics": {
"data": "Daten (KB)", "data": "Daten (KB)",
"data_mb": "Daten (MB)",
"latest_statistics": "Neueste Statistiken", "latest_statistics": "Neueste Statistiken",
"lifetime_stats": "Lifetime-Statistik", "lifetime_stats": "Lifetime-Statistik",
"memory": "Erinnerung",
"no_interfaces": "Keine Statistiken zur Schnittstellenlebensdauer verfügbar", "no_interfaces": "Keine Statistiken zur Schnittstellenlebensdauer verfügbar",
"show_latest": "Letzte Statistik", "show_latest": "Letzte Statistik",
"title": "Statistiken" "title": "Statistiken",
"used": "Verwendeter Speicher %"
}, },
"status": { "status": {
"connection_status": "Status", "connection_status": "Status",
@@ -458,9 +679,27 @@
"percentage_free": "{{percentage}}% von {{total}} kostenlos", "percentage_free": "{{percentage}}% von {{total}} kostenlos",
"percentage_used": "{{percentage}}% von {{total}} verwendet", "percentage_used": "{{percentage}}% von {{total}} verwendet",
"title": "#{{serialNumber}} Status", "title": "#{{serialNumber}} Status",
"total_memory": "Gesamtspeicher",
"uptime": "Betriebszeit", "uptime": "Betriebszeit",
"used_total_memory": "{{used}} verwendet / {{total}} insgesamt" "used_total_memory": "{{used}} verwendet / {{total}} insgesamt"
}, },
"subscriber": {
"add_device_subscriber_explanation": "Um andere Geräte zu reklamieren, kannst du unsere Suchleiste verwenden oder direkt aus der Tabelle reklamieren. Wenn ein Gerät bereits von einem Benutzer beansprucht wurde, müssen Sie zu dessen Details gehen und die Zuweisung aufheben, bevor Sie es beanspruchen.",
"create": "Abonnenten erstellen",
"devices_one": "{{count}} Gerät",
"devices_other": "{{count}} Geräte",
"edit": "Abonnent bearbeiten",
"error_create": "Fehler beim Erstellen des Abonnenten: {{error}}",
"error_delete": "Fehler beim Löschen des Abonnenten: {{error}}",
"error_fetching": "Fehler beim Abrufen von Abonnenten: {{error}}",
"error_fetching_single": "Fehler beim Abrufen des Abonnenten: {{error}}",
"error_update": "Fehler beim Aktualisieren des Abonnenten: {{error}}",
"is_already_claimed": "wird bereits beansprucht von",
"subscribers": "Abonnenten",
"success_create": "Abonnent erfolgreich erstellt!",
"success_delete": "Abonnent erfolgreich gelöscht!",
"success_update": "Abonnent erfolgreich aktualisiert!"
},
"system": { "system": {
"error_fetching": "Fehler beim Abrufen von Systeminformationen", "error_fetching": "Fehler beim Abrufen von Systeminformationen",
"error_reloading": "Fehler beim Neuladen: {{error}}", "error_reloading": "Fehler beim Neuladen: {{error}}",
@@ -507,10 +746,13 @@
"waiting_for_device": "Warten, bis das Gerät wieder verbunden ist" "waiting_for_device": "Warten, bis das Gerät wieder verbunden ist"
}, },
"user": { "user": {
"add_phone_number": "Telefonnummer hinzufügen",
"avatar": "Dein Avatar", "avatar": "Dein Avatar",
"avatar_file": "Dein Avatar (max. 2 MB)", "avatar_file": "Dein Avatar (max. 2 MB)",
"check_phone": "Bitte überprüfen Sie Ihr Telefon auf Ihren Validierungscode",
"confirm_new_password": "Bestätige neues Passwort",
"create": "Benutzer erstellen", "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_failure": "Fehler beim Erstellen des Benutzers: {{error}}",
"create_success": "Benutzer erfolgreich erstellt", "create_success": "Benutzer erfolgreich erstellt",
"creating": "Benutzer erstellen ...", "creating": "Benutzer erstellen ...",
"delete_avatar": "Avatar löschen", "delete_avatar": "Avatar löschen",
@@ -522,36 +764,51 @@
"description": "Beschreibung", "description": "Beschreibung",
"edit": "Benutzer bearbeiten", "edit": "Benutzer bearbeiten",
"email_address": "E-Mail-Addresse", "email_address": "E-Mail-Addresse",
"enter_new_phone": "Geben Sie Ihre neue Telefonnummer ein:",
"error_fetching_users": "Fehler beim Abrufen der Nutzer: {{error}}", "error_fetching_users": "Fehler beim Abrufen der Nutzer: {{error}}",
"error_retrieving": "Fehler beim Abrufen des Benutzers",
"error_sending_code": "Fehler beim Versuch, den Validierungscode zu senden. Bitte bestätigen Sie, dass Ihre Telefonnummer gültig ist.",
"force_password_change": "Passwortänderung bei der Anmeldung erzwingen", "force_password_change": "Passwortänderung bei der Anmeldung erzwingen",
"id": "Benutzeridentifikation.", "id": "Benutzeridentifikation.",
"last_login": "Letzte Anmeldung", "last_login": "Letzte Anmeldung",
"login_id": "Anmelde-ID.", "login_id": "Anmelde-ID.",
"make_sure_same_password": "Stellen Sie sicher, dass beide Passwörter gleich und gültig sind",
"my_profile": "Mein Profil", "my_profile": "Mein Profil",
"name": "Name", "name": "Name",
"new_code_sent": "Neuer Code gesendet!",
"nickname": "Spitzname", "nickname": "Spitzname",
"nickname_explanation": "Spitzname (optional)", "nickname_explanation": "Spitzname (optional)",
"not_validated": "Nicht validiert", "not_validated": "Nicht validiert",
"note": "Hinweis", "note": "Hinweis",
"password": "Passwort", "password": "Passwort",
"phone_number": "Telefonnummer",
"provide_email": "Bitte geben Sie eine gültige E-Mail Adresse an", "provide_email": "Bitte geben Sie eine gültige E-Mail Adresse an",
"provide_password": "Bitte geben Sie ein gültiges Passwort ein", "provide_password": "Bitte geben Sie ein gültiges Passwort ein",
"save_avatar": "Avatar speichern", "save_avatar": "Avatar speichern",
"send_code": "Code senden",
"send_code_again": "Code nochmal senden",
"show_hide_password": "Passwort anzeigen/verbergen", "show_hide_password": "Passwort anzeigen/verbergen",
"successful_validation": "Telefonnummer bestätigt! Klicken Sie auf die Schaltfläche Speichern, um es mit Ihrem Profil zu verknüpfen",
"table_title": "Admin-Benutzer",
"update_failure": "Fehler beim Aktualisieren: {{error}}", "update_failure": "Fehler beim Aktualisieren: {{error}}",
"update_failure_title": "Update fehlgeschlagen", "update_failure_title": "Update fehlgeschlagen",
"update_success": "Benutzer erfolgreich aktualisiert", "update_success": "Benutzer erfolgreich aktualisiert",
"update_success_title": "Erfolg", "update_success_title": "Erfolg",
"user_role": "Rolle", "user_role": "Rolle",
"users": "Benutzer", "users": "Benutzer",
"validated": "Bestätigt" "validate_phone": "Bestätigen",
"validated": "Bestätigt",
"wrong_validation_code": "Sie haben keinen gültigen Code eingegeben. Bitte versuchen Sie es erneut und vergewissern Sie sich, dass Sie die richtige Telefonnummer eingegeben haben"
}, },
"wifi_analysis": { "wifi_analysis": {
"association": "Verband", "association": "Verband",
"associations": "Verbände", "associations": "Verbände",
"mode": "Modus", "mode": "Modus",
"network_diagram": "Netzwerkdiagramm", "network_diagram": "Netzwerkdiagramm",
"override_dfs": "DFS überschreiben",
"radios": "Radios", "radios": "Radios",
"title": "WLAN-Analyse" "scan_warning": "Ihr 5G-Funkgerät befindet sich auf einem Radarkanal, Sie müssen „Override DFS“ aktivieren, um das Scannen aller 5G-Kanäle zu ermöglichen",
"title": "WLAN-Analyse",
"vendor": "Verkäufer"
} }
} }

View File

@@ -17,11 +17,13 @@
"blink": "Blink", "blink": "Blink",
"device_leds": "Device LEDs", "device_leds": "Device LEDs",
"execute_now": "Would you like to set this pattern now?", "execute_now": "Would you like to set this pattern now?",
"explanation": "What pattern would you like to set on this device for 30 seconds?",
"pattern": "LEDs pattern: ", "pattern": "LEDs pattern: ",
"set_leds": "Set LEDs", "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": {
"command_success": "Command Submitted Successfully",
"error": "Error while submitting command!", "error": "Error while submitting command!",
"error_delete_log": "Error while trying to delete: {{error}}", "error_delete_log": "Error while trying to delete: {{error}}",
"event_queue": "Event Queue", "event_queue": "Event Queue",
@@ -32,41 +34,53 @@
"common": { "common": {
"access_policy": "Access Policy", "access_policy": "Access Policy",
"add": "Add", "add": "Add",
"add_items": "Add Items",
"add_note": "Add Note",
"add_note_explanation": "Write your new note below and click the '+' button where you are done",
"adding_ellipsis": "Adding...", "adding_ellipsis": "Adding...",
"all": "All",
"are_you_sure": "Are you sure?", "are_you_sure": "Are you sure?",
"back_to_login": "Back to Login", "back_to_login": "Back to Login",
"back_to_start": "Back to start", "back_to_start": "Back to start",
"blacklist": "Blacklist",
"by": "By", "by": "By",
"cancel": "Cancel", "cancel": "Cancel",
"certificate": "Certificate", "certificate": "Certificate",
"certificates": "Certificates", "certificates": "Certificates",
"claim": "Claim",
"clear": "Clear", "clear": "Clear",
"close": "Close", "close": "Close",
"code": "Code",
"command": "Command", "command": "Command",
"commands": "Commands", "commands": "Commands",
"commands_executed": "Commands Executed", "commands_executed": "Commands Executed",
"compatible": "Compatible", "compatible": "Compatible",
"completed": "Completed", "completed": "Completed",
"concurrent_devices": "Concurrent Devices",
"config_id": "Config. Id", "config_id": "Config. Id",
"confirm": "Confirm", "confirm": "Confirm",
"confirm_stop_editing": "Are you sure you want to stop editing? This will cancel any unsaved changes you have made.",
"connected": "Connected", "connected": "Connected",
"copied": "Copied!", "copied": "Copied!",
"copied_to_clipboard": "Copied to Clipboard!",
"copy_to_clipboard": "Copy to clipboard", "copy_to_clipboard": "Copy to clipboard",
"create": "Create", "create": "Create",
"created": "Created", "created": "Created",
"created_by": "Created By", "created_by": "Created By",
"creator": "Creator",
"current": "Current ", "current": "Current ",
"custom_date": "Custom Date", "custom_date": "Custom Date",
"dashboard": "Dashboard", "dashboard": "Dashboard",
"date": "Date", "date": "Date",
"day": "day", "day": "day",
"days": "days", "days": "days",
"default_map": "Default Map",
"delete": "Delete", "delete": "Delete",
"delete_device": "Delete Device", "delete_device": "Delete Device",
"details": "Details", "details": "Details",
"device": "Device #{{serialNumber}}", "device": "Device #{{serialNumber}}",
"device_dashboard": "Device Dashboard", "device_dashboard": "Device Dashboard",
"device_delete": "Delete Device #{{serialNumber}}", "device_delete": "Delete #{{serialNumber}}",
"device_deleted": "Device Successfully Deleted", "device_deleted": "Device Successfully Deleted",
"device_health": "Device Health", "device_health": "Device Health",
"device_list": "List of Devices", "device_list": "List of Devices",
@@ -78,6 +92,7 @@
"dismiss": "Dismiss", "dismiss": "Dismiss",
"do_now": "Do Now!", "do_now": "Do Now!",
"download": "Download", "download": "Download",
"duplicate": "Duplicate",
"duration": "Duration", "duration": "Duration",
"edit": "Edit", "edit": "Edit",
"edit_user": "Edit", "edit_user": "Edit",
@@ -86,6 +101,8 @@
"endpoints": "Endpoints", "endpoints": "Endpoints",
"error": "Error", "error": "Error",
"error_adding_note": "Error while adding note", "error_adding_note": "Error while adding note",
"error_code": "Error Code",
"errors": "Errors",
"execute_now": "Would you like to execute this command now?", "execute_now": "Would you like to execute this command now?",
"executed": "Executed", "executed": "Executed",
"exit": "Exit", "exit": "Exit",
@@ -96,12 +113,22 @@
"forgot_password_title": "Forgot Password", "forgot_password_title": "Forgot Password",
"from": "From", "from": "From",
"general_error": "API Error, please consult your administrator", "general_error": "API Error, please consult your administrator",
"go_back": "Go Back",
"hide": "Hide", "hide": "Hide",
"hour": "hour", "hour": "hour",
"hours": "hours", "hours": "hours",
"id": "Id", "id": "Id",
"invalid_credentials": "Invalid username and/or password",
"invalid_date_explanation": "Invalid Date, please use the calendar accessible with the button on the right ",
"invalid_file": "The chosen file was invalid, please read the instructions and adjust your file accordingly",
"invalid_password": "This password does not confirm to basic password rules. Please visit our Password Policy page to learn more",
"invalid_pem": "Your .pem file is invalid. It should start with '-----BEGIN CERTIFICATE-----' OR '-----BEGIN PRIVATE KEY-----' and it should end with '-----END CERTIFICATE-----' OR '-----END PRIVATE KEY-----'",
"ip_address": "IP Address", "ip_address": "IP Address",
"ips": "IPs",
"item": "Item",
"items": "Items",
"items_per_page": "Items per page: ", "items_per_page": "Items per page: ",
"key": "Key",
"last_dashboard_refresh": "Last Dashboard Refresh", "last_dashboard_refresh": "Last Dashboard Refresh",
"later_tonight": "Later tonight", "later_tonight": "Later tonight",
"latest": "Latest", "latest": "Latest",
@@ -110,28 +137,35 @@
"loading_more_ellipsis": "Loading more...", "loading_more_ellipsis": "Loading more...",
"logout": "Logout", "logout": "Logout",
"mac": "MAC Address", "mac": "MAC Address",
"main": "Main",
"manufacturer": "Manufacturer", "manufacturer": "Manufacturer",
"memory_used": "Memory Used", "memory_used": "Memory Used",
"min_max": "Min: {{min}}, Max: {{max}}",
"minute": "minute", "minute": "minute",
"minutes": "minutes", "minutes": "minutes",
"modified": "Modified", "modified": "Modified",
"na": "N/A", "na": "N/A",
"need_date": "You need a date...", "need_date": "You need a date...",
"no": "No", "no": "No",
"no_addresses_found": "No Addresses Found",
"no_devices_found": "No Devices Found", "no_devices_found": "No Devices Found",
"no_items": "No Items", "no_items": "No Items",
"none": "None", "none": "None",
"not_connected": "Not Connected", "not_connected": "Not Connected",
"of_connected": "% of devices", "of_connected": "% of connected devices",
"off": "Off", "off": "Off",
"on": "On", "on": "On",
"optional": "Optional", "optional": "Optional",
"overall_health": "Overall Health", "overall_health": "Overall Health",
"password_policy": "Password Policy", "password_policy": "Password Policy",
"preferences": "Preferences",
"preview": "Preview", "preview": "Preview",
"program": "Program",
"reason": "Reason",
"recorded": "Recorded", "recorded": "Recorded",
"refresh": "Refresh", "refresh": "Refresh",
"refresh_device": "Refresh Device", "refresh_device": "Refresh Device",
"remove_claim": "Remove Claim",
"required": "Required", "required": "Required",
"result": "Result", "result": "Result",
"save": "Save", "save": "Save",
@@ -142,16 +176,21 @@
"second": "second", "second": "second",
"seconds": "seconds", "seconds": "seconds",
"seconds_elapsed": "Seconds elapsed", "seconds_elapsed": "Seconds elapsed",
"see_details": "See Details",
"select": "Select",
"serial_num": "Serial #",
"serial_number": "Serial Number", "serial_number": "Serial Number",
"show_all": "Show All", "show_all": "Show All",
"socket_connection_closed": "Connection closed!", "socket_connection_closed": "Connection closed!",
"start": "Start", "start": "Start",
"status": "Status",
"stop_editing": "Stop Editing", "stop_editing": "Stop Editing",
"submit": "Submit", "submit": "Submit",
"submitted": "Submitted", "submitted": "Submitted",
"success": "Success", "success": "Success",
"system": "System", "system": "System",
"table": "Table", "table": "Table",
"time_per_device": "Devices/Second",
"timestamp": "Time", "timestamp": "Time",
"to": "To", "to": "To",
"type": "Type", "type": "Type",
@@ -162,16 +201,22 @@
"unknown": "Unknown", "unknown": "Unknown",
"up_to_date": "Up to Date Devices", "up_to_date": "Up to Date Devices",
"uptimes": "Uptimes", "uptimes": "Uptimes",
"use_file": "Use File",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendors", "vendors": "Vendors",
"view_more": "View more", "view_more": "View more",
"visibility": "Visibility",
"waiting_for_update": "Waiting for Update",
"yes": "Yes" "yes": "Yes"
}, },
"configuration": { "configuration": {
"add_configuration": "Add Configuration", "add_configuration": "Add Configuration",
"add_new_block": "Add new Configuration Block", "add_new_block": "Add new Configuration Block",
"add_or_link": "Link or Add", "add_or_link": "Link or Add",
"add_radio": "Add Radio",
"ca_cert_explanation": "Please use a .pem file that starts with \"-----BEGIN CERTIFICATE-----\" and ends with \"-----END CERTIFICATE-----\". The result will be shown in the field below. You can also copy and paste the certificate you would like to use directly.",
"cannot_delete": "This configuration cannot be deleted because it is being used by at least one entity, venue or device", "cannot_delete": "This configuration cannot be deleted because it is being used by at least one entity, venue or device",
"choose_radio_band": "What radio band you would like to create?",
"choose_section": "Which section you would like this block to contain?", "choose_section": "Which section you would like this block to contain?",
"configuration_browser": "Configuration Browser", "configuration_browser": "Configuration Browser",
"configurations": "Configurations", "configurations": "Configurations",
@@ -182,18 +227,23 @@
"creation_success": "Configuration successfully created!", "creation_success": "Configuration successfully created!",
"currently_associated": "Currently Associated Configuration: {{config}}", "currently_associated": "Currently Associated Configuration: {{config}}",
"currently_selected_config": "Currently Selected Configuration: {{config}}", "currently_selected_config": "Currently Selected Configuration: {{config}}",
"default_configs": "Default Configs",
"default_configurations": "Default Configurations",
"delete_config": "Delete Config", "delete_config": "Delete Config",
"details": "Details", "details": "Details",
"device_password": "Password", "device_password": "Password",
"device_type": "Device Type", "device_type": "Device Type",
"device_types": "Device Types", "device_types": "Device Types",
"devices_affected": "Devices affected by this configuration: ",
"edit_configuration": "Edit Configuration", "edit_configuration": "Edit Configuration",
"error_delete": "Error while trying to delete: {{error}}", "error_delete": "Error while trying to delete: {{error}}",
"error_delete_blacklist": "Error deleting from blacklist: {{error}}",
"error_fetching_config": "Error while fetching configuration", "error_fetching_config": "Error while fetching configuration",
"error_trying_delete": "Error while trying to delete: {{error}}", "error_trying_delete": "Error while trying to delete: {{error}}",
"error_update": "Error: {{error}}", "error_update": "Error: {{error}}",
"explanation": "Explanation", "explanation": "Explanation",
"last_configuration_change": "Config Change", "key_pem_explanation": "Please select .pem file",
"last_configuration_change": "Configuration Change",
"last_configuration_download": "Last Configuration Download", "last_configuration_download": "Last Configuration Download",
"location": "Location", "location": "Location",
"need_device_type": "Every configuration needs to support at least one device type", "need_device_type": "Every configuration needs to support at least one device type",
@@ -213,6 +263,8 @@
"used_by": "Used By", "used_by": "Used By",
"used_by_details": "{{entities}} Entities, {{venues}} Venues and {{devices}} Devices", "used_by_details": "{{entities}} Entities, {{venues}} Venues and {{devices}} Devices",
"uuid": "Config ID", "uuid": "Config ID",
"view_affected_devices": "View Affected Devices",
"view_config": "View Configuration",
"view_in_use": "View In Use", "view_in_use": "View In Use",
"view_json": "View raw JSON" "view_json": "View raw JSON"
}, },
@@ -226,6 +278,38 @@
"connect": { "connect": {
"error_trying_to_connect": "Error while trying to connect to device: {{error}}" "error_trying_to_connect": "Error while trying to connect to device: {{error}}"
}, },
"contact": {
"access_pin": "Access PIN",
"add_contact": "Add Contact",
"contact": "Contact",
"create_contact": "Create Contact",
"currently_selected_contact": "Currently Selected Contact: {{contact}}",
"delete": "Delete Contact?",
"error_assign": "Error while trying to assign contact: {{error}}",
"error_creation": "Error while trying to create contact: {{error}}",
"error_delete": "Error trying to delete contact: {{error}}",
"error_fetching_list": "Error fetching contacts",
"error_fetching_single": "Error fetching contact: {{error}}",
"error_unassign": "Error while trying to unassign contact: {{error}}",
"first_name": "First Name",
"identifier": "Identifier",
"initials": "Initials",
"last_name": "Last Name",
"no_associated_contact": "No Associated Contact",
"primary_email": "Primary Email",
"salutation": "Salutation",
"secondary_email": "Secondary Email",
"successful_assign": "Successfully assigned contact!",
"successful_creation": "Contact Created!",
"successful_delete": "Successfully Deleted Contact!",
"successful_unassign": "Successfully Unassigned Contact",
"successful_update": "Successfully Updated Contact",
"title": "Contacts",
"type": "Type",
"update_error": "Error updating contact: {{error}}",
"user_title": "Title",
"visual": "Correspondence Name"
},
"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"
@@ -237,8 +321,23 @@
"healthchecks_title": "Delete Healthchecks" "healthchecks_title": "Delete Healthchecks"
}, },
"device": { "device": {
"add_to_blacklist": "Add Device To Blacklist",
"all_devices": "All Devices",
"blacklisted_on": "Date",
"capabilities": "Capabilities",
"certificate_explanation": "Certificates of connected devices",
"edit_blacklist": "Edit Blacklisted Device",
"error_adding_blacklist": "Error adding device to blacklist: {{error}}",
"error_edit_blacklist": "Error editing blacklist: {{error}}",
"error_fetching_device": "Error fetching device information: {{error}}", "error_fetching_device": "Error fetching device information: {{error}}",
"error_fetching_devices": "Error while fetching devices: {{error}}" "error_fetching_devices": "Error while fetching devices: {{error}}",
"health_explanation": "Health of connected devices ((Devices=100% * 100 + Devices>90% * 95 + Devices>60% * 75 + Devices<60% * 35) / ConnectedDevices)",
"memory_explanation": "Amount of connected devices with corresponding memory used percentage",
"remove_from_blacklist": "Remove from blacklist",
"success_added_blacklist": "Device successfully added to blacklist!",
"success_edit_blacklist": "Successfully edited blacklist!",
"success_removed_blacklist": "Successfully removed device from blacklist!",
"uptimes_explanation": "Amount of devices connected based on their uptime"
}, },
"device_logs": { "device_logs": {
"log": "Log", "log": "Log",
@@ -248,22 +347,46 @@
"entity": { "entity": {
"add_child": "Add Child Entity to {{entityName}}", "add_child": "Add Child Entity to {{entityName}}",
"add_failure": "Error, the server returned : {{error}}", "add_failure": "Error, the server returned : {{error}}",
"add_ips": "Manage IPs",
"add_root": "Add Root Entity", "add_root": "Add Root Entity",
"add_success": "Entity Successfully Created!", "add_success": "Entity Successfully Created!",
"assigned_inventory": "Assigned Inventory", "assigned_inventory": "Assigned Inventory",
"cannot_delete": "You cannot delete entities which have children. Delete this entity's children to be able to delete it.", "cannot_delete": "You cannot delete entities which have children. Delete this entity's children to be able to delete it.",
"confirm_map_delete": "Are you sure you want to delete the map {{name}}? This action cannot be reverted",
"currently_selected_entity": "Currently Selected Entity: {{config}}", "currently_selected_entity": "Currently Selected Entity: {{config}}",
"currently_selected_venue": "Currently Selected Venue: {{config}}", "currently_selected_venue": "Currently Selected Venue: {{config}}",
"delete_success": "Entity Successfully Deleted", "delete_success": "Entity Successfully Deleted",
"delete_warning": "Warning: this operation cannot be reverted", "delete_warning": "Warning: this operation cannot be reverted",
"duplicate_from_node": "Duplicate with specific Root Node",
"duplicate_map": "Duplicate Map",
"duplicate_with_node": "Duplicate {{mapName}} with {{rootName}} as root node",
"edit_failure": "Update unsuccessful : {{error}}", "edit_failure": "Update unsuccessful : {{error}}",
"enter_here": "Enter the IP(s) you'd like to add here",
"entire_tree": "Network Map",
"entities": "Entities", "entities": "Entities",
"entity": "Entity", "entity": "Entity",
"error_deleting_map": "Error deleting map: {{error}}",
"error_fetch_entity": "Error while fetching entity information", "error_fetch_entity": "Error while fetching entity information",
"error_fetching": "Error while fetching entities", "error_fetching": "Error while fetching entities",
"error_fetching_map": "Error fetching map: {{error}}",
"error_fetching_tree": "Error while fetching tree: {{error}}",
"error_saving": "Error while saving entity", "error_saving": "Error while saving entity",
"error_saving_map": "Error saving map: {{error}}",
"higher_priority": "Make Higher Priority",
"ip_detection": "IP Detection",
"ip_formats": "You can add IPv4 or IPv6 addresses in the following formats:",
"lower_priority": "Make Lower Priority",
"map": "Map",
"map_delete_success": "Map Successfully Deleted!",
"need_select_entity": "You need to select an entity from the table below",
"no_ips": "No IPs selected",
"not_assigned": "Not Assigned", "not_assigned": "Not Assigned",
"only_unassigned": "Only Unassigned", "only_unassigned": "Only Unassigned",
"select_entity": "Select this Entity",
"selected_entity": "Selected Entity",
"selected_map": "Selected Map",
"tree_saved": "Map Successfully Saved!",
"update_failure_error": "Error while trying to update entity: {{error}}",
"valid_serial": "Needs to be a valid serial number (12 HEX characters)", "valid_serial": "Needs to be a valid serial number (12 HEX characters)",
"venues": "Venues" "venues": "Venues"
}, },
@@ -275,6 +398,7 @@
"warning": "Warning: Once you submit this cannot be reverted" "warning": "Warning: Once you submit this cannot be reverted"
}, },
"firmware": { "firmware": {
"age_explanation": "Average number of days for all devices from which we can obtain that value",
"average_age": "Average Firmware Age", "average_age": "Average Firmware Age",
"choose_custom": "Choose", "choose_custom": "Choose",
"details_title": "Image #{{image}} Details", "details_title": "Image #{{image}} Details",
@@ -287,9 +411,11 @@
"image": "Image", "image": "Image",
"image_date": "Image Date", "image_date": "Image Date",
"installed_firmware": "Installed Firmware", "installed_firmware": "Installed Firmware",
"latest_explanation": "Devices running recognized firmware at its latest version",
"latest_version_installed": "Latest Version Installed", "latest_version_installed": "Latest Version Installed",
"newer_firmware_available": "Newer Revisions Available", "newer_firmware_available": "Newer Revisions Available",
"reinstall_latest": "Reinstall ", "reinstall_latest": "Reinstall ",
"release": "Release",
"revision": "Revision", "revision": "Revision",
"show_dev": "Show Dev Releases", "show_dev": "Show Dev Releases",
"size": "Size", "size": "Size",
@@ -318,6 +444,7 @@
"add_tag": "Create Tag", "add_tag": "Create Tag",
"add_tag_to": "Add New Device to {{name}}", "add_tag_to": "Add New Device to {{name}}",
"add_venue": "Add Venue", "add_venue": "Add Venue",
"assign_ent_ven": "Assign to Entity or Venue",
"assign_entity_instructions": "You can either find the entity you want this tag to be assigned to by using the menu below, or you can manually paste the entity's UUID in the field above.", "assign_entity_instructions": "You can either find the entity you want this tag to be assigned to by using the menu below, or you can manually paste the entity's UUID in the field above.",
"assign_error": "Error while trying to assign tag", "assign_error": "Error while trying to assign tag",
"assign_to_entity": "Assign to Entity", "assign_to_entity": "Assign to Entity",
@@ -349,6 +476,7 @@
"error_create_venue": "Error while creating venue", "error_create_venue": "Error while creating venue",
"error_delete_tag": "Error while deleting inventory tag", "error_delete_tag": "Error while deleting inventory tag",
"error_get_venue": "Error while retrieving venues", "error_get_venue": "Error while retrieving venues",
"error_pushing_config": "Error while trying to push configuration to device: {{error}}",
"error_retrieving": "Error occurred while retrieving inventory tags", "error_retrieving": "Error occurred while retrieving inventory tags",
"error_unassign": "Error during unassign operation", "error_unassign": "Error during unassign operation",
"error_update_venue": "Error while updating venue", "error_update_venue": "Error while updating venue",
@@ -394,15 +522,46 @@
"unassigned_deleted_devices": "{{number}} Devices Deleted and Unassigned", "unassigned_deleted_devices": "{{number}} Devices Deleted and Unassigned",
"unassigned_tags": "Unassigned tags", "unassigned_tags": "Unassigned tags",
"validating_import_file": "Validating import file and data...", "validating_import_file": "Validating import file and data...",
"venue": "Venue" "venue": "Venue",
"view_in_gateway": "Details in Gateway"
},
"location": {
"add": "Add Location",
"building_name": "Building Name",
"city": "City",
"country": "Country",
"create": "Create Location",
"currently_selected": "Currently Selected Location: {{location}}",
"delete": "Delete Location?",
"error_assign": "Error while trying to assign location: {{error}}",
"error_creation": "Error while trying to create locations: {{error}}",
"error_delete": "Error while deleting location: {{error}}",
"error_fetching_single": "Error while trying to fetch location: {{error}}",
"geocode": "GeoCode",
"mobiles": "Mobiles",
"no_associated": "No Associated Location",
"phones": "Phones",
"postal": "ZIP/Postal Code",
"search": "Search locations to auto fill the fields below",
"state": "State",
"street_address": "Street Address",
"successful_creation": "Location Successfully Created!",
"successful_delete": "Successfully Deleted Location!",
"successful_update": "Successfully updated location!",
"successfully_assigned": "Location Successfully Assigned!",
"title": "Locations",
"update_error": "Error updating location: {{error}}"
}, },
"login": { "login": {
"account_verification": "Account Verification",
"authentication_expired": "Authentication expired, please start the login process again",
"change_password": "Change Password", "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_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", "change_password_instructions": "Enter and confirm your new password",
"changing_password": "Changing Password... ", "changing_password": "Changing Password... ",
"confirm_new_password": "Confirm New Password", "confirm_new_password": "Confirm New Password",
"different_passwords": "You need to enter the same password twice", "different_passwords": "You need to enter the same password twice",
"email_code_validation": "Please check your email box and enter the verification code we have just sent you in the box below",
"forgot_password_error": "Error while trying to send Forgot Password email. Please make sure this userId is associated to an account.", "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_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", "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",
@@ -411,6 +570,7 @@
"login_error": "Login error, make sure the information you are providing is valid", "login_error": "Login error, make sure the information you are providing is valid",
"new_password": "New Password", "new_password": "New Password",
"password": "Password", "password": "Password",
"phone_validation_explanation": "Please check your mobile device and enter the verification code we have just sent you in the box below",
"please_enter_gateway": "Please enter a uCentralSec 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",
@@ -419,7 +579,12 @@
"sending_ellipsis": "Sending... ", "sending_ellipsis": "Sending... ",
"sign_in_to_account": "Sign in to your account", "sign_in_to_account": "Sign in to your account",
"url": "uCentralSec URL", "url": "uCentralSec URL",
"username": "Username" "username": "Username",
"verification_code": "Enter your verification here",
"wrong_code": "The verification code that was entered is not valid. "
},
"preferences": {
"provisioning": "Provisioning"
}, },
"reboot": { "reboot": {
"directions": "When would you like to reboot this device?", "directions": "When would you like to reboot this device?",
@@ -431,7 +596,7 @@
"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.",
"re_scan": "Re-Scan", "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.", "result_directions": "You can click the 'Scan' button at the top right to $t(scan.re_scan)",
"results": "Wi-Fi Scan Results", "results": "Wi-Fi Scan Results",
"scan": "Scan", "scan": "Scan",
"scanning": "Scanning... ", "scanning": "Scanning... ",
@@ -440,13 +605,69 @@
"settings": { "settings": {
"title": "Settings" "title": "Settings"
}, },
"simulation": {
"add": "Add Simulation",
"cancel": "Cancel",
"cancel_success": "Simulation Run Successfully Cancelled!",
"check_ongoing_sims": "Follow Ongoing Sim",
"client_interval": "Client Interval",
"delete_simulation": "Delete Sim. {{name}}",
"end": "Ended",
"error_creating": "Error creating simulation: {{error}}",
"error_delete": "Error while deleting simulation: {{error}}",
"error_devices": "Error Devices",
"error_edit": "Error while trying to save simulation: {{error}}",
"error_edit_run": "Error while trying to change run state: {{error}}",
"error_fetching_simulations": "Error fetching simulations: {{error}}",
"error_start_run": "Error while trying to start a simulation run: {{error}}",
"gateway": "Gateway",
"healtcheck_interval": "Healthcheck Interval",
"keep_alive": "Keep Alive",
"last_refresh": "Last Refresh",
"length": "Length",
"live_devices": "Live Devices",
"mac_prefix": "MAC Prefix",
"max_associations": "Max. Associations",
"max_clients": "Max. Clients",
"messages_transmitted": "Msgs TX",
"min_associations": "Min. Associations",
"min_clients": "Min. Clients",
"pause": "Pause",
"pause_success": "Run Paused!",
"prefix_length": "Required, needs to be of a length of 6 characters",
"previous_runs": "Previous Runs",
"received": "Received",
"received_messages": "Msgs RX",
"reconnect_interval": "Reconnect Interval",
"resume": "Resume",
"resume_success": "Run Resumed!",
"run": "Simulation Run",
"run_simulation": "Run Simulation",
"started": "Started",
"state_interval": "State Interval",
"stop": "Stop",
"stop_success": "Run Stopped!",
"success_creating": "Simulation Successfully Created!",
"success_run_start": "Successfully Started Run!",
"successful_delete": "Successfully Deleted Simulation!",
"successful_edit": "Successfully Edited Simulation!",
"threads": "Threads",
"time_full_devices": "Time to Full Devices",
"title": "Simulations",
"transmitted": "Transmitted",
"valid_cert": "Valid Certificate",
"valid_key": "Valid Key"
},
"statistics": { "statistics": {
"data": "Data (KB)", "data": "Data (KB)",
"data_mb": "Data (MB)",
"latest_statistics": "Latest Statistics", "latest_statistics": "Latest Statistics",
"lifetime_stats": "Lifetime Statistics", "lifetime_stats": "Lifetime Statistics",
"memory": "Memory",
"no_interfaces": "No interface lifetime statistics available", "no_interfaces": "No interface lifetime statistics available",
"show_latest": "Last Statistics", "show_latest": "Last Statistics",
"title": "Statistics" "title": "Statistics",
"used": "Used Memory %"
}, },
"status": { "status": {
"connection_status": "Status", "connection_status": "Status",
@@ -458,9 +679,27 @@
"percentage_free": "{{percentage}}% of {{total}} free", "percentage_free": "{{percentage}}% of {{total}} free",
"percentage_used": "{{percentage}}% of {{total}} used", "percentage_used": "{{percentage}}% of {{total}} used",
"title": "#{{serialNumber}} Status", "title": "#{{serialNumber}} Status",
"total_memory": "Total Memory",
"uptime": "Uptime", "uptime": "Uptime",
"used_total_memory": "{{used}} used / {{total}} total " "used_total_memory": "{{used}} used / {{total}} total "
}, },
"subscriber": {
"add_device_subscriber_explanation": "To claim devices, you can use our search bar or claim directly from the table. If a device was already claimed by a user, you will need to go to to their details and unassign it before claiming it.",
"create": "Create Subscriber",
"devices_one": "{{count}} Device",
"devices_other": "{{count}} Devices",
"edit": "Edit Subscriber",
"error_create": "Error creating subscriber: {{error}}",
"error_delete": "Error deleting subscriber: {{error}}",
"error_fetching": "Error fetching subscribers: {{error}}",
"error_fetching_single": "Error fetching subscriber: {{error}}",
"error_update": "Error updating subscriber: {{error}}",
"is_already_claimed": "is already claimed by ",
"subscribers": "Subscribers",
"success_create": "Subscriber successfully created!",
"success_delete": "Subscriber successfully deleted!",
"success_update": "Successfully updated subscriber!"
},
"system": { "system": {
"error_fetching": "Error while fetching system information", "error_fetching": "Error while fetching system information",
"error_reloading": "Error while reloading: {{error}}", "error_reloading": "Error while reloading: {{error}}",
@@ -507,10 +746,13 @@
"waiting_for_device": "Waiting for device to reconnect" "waiting_for_device": "Waiting for device to reconnect"
}, },
"user": { "user": {
"add_phone_number": "Add Phone Number",
"avatar": "Your Avatar", "avatar": "Your Avatar",
"avatar_file": "Your Avatar (max. of 2 MB)", "avatar_file": "Your Avatar (max. of 2 MB)",
"check_phone": "Please check your phone for your validation code",
"confirm_new_password": "Confirm New Password",
"create": "Create User", "create": "Create User",
"create_failure": "Error while creating user. Please make sure this email address is not already linked to an account.", "create_failure": "Error while creating user: {{error}}",
"create_success": "User Created Successfully", "create_success": "User Created Successfully",
"creating": "Creating User...", "creating": "Creating User...",
"delete_avatar": "Delete Avatar", "delete_avatar": "Delete Avatar",
@@ -522,36 +764,51 @@
"description": "Description", "description": "Description",
"edit": "Edit User", "edit": "Edit User",
"email_address": "Email Address", "email_address": "Email Address",
"enter_new_phone": "Enter your new phone number: ",
"error_fetching_users": "Error fetching users: {{error}}", "error_fetching_users": "Error fetching users: {{error}}",
"error_retrieving": "Error retrieving user",
"error_sending_code": "Error while trying to send validation code. Please confirm that your phone number is valid.",
"force_password_change": "Force Password Change on Login", "force_password_change": "Force Password Change on Login",
"id": "User Id.", "id": "User Id.",
"last_login": "Last Login", "last_login": "Last Login",
"login_id": "Login Id.", "login_id": "Login Id.",
"make_sure_same_password": "Make sure both passwords are the same and are valid",
"my_profile": "My Profile", "my_profile": "My Profile",
"name": "Name", "name": "Name",
"new_code_sent": "New Code Sent!",
"nickname": "Nickname", "nickname": "Nickname",
"nickname_explanation": "Nickname (optional)", "nickname_explanation": "Nickname (optional)",
"not_validated": "Not Validated", "not_validated": "Not Validated",
"note": "Note", "note": "Note",
"password": "Password", "password": "Password",
"phone_number": "Phone Number",
"provide_email": "Please provide a valid email address", "provide_email": "Please provide a valid email address",
"provide_password": "Please provide a valid password", "provide_password": "Please provide a valid password",
"save_avatar": "Save Avatar", "save_avatar": "Save Avatar",
"send_code": "Send Code",
"send_code_again": "Send Code Again",
"show_hide_password": "Show/Hide Password", "show_hide_password": "Show/Hide Password",
"successful_validation": "Phone Number Validated! Click the save button to link it to your profile",
"table_title": "Admin Users",
"update_failure": "Error while trying to update: {{error}}", "update_failure": "Error while trying to update: {{error}}",
"update_failure_title": "Update Failed", "update_failure_title": "Update Failed",
"update_success": "User Updated Successfully", "update_success": "User Updated Successfully",
"update_success_title": "Success", "update_success_title": "Success",
"user_role": "Role", "user_role": "Role",
"users": "Users", "users": "Users",
"validated": "Validated" "validate_phone": "Validate",
"validated": "Validated",
"wrong_validation_code": "You have not entered a valid code. Please try again and make sure you have entered the right phone number"
}, },
"wifi_analysis": { "wifi_analysis": {
"association": "Association", "association": "Association",
"associations": "Associations", "associations": "Associations",
"mode": "Mode", "mode": "Mode",
"network_diagram": "Network Diagram", "network_diagram": "Network Diagram",
"override_dfs": "Override DFS",
"radios": "Radios", "radios": "Radios",
"title": "Wi-Fi Analysis" "scan_warning": "Your 5G radio is on a radar channel, you must enable “Override DFS” to allow scanning of all 5G channels",
"title": "Wi-Fi Analysis",
"vendor": "Vendor"
} }
} }

View File

@@ -17,11 +17,13 @@
"blink": "Parpadeo", "blink": "Parpadeo",
"device_leds": "LED de dispositivo", "device_leds": "LED de dispositivo",
"execute_now": "¿Le gustaría establecer este patrón ahora?", "execute_now": "¿Le gustaría establecer este patrón ahora?",
"explanation": "¿Qué patrón le gustaría establecer en este dispositivo durante 30 segundos?",
"pattern": "Elija el patrón que le gustaría usar:", "pattern": "Elija el patrón que le gustaría usar:",
"set_leds": "Establecer LED", "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": {
"command_success": "Comando enviado con éxito",
"error": "¡Error al enviar el comando!", "error": "¡Error al enviar el comando!",
"error_delete_log": "Error al intentar eliminar: {{error}}", "error_delete_log": "Error al intentar eliminar: {{error}}",
"event_queue": "Cola de eventos", "event_queue": "Cola de eventos",
@@ -32,41 +34,53 @@
"common": { "common": {
"access_policy": "Política de acceso", "access_policy": "Política de acceso",
"add": "Añadir", "add": "Añadir",
"add_items": "Agregar articulos",
"add_note": "Añadir la nota",
"add_note_explanation": "Escriba su nueva nota a continuación y haga clic en el botón '+' donde haya terminado",
"adding_ellipsis": "Añadiendo ...", "adding_ellipsis": "Añadiendo ...",
"all": "TODOS",
"are_you_sure": "¿Estás seguro?", "are_you_sure": "¿Estás seguro?",
"back_to_login": "Atrás para iniciar sesión", "back_to_login": "Atrás para iniciar sesión",
"back_to_start": "volver a empezar", "back_to_start": "volver a empezar",
"blacklist": "Lista negra",
"by": "Por", "by": "Por",
"cancel": "Cancelar", "cancel": "Cancelar",
"certificate": "Certificado", "certificate": "Certificado",
"certificates": "Certificados", "certificates": "Certificados",
"claim": "Reclamación",
"clear": "Claro", "clear": "Claro",
"close": "Cerrar", "close": "Cerrar",
"code": "Código",
"command": "Mando", "command": "Mando",
"commands": "comandos", "commands": "comandos",
"commands_executed": "Comandos ejecutados", "commands_executed": "Comandos ejecutados",
"compatible": "Compatible", "compatible": "Compatible",
"completed": "terminado", "completed": "terminado",
"concurrent_devices": "Dispositivos concurrentes",
"config_id": "Config. Identificación", "config_id": "Config. Identificación",
"confirm": "Confirmar", "confirm": "Confirmar",
"confirm_stop_editing": "¿Estás seguro de que quieres dejar de editar? Esto cancelará cualquier cambio no guardado que haya realizado.",
"connected": "Conectado", "connected": "Conectado",
"copied": "Copiado!", "copied": "Copiado!",
"copied_to_clipboard": "¡Copiado al portapapeles!",
"copy_to_clipboard": "Copiar al portapapeles", "copy_to_clipboard": "Copiar al portapapeles",
"create": "Crear", "create": "Crear",
"created": "creado", "created": "creado",
"created_by": "Creado por", "created_by": "Creado por",
"creator": "Creador",
"current": "Corriente", "current": "Corriente",
"custom_date": "Fecha personalizada", "custom_date": "Fecha personalizada",
"dashboard": "Tablero", "dashboard": "Tablero",
"date": "Fecha", "date": "Fecha",
"day": "día", "day": "día",
"days": "días", "days": "días",
"default_map": "Mapa predeterminado",
"delete": "Borrar", "delete": "Borrar",
"delete_device": "Eliminar dispositivo", "delete_device": "Eliminar dispositivo",
"details": "Detalles", "details": "Detalles",
"device": "Dispositivo n.º{{serialNumber}}", "device": "Dispositivo n.º{{serialNumber}}",
"device_dashboard": "Panel de control del dispositivo", "device_dashboard": "Panel de control del dispositivo",
"device_delete": "Eliminar dispositivo n.º{{serialNumber}}", "device_delete": "Eliminar #{{serialNumber}}",
"device_deleted": "Dispositivo eliminado correctamente", "device_deleted": "Dispositivo eliminado correctamente",
"device_health": "Salud del dispositivo", "device_health": "Salud del dispositivo",
"device_list": "Listado de dispositivos", "device_list": "Listado de dispositivos",
@@ -78,6 +92,7 @@
"dismiss": "Despedir", "dismiss": "Despedir",
"do_now": "¡Hagan ahora!", "do_now": "¡Hagan ahora!",
"download": "Descargar", "download": "Descargar",
"duplicate": "Duplicar",
"duration": "Duración", "duration": "Duración",
"edit": "Editar", "edit": "Editar",
"edit_user": "Editar", "edit_user": "Editar",
@@ -86,6 +101,8 @@
"endpoints": "Puntos finales", "endpoints": "Puntos finales",
"error": "Error", "error": "Error",
"error_adding_note": "Error al agregar una nota", "error_adding_note": "Error al agregar una nota",
"error_code": "código de error",
"errors": "Los errores",
"execute_now": "¿Le gustaría ejecutar este comando ahora?", "execute_now": "¿Le gustaría ejecutar este comando ahora?",
"executed": "ejecutado", "executed": "ejecutado",
"exit": "salida", "exit": "salida",
@@ -96,12 +113,22 @@
"forgot_password_title": "Se te olvidó 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", "general_error": "Error de API, consulte a su administrador",
"go_back": "Regresa",
"hide": "Esconder", "hide": "Esconder",
"hour": "hora", "hour": "hora",
"hours": "horas", "hours": "horas",
"id": "Carné de identidad", "id": "Carné de identidad",
"invalid_credentials": "Nombre de usuario y / o contraseña inválido",
"invalid_date_explanation": "Fecha no válida, utilice el calendario accesible con el botón de la derecha",
"invalid_file": "El archivo elegido no es válido, lea las instrucciones y ajuste su archivo en consecuencia",
"invalid_password": "Esta contraseña no confirma las reglas básicas de contraseña. Visite nuestra página de Política de contraseñas para obtener más información.",
"invalid_pem": "Su archivo .pem no es válido. Debe comenzar con '----- BEGIN CERTIFICATE -----' O '----- BEGIN PRIVATE KEY -----' y debe terminar con '----- END CERTIFICATE --- - 'O' ----- FIN DE CLAVE PRIVADA ----- '",
"ip_address": "Dirección IP", "ip_address": "Dirección IP",
"ips": "IPs",
"item": "ít",
"items": "artículos",
"items_per_page": "Artículos por página:", "items_per_page": "Artículos por página:",
"key": "Llave",
"last_dashboard_refresh": "Última actualización del panel", "last_dashboard_refresh": "Última actualización del panel",
"later_tonight": "Más tarde esta noche", "later_tonight": "Más tarde esta noche",
"latest": "último", "latest": "último",
@@ -110,28 +137,35 @@
"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",
"main": "Principal",
"manufacturer": "Fabricante", "manufacturer": "Fabricante",
"memory_used": "Memoria usada", "memory_used": "Memoria usada",
"min_max": "Mín .: {{min}}, Máx .: {{max}}",
"minute": "minuto", "minute": "minuto",
"minutes": "minutos", "minutes": "minutos",
"modified": "Modificado", "modified": "Modificado",
"na": "N / A", "na": "N / A",
"need_date": "Necesitas una cita ...", "need_date": "Necesitas una cita ...",
"no": "No", "no": "No",
"no_addresses_found": "No se encontraron direcciones",
"no_devices_found": "No se encontraron dispositivos", "no_devices_found": "No se encontraron dispositivos",
"no_items": "No hay articulos", "no_items": "No hay articulos",
"none": "Ninguna", "none": "Ninguna",
"not_connected": "No conectado", "not_connected": "No conectado",
"of_connected": "% de dispositivos", "of_connected": "% de dispositivos conectados",
"off": "Apagado", "off": "Apagado",
"on": "en", "on": "en",
"optional": "Opcional", "optional": "Opcional",
"overall_health": "Salud en general", "overall_health": "Salud en general",
"password_policy": "Política de contraseñas", "password_policy": "Política de contraseñas",
"preferences": "Preferencias",
"preview": "Avance", "preview": "Avance",
"program": "Programa",
"reason": "Razón",
"recorded": "Grabado", "recorded": "Grabado",
"refresh": "Refrescar", "refresh": "Refrescar",
"refresh_device": "Actualizar dispositivo", "refresh_device": "Actualizar dispositivo",
"remove_claim": "Quitar reclamo",
"required": "Necesario", "required": "Necesario",
"result": "Resultado", "result": "Resultado",
"save": "Salvar", "save": "Salvar",
@@ -142,16 +176,21 @@
"second": "segundo", "second": "segundo",
"seconds": "segundos", "seconds": "segundos",
"seconds_elapsed": "Segundos transcurridos", "seconds_elapsed": "Segundos transcurridos",
"see_details": "Ver detalles",
"select": "Seleccionar",
"serial_num": "Número de serie",
"serial_number": "Número de serie", "serial_number": "Número de serie",
"show_all": "Mostrar todo", "show_all": "Mostrar todo",
"socket_connection_closed": "¡Conexión cerrada!", "socket_connection_closed": "¡Conexión cerrada!",
"start": "comienzo", "start": "comienzo",
"status": "Estado",
"stop_editing": "Dejar de editar", "stop_editing": "Dejar de editar",
"submit": "Enviar", "submit": "Enviar",
"submitted": "Presentado", "submitted": "Presentado",
"success": "Éxito", "success": "Éxito",
"system": "Sistema", "system": "Sistema",
"table": "Mesa", "table": "Mesa",
"time_per_device": "Dispositivo / segundo",
"timestamp": "hora", "timestamp": "hora",
"to": "a", "to": "a",
"type": "Tipo", "type": "Tipo",
@@ -162,16 +201,22 @@
"unknown": "Desconocido", "unknown": "Desconocido",
"up_to_date": "Dispositivos actualizados", "up_to_date": "Dispositivos actualizados",
"uptimes": "Tiempos de actividad", "uptimes": "Tiempos de actividad",
"use_file": "Usar archivo",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendedores", "vendors": "Vendedores",
"view_more": "Ver más", "view_more": "Ver más",
"visibility": "Visibilidad",
"waiting_for_update": "Esperando actualización",
"yes": "Sí" "yes": "Sí"
}, },
"configuration": { "configuration": {
"add_configuration": "Agregar configuración", "add_configuration": "Agregar configuración",
"add_new_block": "Agregar nuevo bloque de configuración", "add_new_block": "Agregar nuevo bloque de configuración",
"add_or_link": "Vincular o agregar", "add_or_link": "Vincular o agregar",
"add_radio": "Agregar radio",
"ca_cert_explanation": "Utilice un archivo .pem que comience con \"----- BEGIN CERTIFICATE -----\" y termine con \"----- END CERTIFICATE -----\". El resultado se mostrará en el campo siguiente. También puede copiar y pegar el certificado que le gustaría usar directamente.",
"cannot_delete": "Esta configuración no se puede eliminar porque está siendo utilizada por al menos una entidad, lugar o dispositivo", "cannot_delete": "Esta configuración no se puede eliminar porque está siendo utilizada por al menos una entidad, lugar o dispositivo",
"choose_radio_band": "¿Qué banda de radio te gustaría crear?",
"choose_section": "Qué sección le gustaría que contenga este bloque?", "choose_section": "Qué sección le gustaría que contenga este bloque?",
"configuration_browser": "Navegador de configuración", "configuration_browser": "Navegador de configuración",
"configurations": "Configuraciones", "configurations": "Configuraciones",
@@ -182,17 +227,22 @@
"creation_success": "¡Configuración creada con éxito!", "creation_success": "¡Configuración creada con éxito!",
"currently_associated": "Configuración asociada actual: {{config}}", "currently_associated": "Configuración asociada actual: {{config}}",
"currently_selected_config": "Configuración seleccionada actualmente: {{config}}", "currently_selected_config": "Configuración seleccionada actualmente: {{config}}",
"default_configs": "Configuraciones predeterminadas",
"default_configurations": "Configuraciones predeterminadas",
"delete_config": "Eliminar Configuración", "delete_config": "Eliminar Configuración",
"details": "Detalles", "details": "Detalles",
"device_password": "Contraseña", "device_password": "Contraseña",
"device_type": "Tipo de dispositivo", "device_type": "Tipo de dispositivo",
"device_types": "Tipos de dispositivos", "device_types": "Tipos de dispositivos",
"devices_affected": "Dispositivos afectados por esta configuración:",
"edit_configuration": "Editar configuración", "edit_configuration": "Editar configuración",
"error_delete": "Error al intentar eliminar: {{error}}", "error_delete": "Error al intentar eliminar: {{error}}",
"error_delete_blacklist": "Error al eliminar de la lista negra: {{error}}",
"error_fetching_config": "Error al obtener la configuración", "error_fetching_config": "Error al obtener la configuración",
"error_trying_delete": "Error al intentar eliminar: {{error}}", "error_trying_delete": "Error al intentar eliminar: {{error}}",
"error_update": "Error: {{error}}", "error_update": "Error: {{error}}",
"explanation": "Explicación", "explanation": "Explicación",
"key_pem_explanation": "Seleccione el archivo .pem",
"last_configuration_change": "CAMBIO DE CONFIGURACIÓN", "last_configuration_change": "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",
@@ -213,6 +263,8 @@
"used_by": "Usado por", "used_by": "Usado por",
"used_by_details": "{{entities}} Entidades, {{venues}} lugares y {{devices}} dispositivos", "used_by_details": "{{entities}} Entidades, {{venues}} lugares y {{devices}} dispositivos",
"uuid": "ID de configuración", "uuid": "ID de configuración",
"view_affected_devices": "Ver dispositivos afectados",
"view_config": "Ver configuración",
"view_in_use": "Ver en uso", "view_in_use": "Ver en uso",
"view_json": "Ver JSON sin procesar" "view_json": "Ver JSON sin procesar"
}, },
@@ -226,6 +278,38 @@
"connect": { "connect": {
"error_trying_to_connect": "Error al intentar conectarse al dispositivo: {{error}}" "error_trying_to_connect": "Error al intentar conectarse al dispositivo: {{error}}"
}, },
"contact": {
"access_pin": "PIN de acceso",
"add_contact": "Agregar contacto",
"contact": "Contacto",
"create_contact": "Crear contacto",
"currently_selected_contact": "Contacto seleccionado actualmente: {{contact}}",
"delete": "¿Borrar contacto?",
"error_assign": "Error al intentar asignar el contacto: {{error}}",
"error_creation": "Error al intentar crear contacto: {{error}}",
"error_delete": "Error al intentar eliminar el contacto: {{error}}",
"error_fetching_list": "Error al obtener los contactos",
"error_fetching_single": "Error al obtener el contacto: {{error}}",
"error_unassign": "Error al intentar anular la asignación del contacto: {{error}}",
"first_name": "Nombre de pila",
"identifier": "Identificador",
"initials": "Iniciales",
"last_name": "Apellido",
"no_associated_contact": "Sin contacto asociado",
"primary_email": "Correo electrónico principal",
"salutation": "saludo",
"secondary_email": "Email secundario",
"successful_assign": "¡Contacto asignado correctamente!",
"successful_creation": "Contacto Creado!",
"successful_delete": "¡Contacto eliminado con éxito!",
"successful_unassign": "Contacto no asignado correctamente",
"successful_update": "Contacto actualizado con éxito",
"title": "Contactos",
"type": "Tipo",
"update_error": "Error al actualizar el contacto: {{error}}",
"user_title": "Título",
"visual": "Nombre de correspondencia"
},
"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"
@@ -237,8 +321,23 @@
"healthchecks_title": "Eliminar comprobaciones de estado" "healthchecks_title": "Eliminar comprobaciones de estado"
}, },
"device": { "device": {
"add_to_blacklist": "Agregar dispositivo a la lista negra",
"all_devices": "Todos los dispositivos",
"blacklisted_on": "Fecha",
"capabilities": "capacidades",
"certificate_explanation": "Certificados de dispositivos conectados",
"edit_blacklist": "Editar dispositivo incluido en la lista negra",
"error_adding_blacklist": "Error al agregar el dispositivo a la lista negra: {{error}}",
"error_edit_blacklist": "Error al editar la lista negra: {{error}}",
"error_fetching_device": "Error al obtener la información del dispositivo: {{error}}", "error_fetching_device": "Error al obtener la información del dispositivo: {{error}}",
"error_fetching_devices": "Error al recuperar dispositivos: {{error}}" "error_fetching_devices": "Error al recuperar dispositivos: {{error}}",
"health_explanation": "Estado de los dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos conectados)",
"memory_explanation": "Cantidad de dispositivos conectados con la memoria correspondiente utilizada%",
"remove_from_blacklist": "ELIMINAR DE LA LISTA NEGRA",
"success_added_blacklist": "¡Dispositivo agregado exitosamente a la lista negra!",
"success_edit_blacklist": "Lista negra editada con éxito!",
"success_removed_blacklist": "¡Dispositivo eliminado con éxito de la lista negra!",
"uptimes_explanation": "Cantidad de dispositivos conectados según su tiempo de actividad"
}, },
"device_logs": { "device_logs": {
"log": "Iniciar sesión", "log": "Iniciar sesión",
@@ -248,22 +347,46 @@
"entity": { "entity": {
"add_child": "Agregar entidad secundaria a {{entityName}}", "add_child": "Agregar entidad secundaria a {{entityName}}",
"add_failure": "Error, el servidor devolvió: {{error}}", "add_failure": "Error, el servidor devolvió: {{error}}",
"add_ips": "Administrar direcciones IP",
"add_root": "Agregar entidad raíz", "add_root": "Agregar entidad raíz",
"add_success": "¡Entidad creada con éxito!", "add_success": "¡Entidad creada con éxito!",
"assigned_inventory": "Inventario asignado", "assigned_inventory": "Inventario asignado",
"cannot_delete": "No puede eliminar entidades que tienen hijos. Elimina los hijos de esta entidad para poder eliminarla.", "cannot_delete": "No puede eliminar entidades que tienen hijos. Elimina los hijos de esta entidad para poder eliminarla.",
"confirm_map_delete": "¿Está seguro de que desea eliminar el mapa {{name}}? Esta acción no se puede revertir",
"currently_selected_entity": "Entidad seleccionada actualmente: {{config}}", "currently_selected_entity": "Entidad seleccionada actualmente: {{config}}",
"currently_selected_venue": "Lugar seleccionado actualmente: {{config}}", "currently_selected_venue": "Lugar seleccionado actualmente: {{config}}",
"delete_success": "Entidad eliminada correctamente", "delete_success": "Entidad eliminada correctamente",
"delete_warning": "Advertencia: esta operación no se puede revertir", "delete_warning": "Advertencia: esta operación no se puede revertir",
"duplicate_from_node": "Duplicar con un nodo raíz específico",
"duplicate_map": "Mapa duplicado",
"duplicate_with_node": "Duplicar {{mapName}} con {{rootName}} como nodo raíz",
"edit_failure": "Actualización fallida: {{error}}", "edit_failure": "Actualización fallida: {{error}}",
"enter_here": "Ingrese las IP que desea agregar aquí",
"entire_tree": "Mapa de red",
"entities": "entidades", "entities": "entidades",
"entity": "Entidad", "entity": "Entidad",
"error_deleting_map": "Error al eliminar el mapa: {{error}}",
"error_fetch_entity": "Error al obtener la información de la entidad", "error_fetch_entity": "Error al obtener la información de la entidad",
"error_fetching": "Error al recuperar entidades", "error_fetching": "Error al recuperar entidades",
"error_fetching_map": "Error al obtener el mapa: {{error}}",
"error_fetching_tree": "Error al obtener el árbol: {{error}}",
"error_saving": "Error al guardar la entidad", "error_saving": "Error al guardar la entidad",
"error_saving_map": "Error al guardar el mapa: {{error}}",
"higher_priority": "Dar mayor prioridad",
"ip_detection": "Detección de IP",
"ip_formats": "Puede agregar direcciones IPv4 o IPv6 en los siguientes formatos:",
"lower_priority": "Hacer una prioridad más baja",
"map": "Mapa",
"map_delete_success": "¡Mapa eliminado correctamente!",
"need_select_entity": "Debe seleccionar una entidad de la siguiente tabla",
"no_ips": "No se seleccionaron direcciones IP",
"not_assigned": "No asignado", "not_assigned": "No asignado",
"only_unassigned": "Solo sin asignar", "only_unassigned": "Solo sin asignar",
"select_entity": "Seleccione esta entidad",
"selected_entity": "Entidad seleccionada",
"selected_map": "Mapa seleccionado",
"tree_saved": "¡Mapa guardado con éxito!",
"update_failure_error": "Error al intentar actualizar la entidad: {{error}}",
"valid_serial": "Debe ser un número de serie válido (12 caracteres HEX)", "valid_serial": "Debe ser un número de serie válido (12 caracteres HEX)",
"venues": "Sedes" "venues": "Sedes"
}, },
@@ -275,6 +398,7 @@
"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": { "firmware": {
"age_explanation": "Número medio de días para todos los dispositivos de los que podemos obtener ese valor",
"average_age": "Edad promedio del firmware", "average_age": "Edad promedio del firmware",
"choose_custom": "Escoger", "choose_custom": "Escoger",
"details_title": "Detalles de la imagen n. °{{image}} ", "details_title": "Detalles de la imagen n. °{{image}} ",
@@ -287,9 +411,11 @@
"image": "Imagen", "image": "Imagen",
"image_date": "Fecha de la imagen", "image_date": "Fecha de la imagen",
"installed_firmware": "Firmware instalado", "installed_firmware": "Firmware instalado",
"latest_explanation": "Dispositivos que ejecutan firmware reconocido en su última versión",
"latest_version_installed": "Última versión instalada", "latest_version_installed": "Última versión instalada",
"newer_firmware_available": "Nuevas revisiones disponibles", "newer_firmware_available": "Nuevas revisiones disponibles",
"reinstall_latest": "Reinstalar", "reinstall_latest": "Reinstalar",
"release": "Lanzamiento",
"revision": "Revisión", "revision": "Revisión",
"show_dev": "Mostrar lanzamientos para desarrolladores", "show_dev": "Mostrar lanzamientos para desarrolladores",
"size": "Tamaño", "size": "Tamaño",
@@ -318,6 +444,7 @@
"add_tag": "Crear etiqueta", "add_tag": "Crear etiqueta",
"add_tag_to": "Agregar nuevo dispositivo a {{name}}", "add_tag_to": "Agregar nuevo dispositivo a {{name}}",
"add_venue": "Agregar lugar", "add_venue": "Agregar lugar",
"assign_ent_ven": "Asignar a entidad o lugar",
"assign_entity_instructions": "Puede encontrar la entidad a la que desea que se asigne esta etiqueta utilizando el menú a continuación, o puede pegar manualmente el UUID de la entidad en el campo de arriba.", "assign_entity_instructions": "Puede encontrar la entidad a la que desea que se asigne esta etiqueta utilizando el menú a continuación, o puede pegar manualmente el UUID de la entidad en el campo de arriba.",
"assign_error": "Error al intentar asignar la etiqueta", "assign_error": "Error al intentar asignar la etiqueta",
"assign_to_entity": "Asignar a entidad", "assign_to_entity": "Asignar a entidad",
@@ -349,6 +476,7 @@
"error_create_venue": "Error al crear el lugar", "error_create_venue": "Error al crear el lugar",
"error_delete_tag": "Error al eliminar la etiqueta de inventario", "error_delete_tag": "Error al eliminar la etiqueta de inventario",
"error_get_venue": "Error al recuperar lugares", "error_get_venue": "Error al recuperar lugares",
"error_pushing_config": "Error al intentar enviar la configuración al dispositivo: {{error}}",
"error_retrieving": "Se produjo un error al recuperar las etiquetas de inventario", "error_retrieving": "Se produjo un error al recuperar las etiquetas de inventario",
"error_unassign": "Error durante la operación de anulación de asignación", "error_unassign": "Error durante la operación de anulación de asignación",
"error_update_venue": "Error al actualizar el lugar", "error_update_venue": "Error al actualizar el lugar",
@@ -394,15 +522,46 @@
"unassigned_deleted_devices": "{{number}} Dispositivos eliminados y sin asignar", "unassigned_deleted_devices": "{{number}} Dispositivos eliminados y sin asignar",
"unassigned_tags": "Etiquetas sin asignar", "unassigned_tags": "Etiquetas sin asignar",
"validating_import_file": "Validando archivo y datos de importación ...", "validating_import_file": "Validando archivo y datos de importación ...",
"venue": "Lugar de encuentro" "venue": "Lugar de encuentro",
"view_in_gateway": "Ver en Gateway"
},
"location": {
"add": "Añade una ubicación",
"building_name": "Nombre del edificio",
"city": "ciudad",
"country": "País",
"create": "Crear ubicación",
"currently_selected": "Ubicación seleccionada actualmente: {{location}}",
"delete": "¿Eliminar ubicación?",
"error_assign": "Error al intentar asignar la ubicación: {{error}}",
"error_creation": "Error al intentar crear ubicaciones: {{error}}",
"error_delete": "Error al eliminar la ubicación: {{error}}",
"error_fetching_single": "Error al intentar obtener la ubicación: {{error}}",
"geocode": "Geocode",
"mobiles": "MOBILES",
"no_associated": "Sin ubicación asociada",
"phones": "Los telefonos",
"postal": "código postal",
"search": "Busque ubicaciones para completar automáticamente los campos a continuación",
"state": "Estado",
"street_address": "Dirección",
"successful_creation": "¡Ubicación creada con éxito!",
"successful_delete": "¡Ubicación eliminada con éxito!",
"successful_update": "¡Ubicación actualizada con éxito!",
"successfully_assigned": "¡Ubicación asignada correctamente!",
"title": "Ubicaciones",
"update_error": "Error al actualizar la ubicación: {{error}}"
}, },
"login": { "login": {
"account_verification": "Verificación de la cuenta",
"authentication_expired": "Autenticación caducada, vuelva a iniciar el proceso de inicio de sesión",
"change_password": "Cambia la contraseña", "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_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", "change_password_instructions": "Ingrese y confirme su nueva contraseña",
"changing_password": "Cambio de contraseña ...", "changing_password": "Cambio de contraseña ...",
"confirm_new_password": "confirmar nueva contraseña", "confirm_new_password": "confirmar nueva contraseña",
"different_passwords": "Debes ingresar la misma contraseña dos veces", "different_passwords": "Debes ingresar la misma contraseña dos veces",
"email_code_validation": "Por favor, marque su casilla de correo electrónico e ingrese el código de verificación que le acabamos de enviar en la casilla a continuación.",
"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_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_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", "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",
@@ -411,6 +570,7 @@
"login_error": "Error de inicio de sesión, asegúrese de que la información que proporciona sea válida", "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", "new_password": "Nueva contraseña",
"password": "Contraseña", "password": "Contraseña",
"phone_validation_explanation": "Verifique su dispositivo móvil e ingrese el código de verificación que le acabamos de enviar en el cuadro a continuación",
"please_enter_gateway": "Ingrese una URL de uCentralSec", "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",
@@ -419,7 +579,12 @@
"sending_ellipsis": "Enviando...", "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", "url": "URL de uCentralSec",
"username": "Nombre de usuario" "username": "Nombre de usuario",
"verification_code": "Ingrese su verificación aquí",
"wrong_code": "El código de verificación que se ingresó no es válido."
},
"preferences": {
"provisioning": "Aprovisionamiento"
}, },
"reboot": { "reboot": {
"directions": "¿Cuándo le gustaría reiniciar este dispositivo?", "directions": "¿Cuándo le gustaría reiniciar este dispositivo?",
@@ -431,7 +596,7 @@
"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.",
"re_scan": "Vuelva a escanear", "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.", "result_directions": "Puede hacer clic en el botón 'Escanear' en la parte superior derecha para $t(scan.re_scan)",
"results": "Resultados de escaneo Wi-Fi", "results": "Resultados de escaneo Wi-Fi",
"scan": "Escanear", "scan": "Escanear",
"scanning": "Exploración... ", "scanning": "Exploración... ",
@@ -440,13 +605,69 @@
"settings": { "settings": {
"title": "Ajustes" "title": "Ajustes"
}, },
"simulation": {
"add": "Agregar simulación",
"cancel": "Cancelar",
"cancel_success": "¡La ejecución de la simulación se canceló con éxito!",
"check_ongoing_sims": "Seguir Sim en curso",
"client_interval": "Intervalo de cliente",
"delete_simulation": "Eliminar Sim. {{name}}",
"end": "Término",
"error_creating": "Error al crear la simulación: {{error}}",
"error_delete": "Error al eliminar la simulación: {{error}}",
"error_devices": "Dispositivos de error",
"error_edit": "Error al intentar guardar la simulación: {{error}}",
"error_edit_run": "Error al intentar cambiar el estado de ejecución: {{error}}",
"error_fetching_simulations": "Error al obtener simulaciones: {{error}}",
"error_start_run": "Error al intentar iniciar una ejecución de simulación: {{error}}",
"gateway": "Puerta",
"healtcheck_interval": "Intervalo de comprobación de estado",
"keep_alive": "Mantener viva",
"last_refresh": "Última actualización",
"length": "Longitud",
"live_devices": "Dispositivos en vivo",
"mac_prefix": "Prefijo MAC",
"max_associations": "Max. Asociaciones",
"max_clients": "Max. Clientela",
"messages_transmitted": "Mensajes TX",
"min_associations": "Min. Asociaciones",
"min_clients": "Min. Clientela",
"pause": "pausa",
"pause_success": "¡Ejecutar en pausa!",
"prefix_length": "Obligatorio, debe tener una longitud de 6 caracteres",
"previous_runs": "Ejecuciones anteriores",
"received": "recibido",
"received_messages": "Msgs RX",
"reconnect_interval": "Intervalo de reconexión",
"resume": "Currículum",
"resume_success": "¡Ejecutar reanudado!",
"run": "Ejecución de simulación",
"run_simulation": "Ejecutar simulación",
"started": "Empezado",
"state_interval": "Intervalo de estado",
"stop": "Detener",
"stop_success": "¡Corre, detenido!",
"success_creating": "¡Simulación creada con éxito!",
"success_run_start": "¡Ejecución iniciada con éxito!",
"successful_delete": "¡Simulación eliminada con éxito!",
"successful_edit": "¡Simulación editada con éxito!",
"threads": "Trapos",
"time_full_devices": "Tiempo para dispositivos completos",
"title": "Simulaciones",
"transmitted": "Transmitido",
"valid_cert": "Certificado válido",
"valid_key": "Clave válida"
},
"statistics": { "statistics": {
"data": "Datos (KB)", "data": "Datos (KB)",
"data_mb": "Datos (MB)",
"latest_statistics": "Últimas estadísticas", "latest_statistics": "Últimas estadísticas",
"lifetime_stats": "Estadísticas de por vida", "lifetime_stats": "Estadísticas de por vida",
"memory": "Memoria",
"no_interfaces": "No hay estadísticas de vida útil de la interfaz disponibles", "no_interfaces": "No hay estadísticas de vida útil de la interfaz disponibles",
"show_latest": "Últimas estadísticas", "show_latest": "Últimas estadísticas",
"title": "estadística" "title": "estadística",
"used": "Memoria usada %"
}, },
"status": { "status": {
"connection_status": "Estado", "connection_status": "Estado",
@@ -458,9 +679,27 @@
"percentage_free": "{{percentage}}% de {{total}} gratis", "percentage_free": "{{percentage}}% de {{total}} gratis",
"percentage_used": "{{percentage}}% de {{total}} utilizado", "percentage_used": "{{percentage}}% de {{total}} utilizado",
"title": "#{{serialNumber}} Estado", "title": "#{{serialNumber}} Estado",
"total_memory": "Memoria total",
"uptime": "Tiempo de actividad", "uptime": "Tiempo de actividad",
"used_total_memory": "{{used}} usado / {{total}} total" "used_total_memory": "{{used}} usado / {{total}} total"
}, },
"subscriber": {
"add_device_subscriber_explanation": "Para reclamar otros dispositivos, puede usar nuestra barra de búsqueda o reclamar directamente desde la tabla. Si un dispositivo ya fue reclamado por un usuario, deberá ir a sus detalles y anular la asignación antes de reclamarlo.",
"create": "Crear suscriptor",
"devices_one": "{{count}} dispositivo",
"devices_other": "{{count}} dispositivos",
"edit": "Editar suscriptor",
"error_create": "Error al crear el suscriptor: {{error}}",
"error_delete": "Error al eliminar el suscriptor: {{error}}",
"error_fetching": "Error al obtener suscriptores: {{error}}",
"error_fetching_single": "Error al obtener el suscriptor: {{error}}",
"error_update": "Error al actualizar el suscriptor: {{error}}",
"is_already_claimed": "ya es reclamado por",
"subscribers": "Suscriptores",
"success_create": "¡Suscriptor creado correctamente!",
"success_delete": "¡Suscriptor eliminado correctamente!",
"success_update": "Suscriptor actualizado con éxito!"
},
"system": { "system": {
"error_fetching": "Error al obtener información del sistema", "error_fetching": "Error al obtener información del sistema",
"error_reloading": "Error al recargar: {{error}}", "error_reloading": "Error al recargar: {{error}}",
@@ -507,10 +746,13 @@
"waiting_for_device": "Esperando que el dispositivo se vuelva a conectar" "waiting_for_device": "Esperando que el dispositivo se vuelva a conectar"
}, },
"user": { "user": {
"add_phone_number": "Agregar el número de teléfono",
"avatar": "Tu avatar", "avatar": "Tu avatar",
"avatar_file": "Tu avatar (máx. De 2 MB)", "avatar_file": "Tu avatar (máx. De 2 MB)",
"check_phone": "Por favor revise su teléfono para su código de validación",
"confirm_new_password": "confirmar nueva contraseña",
"create": "Crear usuario", "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_failure": "Error al crear el usuario: {{error}}",
"create_success": "Usuario creado con éxito", "create_success": "Usuario creado con éxito",
"creating": "Creando usuario ...", "creating": "Creando usuario ...",
"delete_avatar": "Eliminar avatar", "delete_avatar": "Eliminar avatar",
@@ -522,36 +764,51 @@
"description": "Descripción", "description": "Descripción",
"edit": "editar usuario", "edit": "editar usuario",
"email_address": "Dirección de correo electrónico", "email_address": "Dirección de correo electrónico",
"enter_new_phone": "Ingrese su nuevo número de teléfono:",
"error_fetching_users": "Error al obtener usuarios: {{error}}", "error_fetching_users": "Error al obtener usuarios: {{error}}",
"error_retrieving": "Error al recuperar usuario",
"error_sending_code": "Error al intentar enviar el código de validación. Confirma que tu número de teléfono es válido.",
"force_password_change": "Forzar cambio de contraseña al iniciar sesión", "force_password_change": "Forzar cambio de contraseña al iniciar sesión",
"id": "Id. De usuario", "id": "Id. De usuario",
"last_login": "Último acceso", "last_login": "Último acceso",
"login_id": "Ingresar identificación.", "login_id": "Ingresar identificación.",
"make_sure_same_password": "Asegúrese de que ambas contraseñas sean iguales y válidas",
"my_profile": "Mi perfil", "my_profile": "Mi perfil",
"name": "Nombre", "name": "Nombre",
"new_code_sent": "¡Nuevo código enviado!",
"nickname": "Apodo", "nickname": "Apodo",
"nickname_explanation": "Apodo (opcional)", "nickname_explanation": "Apodo (opcional)",
"not_validated": "No validado", "not_validated": "No validado",
"note": "Nota", "note": "Nota",
"password": "Contraseña", "password": "Contraseña",
"phone_number": "Número de teléfono",
"provide_email": "Por favor ingrese su dirección de correo electrónico válida", "provide_email": "Por favor ingrese su dirección de correo electrónico válida",
"provide_password": "Proporcione una contraseña válida", "provide_password": "Proporcione una contraseña válida",
"save_avatar": "Guardar avatar", "save_avatar": "Guardar avatar",
"send_code": "Enviar código",
"send_code_again": "Enviar Código De nuevo",
"show_hide_password": "Mostrar / Ocultar contraseña", "show_hide_password": "Mostrar / Ocultar contraseña",
"successful_validation": "¡Número de teléfono validado! Haga clic en el botón guardar para vincularlo a su perfil",
"table_title": "Usuarios administrativos",
"update_failure": "Error al intentar actualizar: {{error}}", "update_failure": "Error al intentar actualizar: {{error}}",
"update_failure_title": "Actualización fallida", "update_failure_title": "Actualización fallida",
"update_success": "Usuario actualizado con éxito", "update_success": "Usuario actualizado con éxito",
"update_success_title": "Éxito", "update_success_title": "Éxito",
"user_role": "papel", "user_role": "papel",
"users": "Usuarios", "users": "Usuarios",
"validated": "Validado" "validate_phone": "validar",
"validated": "Validado",
"wrong_validation_code": "No ha introducido un código válido. Vuelve a intentarlo y asegúrate de haber ingresado el número de teléfono correcto."
}, },
"wifi_analysis": { "wifi_analysis": {
"association": "Asociación", "association": "Asociación",
"associations": "Asociaciones", "associations": "Asociaciones",
"mode": "Modo", "mode": "Modo",
"network_diagram": "Diagrama de Red", "network_diagram": "Diagrama de Red",
"override_dfs": "Anular DFS",
"radios": "Radios", "radios": "Radios",
"title": "Análisis de Wi-Fi" "scan_warning": "Su radio 5G está en un canal de radar, debe habilitar \"Anular DFS\" para permitir el escaneo de todos los canales 5G",
"title": "Análisis de Wi-Fi",
"vendor": "Vendedor"
} }
} }

View File

@@ -17,11 +17,13 @@
"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 ?", "execute_now": "Souhaitez-vous définir ce modèle maintenant ?",
"explanation": "Quel modèle souhaitez-vous définir sur cet appareil pendant 30 secondes ?",
"pattern": "Choisissez le modèle que vous souhaitez utiliser :", "pattern": "Choisissez le modèle que vous souhaitez utiliser :",
"set_leds": "Définir les LED", "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": {
"command_success": "Commande soumise avec succès",
"error": "Erreur lors de la soumission de la commande !", "error": "Erreur lors de la soumission de la commande !",
"error_delete_log": "Erreur lors de la tentative de suppression : {{error}}", "error_delete_log": "Erreur lors de la tentative de suppression : {{error}}",
"event_queue": "File d'attente d'événements", "event_queue": "File d'attente d'événements",
@@ -32,41 +34,53 @@
"common": { "common": {
"access_policy": "Politique d'accès", "access_policy": "Politique d'accès",
"add": "Ajouter", "add": "Ajouter",
"add_items": "Ajouter des articles",
"add_note": "Ajouter une note",
"add_note_explanation": "Écrivez votre nouvelle note ci-dessous et cliquez sur le bouton '+' où vous avez terminé",
"adding_ellipsis": "Ajouter...", "adding_ellipsis": "Ajouter...",
"all": "Tout",
"are_you_sure": "Êtes-vous sûr?", "are_you_sure": "Êtes-vous sûr?",
"back_to_login": "Retour connexion", "back_to_login": "Retour connexion",
"back_to_start": "Retour au début", "back_to_start": "Retour au début",
"blacklist": "Liste noire",
"by": "Par", "by": "Par",
"cancel": "annuler", "cancel": "annuler",
"certificate": "Certificat", "certificate": "Certificat",
"certificates": "Certificats", "certificates": "Certificats",
"claim": "Prétendre",
"clear": "Clair", "clear": "Clair",
"close": "Fermer", "close": "Fermer",
"code": "Code",
"command": "Commander", "command": "Commander",
"commands": "Les commandes", "commands": "Les commandes",
"commands_executed": "commandes exécutées", "commands_executed": "commandes exécutées",
"compatible": "Compatible", "compatible": "Compatible",
"completed": "Terminé", "completed": "Terminé",
"concurrent_devices": "Périphériques simultanés",
"config_id": "Config. Identifiant", "config_id": "Config. Identifiant",
"confirm": "Confirmer", "confirm": "Confirmer",
"confirm_stop_editing": "Voulez-vous vraiment arrêter la modification ? Cela annulera toutes les modifications non enregistrées que vous avez apportées.",
"connected": "Connecté", "connected": "Connecté",
"copied": "Copié!", "copied": "Copié!",
"copied_to_clipboard": "Copié dans le presse-papier!",
"copy_to_clipboard": "Copier dans le presse-papier", "copy_to_clipboard": "Copier dans le presse-papier",
"create": "Créer", "create": "Créer",
"created": "Créé", "created": "Créé",
"created_by": "Créé par", "created_by": "Créé par",
"creator": "Créateur",
"current": "Actuel", "current": "Actuel",
"custom_date": "Date personnalisée", "custom_date": "Date personnalisée",
"dashboard": "Tableau de bord", "dashboard": "Tableau de bord",
"date": "Rendez-vous amoureux", "date": "Rendez-vous amoureux",
"day": "journée", "day": "journée",
"days": "journées", "days": "journées",
"default_map": "Carte par défaut",
"delete": "Effacer", "delete": "Effacer",
"delete_device": "Supprimer le périphérique", "delete_device": "Supprimer le périphérique",
"details": "Détails", "details": "Détails",
"device": "N° d'appareil{{serialNumber}}", "device": "N° d'appareil{{serialNumber}}",
"device_dashboard": "Tableau de bord de l'appareil", "device_dashboard": "Tableau de bord de l'appareil",
"device_delete": "Supprimer l'appareil n°{{serialNumber}}", "device_delete": "Supprimer #{{serialNumber}}",
"device_deleted": "Appareil supprimé avec succès", "device_deleted": "Appareil supprimé avec succès",
"device_health": "Santé de l'appareil", "device_health": "Santé de l'appareil",
"device_list": "Liste des appareils", "device_list": "Liste des appareils",
@@ -78,6 +92,7 @@
"dismiss": "Rejeter", "dismiss": "Rejeter",
"do_now": "Faire maintenant!", "do_now": "Faire maintenant!",
"download": "Télécharger", "download": "Télécharger",
"duplicate": "Dupliquer",
"duration": "Durée", "duration": "Durée",
"edit": "modifier", "edit": "modifier",
"edit_user": "Modifier", "edit_user": "Modifier",
@@ -86,6 +101,8 @@
"endpoints": "Points de terminaison", "endpoints": "Points de terminaison",
"error": "Erreur", "error": "Erreur",
"error_adding_note": "Erreur lors de l'ajout de la note", "error_adding_note": "Erreur lors de l'ajout de la note",
"error_code": "Code d'erreur",
"errors": "les erreurs",
"execute_now": "Souhaitez-vous exécuter cette commande maintenant ?", "execute_now": "Souhaitez-vous exécuter cette commande maintenant ?",
"executed": "réalisé", "executed": "réalisé",
"exit": "Sortie", "exit": "Sortie",
@@ -96,12 +113,22 @@
"forgot_password_title": "Mot de passe oublié", "forgot_password_title": "Mot de passe oublié",
"from": "De", "from": "De",
"general_error": "Erreur API, veuillez consulter votre administrateur", "general_error": "Erreur API, veuillez consulter votre administrateur",
"go_back": "Retourner",
"hide": "Cacher", "hide": "Cacher",
"hour": "heure", "hour": "heure",
"hours": "heures", "hours": "heures",
"id": "Id", "id": "Id",
"invalid_credentials": "Nom d'utilisateur et / ou mot de passe incorrect",
"invalid_date_explanation": "Date invalide, merci d'utiliser le calendrier accessible avec le bouton à droite",
"invalid_file": "Le fichier choisi n'était pas valide, veuillez lire les instructions et ajuster votre fichier en conséquence",
"invalid_password": "Ce mot de passe ne confirme pas les règles de base des mots de passe. Veuillez visiter notre page Politique de mot de passe pour en savoir plus",
"invalid_pem": "Votre fichier .pem n'est pas valide. Il doit commencer par '-----BEGIN CERTIFICATE-----' OU '-----BEGIN PRIVATE KEY-----' et il doit se terminer par '-----END CERTIFICATE--- --' OU '-----FIN CLÉ PRIVÉE-----'",
"ip_address": "Adresse IP", "ip_address": "Adresse IP",
"ips": "IPS",
"item": "article",
"items": "Articles",
"items_per_page": "Objets par page:", "items_per_page": "Objets par page:",
"key": "Clé",
"last_dashboard_refresh": "Dernière actualisation du tableau de bord", "last_dashboard_refresh": "Dernière actualisation du tableau de bord",
"later_tonight": "Plus tard ce soir", "later_tonight": "Plus tard ce soir",
"latest": "Dernier", "latest": "Dernier",
@@ -110,28 +137,35 @@
"loading_more_ellipsis": "Chargement plus ...", "loading_more_ellipsis": "Chargement plus ...",
"logout": "Connectez - Out", "logout": "Connectez - Out",
"mac": "ADRESSE MAC", "mac": "ADRESSE MAC",
"main": "Principale",
"manufacturer": "fabricant", "manufacturer": "fabricant",
"memory_used": "Mémoire utilisée", "memory_used": "Mémoire utilisée",
"min_max": "Min : {{min}}, Max : {{max}}",
"minute": "minute", "minute": "minute",
"minutes": "minutes", "minutes": "minutes",
"modified": "Modifié", "modified": "Modifié",
"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": "Non",
"no_addresses_found": "Aucune adresse trouvée",
"no_devices_found": "Aucun périphérique trouvé", "no_devices_found": "Aucun périphérique trouvé",
"no_items": "Pas d'objet", "no_items": "Pas d'objet",
"none": "Aucun", "none": "Aucun",
"not_connected": "Pas connecté", "not_connected": "Pas connecté",
"of_connected": "% d'appareils", "of_connected": "% d'appareils connectés",
"off": "De", "off": "De",
"on": "sur", "on": "sur",
"optional": "Optionnel", "optional": "Optionnel",
"overall_health": "Santé globale", "overall_health": "Santé globale",
"password_policy": "Politique de mot de passe", "password_policy": "Politique de mot de passe",
"preferences": "Préférences",
"preview": "Aperçu", "preview": "Aperçu",
"program": "Programme",
"reason": "raison",
"recorded": "Enregistré", "recorded": "Enregistré",
"refresh": "Rafraîchir", "refresh": "Rafraîchir",
"refresh_device": "Actualiser l'appareil", "refresh_device": "Actualiser l'appareil",
"remove_claim": "Supprimer la réclamation",
"required": "Champs obligatoires", "required": "Champs obligatoires",
"result": "Résultat", "result": "Résultat",
"save": "Sauvegarder", "save": "Sauvegarder",
@@ -142,16 +176,21 @@
"second": "seconde", "second": "seconde",
"seconds": "secondes", "seconds": "secondes",
"seconds_elapsed": "Secondes écoulées", "seconds_elapsed": "Secondes écoulées",
"see_details": "Voir les détails",
"select": "sélectionner",
"serial_num": "Numéro de série",
"serial_number": "Numéro de série", "serial_number": "Numéro de série",
"show_all": "Montre tout", "show_all": "Montre tout",
"socket_connection_closed": "Connexion fermée !", "socket_connection_closed": "Connexion fermée !",
"start": "Début", "start": "Début",
"status": "Statut",
"stop_editing": "Arrêter la modification", "stop_editing": "Arrêter la modification",
"submit": "Soumettre", "submit": "Soumettre",
"submitted": "Soumis", "submitted": "Soumis",
"success": "Succès", "success": "Succès",
"system": "Système", "system": "Système",
"table": "Table", "table": "Table",
"time_per_device": "Appareils/Seconde",
"timestamp": "Temps", "timestamp": "Temps",
"to": "à", "to": "à",
"type": "Type", "type": "Type",
@@ -162,16 +201,22 @@
"unknown": "Inconnu", "unknown": "Inconnu",
"up_to_date": "Appareils à jour", "up_to_date": "Appareils à jour",
"uptimes": "Disponibilités", "uptimes": "Disponibilités",
"use_file": "Utiliser le fichier",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendeurs", "vendors": "Vendeurs",
"view_more": "Afficher plus", "view_more": "Afficher plus",
"visibility": "Visibilité",
"waiting_for_update": "En attente de mise à jour",
"yes": "Oui" "yes": "Oui"
}, },
"configuration": { "configuration": {
"add_configuration": "Ajouter une configuration", "add_configuration": "Ajouter une configuration",
"add_new_block": "Ajouter un nouveau bloc de configuration", "add_new_block": "Ajouter un nouveau bloc de configuration",
"add_or_link": "Lier ou ajouter", "add_or_link": "Lier ou ajouter",
"add_radio": "Ajouter une radio",
"ca_cert_explanation": "Veuillez utiliser un fichier .pem qui commence par \"-----BEGIN CERTIFICATE-----\" et se termine par \"-----END CERTIFICATE-----\". Le résultat sera affiché dans le champ ci-dessous. Vous pouvez également copier et coller le certificat que vous souhaitez utiliser directement.",
"cannot_delete": "Cette configuration ne peut pas être supprimée car elle est utilisée par au moins une entité, un lieu ou un appareil", "cannot_delete": "Cette configuration ne peut pas être supprimée car elle est utilisée par au moins une entité, un lieu ou un appareil",
"choose_radio_band": "Quelle bande de radio voudriez-vous créer ?",
"choose_section": "Quelle section souhaitez-vous que ce bloc contienne?", "choose_section": "Quelle section souhaitez-vous que ce bloc contienne?",
"configuration_browser": "Navigateur de configuration", "configuration_browser": "Navigateur de configuration",
"configurations": "Les configurations", "configurations": "Les configurations",
@@ -182,17 +227,22 @@
"creation_success": "Configuration créée avec succès !", "creation_success": "Configuration créée avec succès !",
"currently_associated": "Configuration associée actuelle : {{config}}", "currently_associated": "Configuration associée actuelle : {{config}}",
"currently_selected_config": "Configuration actuellement sélectionnée : {{config}}", "currently_selected_config": "Configuration actuellement sélectionnée : {{config}}",
"default_configs": "Configurations par défaut",
"default_configurations": "Configurations par défaut",
"delete_config": "Supprimer la configuration", "delete_config": "Supprimer la configuration",
"details": "Détails", "details": "Détails",
"device_password": "Mot de passe", "device_password": "Mot de passe",
"device_type": "Type d'appareil", "device_type": "Type d'appareil",
"device_types": "Types d'appareils", "device_types": "Types d'appareils",
"devices_affected": "Appareils concernés par cette configuration :",
"edit_configuration": "Modifier la configuration", "edit_configuration": "Modifier la configuration",
"error_delete": "Erreur lors de la tentative de suppression : {{error}}", "error_delete": "Erreur lors de la tentative de suppression : {{error}}",
"error_delete_blacklist": "Erreur lors de la suppression de la liste noire : {{error}}",
"error_fetching_config": "Erreur lors de la récupération de la configuration", "error_fetching_config": "Erreur lors de la récupération de la configuration",
"error_trying_delete": "Erreur lors de la tentative de suppression : {{error}}", "error_trying_delete": "Erreur lors de la tentative de suppression : {{error}}",
"error_update": "Erreur: {{error}}", "error_update": "Erreur: {{error}}",
"explanation": "Explication", "explanation": "Explication",
"key_pem_explanation": "Veuillez sélectionner le fichier .pem",
"last_configuration_change": "Changement de configuration", "last_configuration_change": "Changement 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",
@@ -213,6 +263,8 @@
"used_by": "Utilisé par", "used_by": "Utilisé par",
"used_by_details": "{{entities}} Entités, {{venues}} Lieux et {{devices}} Appareils", "used_by_details": "{{entities}} Entités, {{venues}} Lieux et {{devices}} Appareils",
"uuid": "Identifiant de configuration", "uuid": "Identifiant de configuration",
"view_affected_devices": "Afficher les appareils concernés",
"view_config": "Afficher la configuration",
"view_in_use": "Afficher en cours d'utilisation", "view_in_use": "Afficher en cours d'utilisation",
"view_json": "Afficher le JSON brut" "view_json": "Afficher le JSON brut"
}, },
@@ -226,6 +278,38 @@
"connect": { "connect": {
"error_trying_to_connect": "Erreur lors de la tentative de connexion à l'appareil : {{error}}" "error_trying_to_connect": "Erreur lors de la tentative de connexion à l'appareil : {{error}}"
}, },
"contact": {
"access_pin": "NIP d'accès",
"add_contact": "Ajouter le contact",
"contact": "Contact",
"create_contact": "Créer un contact",
"currently_selected_contact": "Contact actuellement sélectionné : {{contact}}",
"delete": "Effacer le contact?",
"error_assign": "Erreur lors de la tentative d'attribution du contact : {{error}}",
"error_creation": "Erreur lors de la tentative de création du contact : {{error}}",
"error_delete": "Erreur lors de la tentative de suppression du contact : {{error}}",
"error_fetching_list": "Erreur lors de la récupération des contacts",
"error_fetching_single": "Erreur lors de la récupération du contact : {{error}}",
"error_unassign": "Erreur lors de la tentative de désattribution du contact : {{error}}",
"first_name": "Prénom",
"identifier": "Identifiant",
"initials": "Initiales",
"last_name": "Nom de famille",
"no_associated_contact": "Aucun contact associé",
"primary_email": "Email primaire",
"salutation": "salutation",
"secondary_email": "Email secondaire",
"successful_assign": "Contact attribué avec succès !",
"successful_creation": "Contact créé!",
"successful_delete": "Contact supprimé avec succès !",
"successful_unassign": "Contact non attribué avec succès",
"successful_update": "Contact mis à jour avec succès",
"title": "Contacts",
"type": "Type",
"update_error": "Erreur lors de la mise à jour du contact : {{error}}",
"user_title": "Titre",
"visual": "Nom de la correspondance"
},
"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"
@@ -237,8 +321,23 @@
"healthchecks_title": "Supprimer les vérifications d'état" "healthchecks_title": "Supprimer les vérifications d'état"
}, },
"device": { "device": {
"add_to_blacklist": "Ajouter un appareil à la liste noire",
"all_devices": "Tous les dispositifs",
"blacklisted_on": "Rendez-vous amoureux",
"capabilities": "Capacités",
"certificate_explanation": "Certificats des appareils connectés",
"edit_blacklist": "Modifier l'appareil sur liste noire",
"error_adding_blacklist": "Erreur lors de l'ajout de l'appareil à la liste noire : {{error}}",
"error_edit_blacklist": "Erreur lors de la modification de la liste noire : {{error}}",
"error_fetching_device": "Erreur lors de la récupération des informations sur l'appareil : {{error}}", "error_fetching_device": "Erreur lors de la récupération des informations sur l'appareil : {{error}}",
"error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}" "error_fetching_devices": "Erreur lors de la récupération des appareils : {{error}}",
"health_explanation": "Santé des appareils connectés ((Appareils = 100 % * 100 + Appareils> 90 % * 95 + Appareils> 60 % * 75 + Appareils < 60 % * 35) / Appareils connectés)",
"memory_explanation": "Nombre d'appareils connectés avec la mémoire correspondante utilisée %",
"remove_from_blacklist": "Supprimer de la liste noire",
"success_added_blacklist": "Appareil ajouté avec succès à la liste noire !",
"success_edit_blacklist": "Liste noire modifiée avec succès !",
"success_removed_blacklist": "Appareil supprimé de la liste noire !",
"uptimes_explanation": "Nombre d'appareils connectés en fonction de leur disponibilité"
}, },
"device_logs": { "device_logs": {
"log": "Bûche", "log": "Bûche",
@@ -248,22 +347,46 @@
"entity": { "entity": {
"add_child": "Ajouter une entité enfant à {{entityName}}", "add_child": "Ajouter une entité enfant à {{entityName}}",
"add_failure": "Erreur, le serveur a renvoyé : {{error}}", "add_failure": "Erreur, le serveur a renvoyé : {{error}}",
"add_ips": "Gérer les IP",
"add_root": "Ajouter une entité racine", "add_root": "Ajouter une entité racine",
"add_success": "Entité créée avec succès !", "add_success": "Entité créée avec succès !",
"assigned_inventory": "Inventaire assigné", "assigned_inventory": "Inventaire assigné",
"cannot_delete": "Vous ne pouvez pas supprimer des entités qui ont des enfants. Supprimez les enfants de cette entité pour pouvoir la supprimer.", "cannot_delete": "Vous ne pouvez pas supprimer des entités qui ont des enfants. Supprimez les enfants de cette entité pour pouvoir la supprimer.",
"confirm_map_delete": "Êtes-vous sûr de vouloir supprimer la carte {{name}}? Cette action ne peut pas être annulée",
"currently_selected_entity": "Entité actuellement sélectionnée : {{config}}", "currently_selected_entity": "Entité actuellement sélectionnée : {{config}}",
"currently_selected_venue": "Lieu actuellement sélectionné : {{config}}", "currently_selected_venue": "Lieu actuellement sélectionné : {{config}}",
"delete_success": "Entité supprimée avec succès", "delete_success": "Entité supprimée avec succès",
"delete_warning": "Attention : cette opération ne peut pas être annulée", "delete_warning": "Attention : cette opération ne peut pas être annulée",
"duplicate_from_node": "Dupliquer avec un nœud racine spécifique",
"duplicate_map": "Carte en double",
"duplicate_with_node": "Dupliquer {{mapName}} avec {{rootName}} comme nœud racine",
"edit_failure": "Échec de la mise à jour : {{error}}", "edit_failure": "Échec de la mise à jour : {{error}}",
"enter_here": "Entrez les IP que vous souhaitez ajouter ici",
"entire_tree": "Carte du réseau",
"entities": "Entités", "entities": "Entités",
"entity": "Entité", "entity": "Entité",
"error_deleting_map": "Erreur lors de la suppression de la carte : {{error}}",
"error_fetch_entity": "Erreur lors de la récupération des informations sur l'entité", "error_fetch_entity": "Erreur lors de la récupération des informations sur l'entité",
"error_fetching": "Erreur lors de la récupération des entités", "error_fetching": "Erreur lors de la récupération des entités",
"error_fetching_map": "Erreur lors de la récupération de la carte : {{error}}",
"error_fetching_tree": "Erreur lors de la récupération de l'arborescence : {{error}}",
"error_saving": "Erreur lors de l'enregistrement de l'entité", "error_saving": "Erreur lors de l'enregistrement de l'entité",
"error_saving_map": "Erreur lors de l'enregistrement de la carte : {{error}}",
"higher_priority": "Faire une priorité plus élevée",
"ip_detection": "Détection IP",
"ip_formats": "Vous pouvez ajouter des adresses IPv4 ou IPv6 aux formats suivants :",
"lower_priority": "Faire une priorité inférieure",
"map": "Carte",
"map_delete_success": "Carte supprimée avec succès !",
"need_select_entity": "Vous devez sélectionner une entité dans le tableau ci-dessous",
"no_ips": "Aucune adresse IP sélectionnée",
"not_assigned": "Non attribué", "not_assigned": "Non attribué",
"only_unassigned": "Uniquement non attribué", "only_unassigned": "Uniquement non attribué",
"select_entity": "Sélectionnez cette entité",
"selected_entity": "Entité sélectionnée",
"selected_map": "Carte sélectionnée",
"tree_saved": "Carte enregistrée avec succès !",
"update_failure_error": "Erreur lors de la tentative de mise à jour de l'entité : {{error}}",
"valid_serial": "Doit être un numéro de série valide (12 caractères HEX)", "valid_serial": "Doit être un numéro de série valide (12 caractères HEX)",
"venues": "Les lieux" "venues": "Les lieux"
}, },
@@ -275,6 +398,7 @@
"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": { "firmware": {
"age_explanation": "Nombre moyen de jours pour tous les appareils à partir desquels nous pouvons obtenir cette valeur",
"average_age": "Âge moyen du micrologiciel", "average_age": "Âge moyen du micrologiciel",
"choose_custom": "Choisir", "choose_custom": "Choisir",
"details_title": "Image #{{image}} Détails", "details_title": "Image #{{image}} Détails",
@@ -287,9 +411,11 @@
"image": "Image", "image": "Image",
"image_date": "Date de l'image", "image_date": "Date de l'image",
"installed_firmware": "Micrologiciel installé", "installed_firmware": "Micrologiciel installé",
"latest_explanation": "Appareils exécutant un firmware reconnu dans sa dernière version",
"latest_version_installed": "Dernière version installée", "latest_version_installed": "Dernière version installée",
"newer_firmware_available": "Révisions plus récentes disponibles", "newer_firmware_available": "Révisions plus récentes disponibles",
"reinstall_latest": "Réinstaller", "reinstall_latest": "Réinstaller",
"release": "libération",
"revision": "Révision", "revision": "Révision",
"show_dev": "Afficher les versions des développeurs", "show_dev": "Afficher les versions des développeurs",
"size": "Taille", "size": "Taille",
@@ -318,6 +444,7 @@
"add_tag": "Créer un tag", "add_tag": "Créer un tag",
"add_tag_to": "Ajouter un nouvel appareil à {{name}}", "add_tag_to": "Ajouter un nouvel appareil à {{name}}",
"add_venue": "Ajouter un lieu", "add_venue": "Ajouter un lieu",
"assign_ent_ven": "Attribuer à une entité ou à un lieu",
"assign_entity_instructions": "Vous pouvez soit trouver l'entité à laquelle vous souhaitez que cette balise soit attribuée en utilisant le menu ci-dessous, soit coller manuellement l'UUID de l'entité dans le champ ci-dessus.", "assign_entity_instructions": "Vous pouvez soit trouver l'entité à laquelle vous souhaitez que cette balise soit attribuée en utilisant le menu ci-dessous, soit coller manuellement l'UUID de l'entité dans le champ ci-dessus.",
"assign_error": "Erreur lors de la tentative d'attribution de balise", "assign_error": "Erreur lors de la tentative d'attribution de balise",
"assign_to_entity": "Affecter à l'entité", "assign_to_entity": "Affecter à l'entité",
@@ -349,6 +476,7 @@
"error_create_venue": "Erreur lors de la création du lieu", "error_create_venue": "Erreur lors de la création du lieu",
"error_delete_tag": "Erreur lors de la suppression de la balise d'inventaire", "error_delete_tag": "Erreur lors de la suppression de la balise d'inventaire",
"error_get_venue": "Erreur lors de la récupération des lieux", "error_get_venue": "Erreur lors de la récupération des lieux",
"error_pushing_config": "Erreur lors de la tentative d'envoi de la configuration sur l'appareil : {{error}}",
"error_retrieving": "Une erreur s'est produite lors de la récupération des balises d'inventaire", "error_retrieving": "Une erreur s'est produite lors de la récupération des balises d'inventaire",
"error_unassign": "Erreur lors de l'opération de désaffectation", "error_unassign": "Erreur lors de l'opération de désaffectation",
"error_update_venue": "Erreur lors de la mise à jour du lieu", "error_update_venue": "Erreur lors de la mise à jour du lieu",
@@ -394,15 +522,46 @@
"unassigned_deleted_devices": "{{number}} appareils supprimés et non attribués", "unassigned_deleted_devices": "{{number}} appareils supprimés et non attribués",
"unassigned_tags": "Balises non attribuées", "unassigned_tags": "Balises non attribuées",
"validating_import_file": "Validation du fichier d'importation et des données...", "validating_import_file": "Validation du fichier d'importation et des données...",
"venue": "Lieu" "venue": "Lieu",
"view_in_gateway": "Afficher dans la passerelle"
},
"location": {
"add": "Ajouter un emplacement",
"building_name": "Nom du bâtiment",
"city": "Ville",
"country": "Pays",
"create": "Créer un lieu",
"currently_selected": "Emplacement actuellement sélectionné : {{location}}",
"delete": "Supprimer le lieu?",
"error_assign": "Erreur lors de la tentative d'attribution de l'emplacement : {{error}}",
"error_creation": "Erreur lors de la tentative de création d'établissements : {{error}}",
"error_delete": "Erreur lors de la suppression de l'emplacement : {{error}}",
"error_fetching_single": "Erreur lors de la tentative de récupération de l'emplacement : {{error}}",
"geocode": "Géocode",
"mobiles": "MOBILES",
"no_associated": "Aucun emplacement associé",
"phones": "Téléphones",
"postal": "Zip / code postal",
"search": "Recherchez des emplacements pour remplir automatiquement les champs ci-dessous",
"state": "Etat",
"street_address": "Adresse de rue",
"successful_creation": "Emplacement créé avec succès !",
"successful_delete": "Emplacement supprimé avec succès !",
"successful_update": "Emplacement mis à jour avec succès !",
"successfully_assigned": "Emplacement attribué avec succès !",
"title": "Emplacements",
"update_error": "Erreur lors de la mise à jour de l'emplacement : {{error}}"
}, },
"login": { "login": {
"account_verification": "Vérification de compte",
"authentication_expired": "Authentification expirée, veuillez recommencer le processus de connexion",
"change_password": "Changer le mot de passe", "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_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", "change_password_instructions": "Saisissez et confirmez votre nouveau mot de passe",
"changing_password": "Modification du mot de passe...", "changing_password": "Modification du mot de passe...",
"confirm_new_password": "Confirmer le nouveau 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", "different_passwords": "Vous devez saisir deux fois le même mot de passe",
"email_code_validation": "Veuillez vérifier votre boîte e-mail et entrer le code de vérification que nous venons de vous envoyer dans la case ci-dessous",
"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_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_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", "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",
@@ -411,6 +570,7 @@
"login_error": "Erreur de connexion, assurez-vous que les informations que vous fournissez sont valides", "login_error": "Erreur de connexion, assurez-vous que les informations que vous fournissez sont valides",
"new_password": "Nouveau mot de passe", "new_password": "Nouveau mot de passe",
"password": "Mot de passe", "password": "Mot de passe",
"phone_validation_explanation": "Veuillez vérifier votre appareil mobile et entrer le code de vérification que nous venons de vous envoyer dans la case ci-dessous",
"please_enter_gateway": "Veuillez saisir une URL uCentralSec", "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",
@@ -419,7 +579,12 @@
"sending_ellipsis": "Envoi...", "sending_ellipsis": "Envoi...",
"sign_in_to_account": "Connectez-vous à votre compte", "sign_in_to_account": "Connectez-vous à votre compte",
"url": "URL uCentralSec", "url": "URL uCentralSec",
"username": "Nom d'utilisateur" "username": "Nom d'utilisateur",
"verification_code": "Entrez votre vérification ici",
"wrong_code": "Le code de vérification saisi n'est pas valide."
},
"preferences": {
"provisioning": "Provisioning"
}, },
"reboot": { "reboot": {
"directions": "Quand souhaitez-vous redémarrer cet appareil ?", "directions": "Quand souhaitez-vous redémarrer cet appareil ?",
@@ -431,7 +596,7 @@
"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.",
"re_scan": "Nouvelle analyse", "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.", "result_directions": "Vous pouvez cliquer sur le bouton 'Scan' en haut à droite pour $t(scan.re_scan)",
"results": "Résultats de l'analyse Wi-Fi", "results": "Résultats de l'analyse Wi-Fi",
"scan": "Balayage", "scan": "Balayage",
"scanning": "Balayage... ", "scanning": "Balayage... ",
@@ -440,13 +605,69 @@
"settings": { "settings": {
"title": "Réglages" "title": "Réglages"
}, },
"simulation": {
"add": "Ajouter une simulation",
"cancel": "annuler",
"cancel_success": "Exécution de la simulation annulée avec succès !",
"check_ongoing_sims": "Suivre le Sim en cours",
"client_interval": "Intervalle client",
"delete_simulation": "Supprimer Sim. {{name}}",
"end": "Terminé",
"error_creating": "Erreur lors de la création de la simulation : {{error}}",
"error_delete": "Erreur lors de la suppression de la simulation : {{error}}",
"error_devices": "Périphériques d'erreur",
"error_edit": "Erreur lors de la tentative d'enregistrement de la simulation : {{error}}",
"error_edit_run": "Erreur lors de la tentative de modification de l'état d'exécution : {{error}}",
"error_fetching_simulations": "Erreur lors de la récupération des simulations : {{error}}",
"error_start_run": "Erreur lors de la tentative de démarrage d'une simulation : {{error}}",
"gateway": "passerelle",
"healtcheck_interval": "Intervalle de vérification de l'état",
"keep_alive": "Rester en vie",
"last_refresh": "Dernier rafraîchissement",
"length": "Longueur",
"live_devices": "Appareils en direct",
"mac_prefix": "Préfixe MAC",
"max_associations": "Max. Les associations",
"max_clients": "Max. Clients",
"messages_transmitted": "Émission de messages",
"min_associations": "Min. Les associations",
"min_clients": "Min. Clients",
"pause": "Pause",
"pause_success": "Exécuter en pause !",
"prefix_length": "Obligatoire, doit être d'une longueur de 6 caractères",
"previous_runs": "Courses précédentes",
"received": "reçu",
"received_messages": "Réception des messages",
"reconnect_interval": "Intervalle de reconnexion",
"resume": "CV",
"resume_success": "Exécution reprise !",
"run": "Exécution de simulation",
"run_simulation": "Exécuter la simulation",
"started": "commencé",
"state_interval": "Intervalle d'état",
"stop": "Arrêtez",
"stop_success": "Exécution arrêtée !",
"success_creating": "Simulation créée avec succès !",
"success_run_start": "Exécution démarrée avec succès !",
"successful_delete": "Simulation supprimée avec succès !",
"successful_edit": "Simulation éditée avec succès !",
"threads": "Fils",
"time_full_devices": "Temps pour les appareils pleins",
"title": "Simulations",
"transmitted": "Transmis",
"valid_cert": "Certificat valide",
"valid_key": "Clé valide"
},
"statistics": { "statistics": {
"data": "Données (Ko)", "data": "Données (Ko)",
"data_mb": "Données (Mo)",
"latest_statistics": "Dernières statistiques", "latest_statistics": "Dernières statistiques",
"lifetime_stats": "Statistiques à vie", "lifetime_stats": "Statistiques à vie",
"memory": "mémoire",
"no_interfaces": "Aucune statistique de durée de vie de l'interface disponible", "no_interfaces": "Aucune statistique de durée de vie de l'interface disponible",
"show_latest": "Dernières statistiques", "show_latest": "Dernières statistiques",
"title": "statistiques" "title": "statistiques",
"used": "Mémoire utilisée %"
}, },
"status": { "status": {
"connection_status": "Statut", "connection_status": "Statut",
@@ -458,9 +679,27 @@
"percentage_free": "{{percentage}}% de {{total}} gratuit", "percentage_free": "{{percentage}}% de {{total}} gratuit",
"percentage_used": "{{percentage}}% de {{total}} utilisé", "percentage_used": "{{percentage}}% de {{total}} utilisé",
"title": "#{{serialNumber}} état", "title": "#{{serialNumber}} état",
"total_memory": "Mémoire totale",
"uptime": "La disponibilité", "uptime": "La disponibilité",
"used_total_memory": "{{used}} utilisé / {{total}} total" "used_total_memory": "{{used}} utilisé / {{total}} total"
}, },
"subscriber": {
"add_device_subscriber_explanation": "Pour réclamer d'autres appareils, vous pouvez utiliser notre barre de recherche ou réclamer directement à partir du tableau. Si un appareil a déjà été réclamé par un utilisateur, vous devrez accéder à ses détails et le désaffecter avant de le réclamer.",
"create": "Créer un abonné",
"devices_one": "{{count}} Appareil",
"devices_other": "{{count}} appareils",
"edit": "Modifier l'abonné",
"error_create": "Erreur lors de la création de l'abonné : {{error}}",
"error_delete": "Erreur lors de la suppression de l'abonné : {{error}}",
"error_fetching": "Erreur lors de la récupération des abonnés : {{error}}",
"error_fetching_single": "Erreur lors de la récupération de l'abonné : {{error}}",
"error_update": "Erreur lors de la mise à jour de l'abonné : {{error}}",
"is_already_claimed": "est déjà réclamé par",
"subscribers": "Les abonnés",
"success_create": "Abonné créé avec succès !",
"success_delete": "Abonné supprimé avec succès !",
"success_update": "Abonné mis à jour avec succès !"
},
"system": { "system": {
"error_fetching": "Erreur lors de la récupération des informations système", "error_fetching": "Erreur lors de la récupération des informations système",
"error_reloading": "Erreur lors du rechargement : {{error}}", "error_reloading": "Erreur lors du rechargement : {{error}}",
@@ -507,10 +746,13 @@
"waiting_for_device": "En attente de la reconnexion de l'appareil" "waiting_for_device": "En attente de la reconnexion de l'appareil"
}, },
"user": { "user": {
"add_phone_number": "Ajouter un numéro de téléphone",
"avatar": "Votre avatar", "avatar": "Votre avatar",
"avatar_file": "Votre Avatar (max. de 2 Mo)", "avatar_file": "Votre Avatar (max. de 2 Mo)",
"check_phone": "Veuillez vérifier votre téléphone pour votre code de validation",
"confirm_new_password": "Confirmer le nouveau mot de passe",
"create": "Créer un utilisateur", "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_failure": "Erreur lors de la création de l'utilisateur : {{error}}",
"create_success": "L'utilisateur a été créé avec succès", "create_success": "L'utilisateur a été créé avec succès",
"creating": "Création de l'utilisateur...", "creating": "Création de l'utilisateur...",
"delete_avatar": "Supprimer l'avatar", "delete_avatar": "Supprimer l'avatar",
@@ -522,36 +764,51 @@
"description": "La description", "description": "La description",
"edit": "Modifier l'utilisateur", "edit": "Modifier l'utilisateur",
"email_address": "Adresse électronique", "email_address": "Adresse électronique",
"enter_new_phone": "Saisissez votre nouveau numéro de téléphone :",
"error_fetching_users": "Erreur lors de la récupération des utilisateurs : {{error}}", "error_fetching_users": "Erreur lors de la récupération des utilisateurs : {{error}}",
"error_retrieving": "Erreur lors de la récupération de l'utilisateur",
"error_sending_code": "Erreur lors de la tentative d'envoi du code de validation. Veuillez confirmer que votre numéro de téléphone est valide.",
"force_password_change": "Forcer le changement de mot de passe lors de la connexion", "force_password_change": "Forcer le changement de mot de passe lors de la connexion",
"id": "Identifiant d'utilisateur.", "id": "Identifiant d'utilisateur.",
"last_login": "Dernière connexion", "last_login": "Dernière connexion",
"login_id": "Identifiant de connexion.", "login_id": "Identifiant de connexion.",
"make_sure_same_password": "Assurez-vous que les deux mots de passe sont identiques et valides",
"my_profile": "Mon profil", "my_profile": "Mon profil",
"name": "Prénom", "name": "Prénom",
"new_code_sent": "Nouveau code envoyé !",
"nickname": "Surnom", "nickname": "Surnom",
"nickname_explanation": "Surnom (optionnel)", "nickname_explanation": "Surnom (optionnel)",
"not_validated": "Pas valide", "not_validated": "Pas valide",
"note": "Remarque", "note": "Remarque",
"password": "Mot de passe", "password": "Mot de passe",
"phone_number": "Numéro de téléphone",
"provide_email": "Veuillez fournir une adresse email valide", "provide_email": "Veuillez fournir une adresse email valide",
"provide_password": "Veuillez fournir un mot de passe valide", "provide_password": "Veuillez fournir un mot de passe valide",
"save_avatar": "Enregistrer l'avatar", "save_avatar": "Enregistrer l'avatar",
"send_code": "Envoyer le code",
"send_code_again": "Envoyer code à nouveau",
"show_hide_password": "Afficher/Masquer le mot de passe", "show_hide_password": "Afficher/Masquer le mot de passe",
"successful_validation": "Numéro de téléphone validé ! Cliquez sur le bouton Enregistrer pour le lier à votre profil",
"table_title": "Utilisateurs administrateurs",
"update_failure": "Erreur lors de la tentative de mise à jour : {{error}}", "update_failure": "Erreur lors de la tentative de mise à jour : {{error}}",
"update_failure_title": "mise à jour a échoué", "update_failure_title": "mise à jour a échoué",
"update_success": "L'utilisateur a bien été mis à jour", "update_success": "L'utilisateur a bien été mis à jour",
"update_success_title": "Succès", "update_success_title": "Succès",
"user_role": "Rôle", "user_role": "Rôle",
"users": "Utilisateurs", "users": "Utilisateurs",
"validated": "Validé" "validate_phone": "Valider",
"validated": "Validé",
"wrong_validation_code": "Vous n'avez pas entré de code valide. Veuillez réessayer et assurez-vous d'avoir entré le bon numéro de téléphone"
}, },
"wifi_analysis": { "wifi_analysis": {
"association": "Association", "association": "Association",
"associations": "Les associations", "associations": "Les associations",
"mode": "Mode", "mode": "Mode",
"network_diagram": "Diagramme de réseau", "network_diagram": "Diagramme de réseau",
"override_dfs": "Remplacer DFS",
"radios": "Radios", "radios": "Radios",
"title": "Analyse Wi-Fi" "scan_warning": "Votre radio 5G est sur un canal radar, vous devez activer \"Override DFS\" pour permettre le balayage de tous les canaux 5G",
"title": "Analyse Wi-Fi",
"vendor": "vendeur"
} }
} }

View File

@@ -17,11 +17,13 @@
"blink": "Piscar", "blink": "Piscar",
"device_leds": "LEDs do dispositivo", "device_leds": "LEDs do dispositivo",
"execute_now": "Você gostaria de definir este padrão agora?", "execute_now": "Você gostaria de definir este padrão agora?",
"explanation": "Que padrão você gostaria de definir neste dispositivo por 30 segundos?",
"pattern": "Escolha o padrão que deseja usar:", "pattern": "Escolha o padrão que deseja usar:",
"set_leds": "Definir LEDs", "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": {
"command_success": "Comando enviado com sucesso",
"error": "Erro ao enviar comando!", "error": "Erro ao enviar comando!",
"error_delete_log": "Erro ao tentar excluir: {{error}}", "error_delete_log": "Erro ao tentar excluir: {{error}}",
"event_queue": "Fila de Eventos", "event_queue": "Fila de Eventos",
@@ -32,41 +34,53 @@
"common": { "common": {
"access_policy": "Política de Acesso", "access_policy": "Política de Acesso",
"add": "Adicionar", "add": "Adicionar",
"add_items": "Adicionar itens",
"add_note": "Adicionar nota",
"add_note_explanation": "Escreva sua nova nota abaixo e clique no botão '+' quando terminar",
"adding_ellipsis": "Adicionando ...", "adding_ellipsis": "Adicionando ...",
"all": "Todos",
"are_you_sure": "Você tem certeza?", "are_you_sure": "Você tem certeza?",
"back_to_login": "Volte ao login", "back_to_login": "Volte ao login",
"back_to_start": "Voltar ao Início", "back_to_start": "Voltar ao Início",
"blacklist": "Lista negra",
"by": "Por", "by": "Por",
"cancel": "Cancelar", "cancel": "Cancelar",
"certificate": "Certificado", "certificate": "Certificado",
"certificates": "Certificados", "certificates": "Certificados",
"claim": "Afirmação",
"clear": "Claro", "clear": "Claro",
"close": "Perto", "close": "Perto",
"code": "Código",
"command": "Comando", "command": "Comando",
"commands": "comandos", "commands": "comandos",
"commands_executed": "Comandos Executados", "commands_executed": "Comandos Executados",
"compatible": "Compatível", "compatible": "Compatível",
"completed": "Concluído", "completed": "Concluído",
"concurrent_devices": "Dispositivos Simultâneos",
"config_id": "Config. Identidade", "config_id": "Config. Identidade",
"confirm": "confirme", "confirm": "confirme",
"confirm_stop_editing": "Tem certeza que deseja parar de editar? Isso cancelará todas as alterações não salvas que você fez.",
"connected": "Conectado", "connected": "Conectado",
"copied": "Copiado!", "copied": "Copiado!",
"copied_to_clipboard": "Copiado para a área de transferência!",
"copy_to_clipboard": "Copiar para área de transferência", "copy_to_clipboard": "Copiar para área de transferência",
"create": "Crio", "create": "Crio",
"created": "Criado", "created": "Criado",
"created_by": "Criado Por", "created_by": "Criado Por",
"creator": "O Criador",
"current": "Atual", "current": "Atual",
"custom_date": "Data personalizada", "custom_date": "Data personalizada",
"dashboard": "painel de controle", "dashboard": "painel de controle",
"date": "Encontro", "date": "Encontro",
"day": "dia", "day": "dia",
"days": "dias", "days": "dias",
"default_map": "Mapa Padrão",
"delete": "Excluir", "delete": "Excluir",
"delete_device": "Apagar dispositivo", "delete_device": "Apagar dispositivo",
"details": "Detalhes", "details": "Detalhes",
"device": "Dispositivo nº{{serialNumber}}", "device": "Dispositivo nº{{serialNumber}}",
"device_dashboard": "Painel do dispositivo", "device_dashboard": "Painel do dispositivo",
"device_delete": "Excluir dispositivo nº{{serialNumber}}", "device_delete": "Excluir #{{serialNumber}}",
"device_deleted": "Dispositivo excluído com sucesso", "device_deleted": "Dispositivo excluído com sucesso",
"device_health": "Saúde do Dispositivo", "device_health": "Saúde do Dispositivo",
"device_list": "Lista de Dispositivos", "device_list": "Lista de Dispositivos",
@@ -78,6 +92,7 @@
"dismiss": "Dispensar", "dismiss": "Dispensar",
"do_now": "Faça agora!", "do_now": "Faça agora!",
"download": "Baixar", "download": "Baixar",
"duplicate": "Duplicado",
"duration": "Duração", "duration": "Duração",
"edit": "Editar", "edit": "Editar",
"edit_user": "Editar", "edit_user": "Editar",
@@ -86,6 +101,8 @@
"endpoints": "Pontos finais", "endpoints": "Pontos finais",
"error": "Erro", "error": "Erro",
"error_adding_note": "Erro ao adicionar nota", "error_adding_note": "Erro ao adicionar nota",
"error_code": "Erro de código",
"errors": "Erros",
"execute_now": "Você gostaria de executar este comando agora?", "execute_now": "Você gostaria de executar este comando agora?",
"executed": "Executado", "executed": "Executado",
"exit": "Saída", "exit": "Saída",
@@ -96,12 +113,22 @@
"forgot_password_title": "Esqueceu a senha", "forgot_password_title": "Esqueceu a senha",
"from": "De", "from": "De",
"general_error": "Erro de API, consulte o seu administrador", "general_error": "Erro de API, consulte o seu administrador",
"go_back": "Volte",
"hide": "Ocultar", "hide": "Ocultar",
"hour": "hora", "hour": "hora",
"hours": "horas", "hours": "horas",
"id": "identidade", "id": "identidade",
"invalid_credentials": "Nome de usuário e / ou senha inválidos",
"invalid_date_explanation": "Data inválida, use o calendário acessível com o botão à direita",
"invalid_file": "O arquivo escolhido era inválido, por favor, leia as instruções e ajuste seu arquivo de acordo",
"invalid_password": "Esta senha não está de acordo com as regras básicas de senha. Visite nossa página de Política de Senha para saber mais",
"invalid_pem": "Seu arquivo .pem é inválido. Deve começar com '----- BEGIN CERTIFICATE -----' OU '----- BEGIN PRIVATE KEY -----' e deve terminar com '----- END CERTIFICATE --- - 'OU' ----- END PRIVATE KEY ----- '",
"ip_address": "Endereço de IP", "ip_address": "Endereço de IP",
"ips": "IPs",
"item": "Item",
"items": "Unid",
"items_per_page": "Itens por página:", "items_per_page": "Itens por página:",
"key": "Chave",
"last_dashboard_refresh": "Última atualização do painel", "last_dashboard_refresh": "Última atualização do painel",
"later_tonight": "Logo à noite", "later_tonight": "Logo à noite",
"latest": "Mais recentes", "latest": "Mais recentes",
@@ -110,28 +137,35 @@
"loading_more_ellipsis": "Carregando mais ...", "loading_more_ellipsis": "Carregando mais ...",
"logout": "Sair", "logout": "Sair",
"mac": "Endereço MAC", "mac": "Endereço MAC",
"main": "a Principal",
"manufacturer": "Fabricante", "manufacturer": "Fabricante",
"memory_used": "Memória Usada", "memory_used": "Memória Usada",
"min_max": "Mín: {{min}}, Máx: {{max}}",
"minute": "minuto", "minute": "minuto",
"minutes": "minutos", "minutes": "minutos",
"modified": "Modificado", "modified": "Modificado",
"na": "N / D", "na": "N / D",
"need_date": "Você precisa de um encontro ...", "need_date": "Você precisa de um encontro ...",
"no": "Não", "no": "Não",
"no_addresses_found": "Nenhum endereço encontrado",
"no_devices_found": "Nenhum dispositivo encontrado", "no_devices_found": "Nenhum dispositivo encontrado",
"no_items": "Nenhum item", "no_items": "Nenhum item",
"none": "Nenhum", "none": "Nenhum",
"not_connected": "Não conectado", "not_connected": "Não conectado",
"of_connected": "% de dispositivos", "of_connected": "% de dispositivos conectados",
"off": "Fora", "off": "Fora",
"on": "em", "on": "em",
"optional": "Opcional", "optional": "Opcional",
"overall_health": "Saúde geral", "overall_health": "Saúde geral",
"password_policy": "Política de Senha", "password_policy": "Política de Senha",
"preferences": "Preferências",
"preview": "Visualizar", "preview": "Visualizar",
"program": "Programa",
"reason": "RAZÃO",
"recorded": "Gravado", "recorded": "Gravado",
"refresh": "REFRESH", "refresh": "REFRESH",
"refresh_device": "Atualizar dispositivo", "refresh_device": "Atualizar dispositivo",
"remove_claim": "Remover reivindicação",
"required": "Requeridos", "required": "Requeridos",
"result": "Resultado", "result": "Resultado",
"save": "Salve", "save": "Salve",
@@ -142,16 +176,21 @@
"second": "segundo", "second": "segundo",
"seconds": "segundos", "seconds": "segundos",
"seconds_elapsed": "Segundos decorridos", "seconds_elapsed": "Segundos decorridos",
"see_details": "Veja detalhes",
"select": "Selecione",
"serial_num": "Nº de série",
"serial_number": "Número de série", "serial_number": "Número de série",
"show_all": "mostre tudo", "show_all": "mostre tudo",
"socket_connection_closed": "Conexão fechada!", "socket_connection_closed": "Conexão fechada!",
"start": "Começar", "start": "Começar",
"status": "Status",
"stop_editing": "Pare de editar", "stop_editing": "Pare de editar",
"submit": "Enviar", "submit": "Enviar",
"submitted": "Submetido", "submitted": "Submetido",
"success": "Sucesso", "success": "Sucesso",
"system": "Sistema", "system": "Sistema",
"table": "Mesa", "table": "Mesa",
"time_per_device": "Dispositivo / segundo",
"timestamp": "tempo", "timestamp": "tempo",
"to": "Para", "to": "Para",
"type": "Tipo", "type": "Tipo",
@@ -162,16 +201,22 @@
"unknown": "Desconhecido", "unknown": "Desconhecido",
"up_to_date": "Dispositivos atualizados", "up_to_date": "Dispositivos atualizados",
"uptimes": "Uptimes", "uptimes": "Uptimes",
"use_file": "Usar arquivo",
"uuid": "UUID", "uuid": "UUID",
"vendors": "Vendedores", "vendors": "Vendedores",
"view_more": "Veja mais", "view_more": "Veja mais",
"visibility": "visibilidade",
"waiting_for_update": "Aguardando atualização",
"yes": "sim" "yes": "sim"
}, },
"configuration": { "configuration": {
"add_configuration": "Adicionar configuração", "add_configuration": "Adicionar configuração",
"add_new_block": "Adicionar novo bloco de configuração", "add_new_block": "Adicionar novo bloco de configuração",
"add_or_link": "Link ou adicionar", "add_or_link": "Link ou adicionar",
"add_radio": "Adicionar rádio",
"ca_cert_explanation": "Use um arquivo .pem que comece com \"----- BEGIN CERTIFICATE -----\" e termine com \"----- END CERTIFICATE -----\". O resultado será mostrado no campo abaixo. Você também pode copiar e colar o certificado que deseja usar diretamente.",
"cannot_delete": "Esta configuração não pode ser excluída porque está sendo usada por pelo menos uma entidade, local ou dispositivo", "cannot_delete": "Esta configuração não pode ser excluída porque está sendo usada por pelo menos uma entidade, local ou dispositivo",
"choose_radio_band": "Que banda de rádio você gostaria de criar?",
"choose_section": "Qual seção você gostaria que este bloco contivesse?", "choose_section": "Qual seção você gostaria que este bloco contivesse?",
"configuration_browser": "Navegador de configuração", "configuration_browser": "Navegador de configuração",
"configurations": "configurações", "configurations": "configurações",
@@ -182,17 +227,22 @@
"creation_success": "Configuração criada com sucesso!", "creation_success": "Configuração criada com sucesso!",
"currently_associated": "Configuração atual associada: {{config}}", "currently_associated": "Configuração atual associada: {{config}}",
"currently_selected_config": "Configuração atualmente selecionada: {{config}}", "currently_selected_config": "Configuração atualmente selecionada: {{config}}",
"default_configs": "Configurações padrão",
"default_configurations": "Configurações padrão",
"delete_config": "Excluir configuração", "delete_config": "Excluir configuração",
"details": "Detalhes", "details": "Detalhes",
"device_password": "Senha", "device_password": "Senha",
"device_type": "Tipo de dispositivo", "device_type": "Tipo de dispositivo",
"device_types": "Tipos de dispositivos", "device_types": "Tipos de dispositivos",
"devices_affected": "Dispositivos afetados por esta configuração:",
"edit_configuration": "Editar configuração", "edit_configuration": "Editar configuração",
"error_delete": "Erro ao tentar excluir: {{error}}", "error_delete": "Erro ao tentar excluir: {{error}}",
"error_delete_blacklist": "Erro ao excluir da lista negra: {{error}}",
"error_fetching_config": "Erro ao buscar configuração", "error_fetching_config": "Erro ao buscar configuração",
"error_trying_delete": "Erro ao tentar excluir: {{error}}", "error_trying_delete": "Erro ao tentar excluir: {{error}}",
"error_update": "Erro: {{error}}", "error_update": "Erro: {{error}}",
"explanation": "Explicação", "explanation": "Explicação",
"key_pem_explanation": "Selecione o arquivo .pem",
"last_configuration_change": "Mudança de configuração", "last_configuration_change": "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",
@@ -213,6 +263,8 @@
"used_by": "Usado por", "used_by": "Usado por",
"used_by_details": "{{entities}} Entidades, {{venues}} Locais e {{devices}} Dispositivos", "used_by_details": "{{entities}} Entidades, {{venues}} Locais e {{devices}} Dispositivos",
"uuid": "ID de configuração", "uuid": "ID de configuração",
"view_affected_devices": "Exibir dispositivos afetados",
"view_config": "Ver configuração",
"view_in_use": "Visualização em uso", "view_in_use": "Visualização em uso",
"view_json": "Exibir JSON bruto" "view_json": "Exibir JSON bruto"
}, },
@@ -226,6 +278,38 @@
"connect": { "connect": {
"error_trying_to_connect": "Erro ao tentar conectar ao dispositivo: {{error}}" "error_trying_to_connect": "Erro ao tentar conectar ao dispositivo: {{error}}"
}, },
"contact": {
"access_pin": "PIN de acesso",
"add_contact": "Adicionar contato",
"contact": "Contato",
"create_contact": "Criar Contato",
"currently_selected_contact": "Contato atualmente selecionado: {{contact}}",
"delete": "Excluir contato?",
"error_assign": "Erro ao tentar atribuir contato: {{error}}",
"error_creation": "Erro ao tentar criar contato: {{error}}",
"error_delete": "Erro ao tentar excluir contato: {{error}}",
"error_fetching_list": "Erro ao buscar contatos",
"error_fetching_single": "Erro ao buscar contato: {{error}}",
"error_unassign": "Erro ao tentar cancelar a atribuição do contato: {{error}}",
"first_name": "Primeiro nome",
"identifier": "Identificador",
"initials": "Iniciais",
"last_name": "Último nome",
"no_associated_contact": "Nenhum contato associado",
"primary_email": "e-mail primário",
"salutation": "Saudação",
"secondary_email": "Email secundário",
"successful_assign": "Contato atribuído com sucesso!",
"successful_creation": "Contato criado!",
"successful_delete": "Contato excluído com sucesso!",
"successful_unassign": "Contato não atribuído com sucesso",
"successful_update": "Contato atualizado com sucesso",
"title": "Contatos",
"type": "Tipo",
"update_error": "Erro ao atualizar contato: {{error}}",
"user_title": "Título",
"visual": "Nome para Correspondência"
},
"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"
@@ -237,8 +321,23 @@
"healthchecks_title": "Excluir verificações de saúde" "healthchecks_title": "Excluir verificações de saúde"
}, },
"device": { "device": {
"add_to_blacklist": "Adicionar dispositivo à lista negra",
"all_devices": "Todos os dispositivos",
"blacklisted_on": "Encontro",
"capabilities": "Recursos",
"certificate_explanation": "Certificados de dispositivos conectados",
"edit_blacklist": "Editar dispositivo na lista negra",
"error_adding_blacklist": "Erro ao adicionar dispositivo à lista negra: {{error}}",
"error_edit_blacklist": "Erro ao editar a lista negra: {{error}}",
"error_fetching_device": "Erro ao buscar informações do dispositivo: {{error}}", "error_fetching_device": "Erro ao buscar informações do dispositivo: {{error}}",
"error_fetching_devices": "Erro ao buscar dispositivos: {{error}}" "error_fetching_devices": "Erro ao buscar dispositivos: {{error}}",
"health_explanation": "Integridade dos dispositivos conectados ((Dispositivos = 100% * 100 + Dispositivos> 90% * 95 + Dispositivos> 60% * 75 + Dispositivos <60% * 35) / Dispositivos Conectados)",
"memory_explanation": "Quantidade de dispositivos conectados com a memória correspondente usada%",
"remove_from_blacklist": "Remover da lista negra",
"success_added_blacklist": "Dispositivo adicionado à lista negra com sucesso!",
"success_edit_blacklist": "Lista negra editada com sucesso!",
"success_removed_blacklist": "Dispositivo removido com sucesso da lista negra!",
"uptimes_explanation": "Quantidade de dispositivos conectados com base em seu tempo de atividade"
}, },
"device_logs": { "device_logs": {
"log": "Registro", "log": "Registro",
@@ -248,22 +347,46 @@
"entity": { "entity": {
"add_child": "Adicionar Entidade Filha a {{entityName}}", "add_child": "Adicionar Entidade Filha a {{entityName}}",
"add_failure": "Erro, o servidor retornou: {{error}}", "add_failure": "Erro, o servidor retornou: {{error}}",
"add_ips": "Gerenciar IPs",
"add_root": "Adicionar Entidade Raiz", "add_root": "Adicionar Entidade Raiz",
"add_success": "Entidade criada com sucesso!", "add_success": "Entidade criada com sucesso!",
"assigned_inventory": "Estoque Atribuído", "assigned_inventory": "Estoque Atribuído",
"cannot_delete": "Você não pode excluir entidades que têm filhos. Exclua os filhos desta entidade para poder excluí-la.", "cannot_delete": "Você não pode excluir entidades que têm filhos. Exclua os filhos desta entidade para poder excluí-la.",
"confirm_map_delete": "Tem certeza que deseja excluir o mapa {{name}}? Esta ação não pode ser revertida",
"currently_selected_entity": "Entidade atualmente selecionada: {{config}}", "currently_selected_entity": "Entidade atualmente selecionada: {{config}}",
"currently_selected_venue": "Local selecionado atualmente: {{config}}", "currently_selected_venue": "Local selecionado atualmente: {{config}}",
"delete_success": "Entidade excluída com sucesso", "delete_success": "Entidade excluída com sucesso",
"delete_warning": "Aviso: esta operação não pode ser revertida", "delete_warning": "Aviso: esta operação não pode ser revertida",
"duplicate_from_node": "Duplicar com nó raiz específico",
"duplicate_map": "Mapa duplicado",
"duplicate_with_node": "Duplicar {{mapName}} com {{rootName}} como nó raiz",
"edit_failure": "Atualização malsucedida: {{error}}", "edit_failure": "Atualização malsucedida: {{error}}",
"enter_here": "Digite o (s) IP (s) que deseja adicionar aqui",
"entire_tree": "Mapa de Rede",
"entities": "Entidades", "entities": "Entidades",
"entity": "Entidade", "entity": "Entidade",
"error_deleting_map": "Erro ao excluir mapa: {{error}}",
"error_fetch_entity": "Erro ao buscar informações da entidade", "error_fetch_entity": "Erro ao buscar informações da entidade",
"error_fetching": "Erro ao buscar entidades", "error_fetching": "Erro ao buscar entidades",
"error_fetching_map": "Erro ao buscar mapa: {{error}}",
"error_fetching_tree": "Erro ao buscar árvore: {{error}}",
"error_saving": "Erro ao salvar entidade", "error_saving": "Erro ao salvar entidade",
"error_saving_map": "Erro ao salvar o mapa: {{error}}",
"higher_priority": "Dê maior prioridade",
"ip_detection": "Detecção de IP",
"ip_formats": "Você pode adicionar endereços IPv4 ou IPv6 nos seguintes formatos:",
"lower_priority": "Faça menor prioridade",
"map": "Mapa",
"map_delete_success": "Mapa excluído com sucesso!",
"need_select_entity": "Você precisa selecionar uma entidade da tabela abaixo",
"no_ips": "Nenhum IP selecionado",
"not_assigned": "Não atribuído", "not_assigned": "Não atribuído",
"only_unassigned": "Apenas não atribuídos", "only_unassigned": "Apenas não atribuídos",
"select_entity": "Selecione esta Entidade",
"selected_entity": "Entidade Selecionada",
"selected_map": "Mapa Selecionado",
"tree_saved": "Mapa salvo com sucesso!",
"update_failure_error": "Erro ao tentar atualizar a entidade: {{error}}",
"valid_serial": "Precisa ser um número de série válido (12 caracteres HEX)", "valid_serial": "Precisa ser um número de série válido (12 caracteres HEX)",
"venues": "Locais" "venues": "Locais"
}, },
@@ -275,6 +398,7 @@
"warning": "Aviso: depois de enviar, isso não pode ser revertido" "warning": "Aviso: depois de enviar, isso não pode ser revertido"
}, },
"firmware": { "firmware": {
"age_explanation": "Número médio de dias para todos os dispositivos dos quais podemos obter esse valor",
"average_age": "Idade Média do Firmware", "average_age": "Idade Média do Firmware",
"choose_custom": "Escolher", "choose_custom": "Escolher",
"details_title": "Detalhes da imagem #{{image}} ", "details_title": "Detalhes da imagem #{{image}} ",
@@ -287,9 +411,11 @@
"image": "Imagem", "image": "Imagem",
"image_date": "Data da Imagem", "image_date": "Data da Imagem",
"installed_firmware": "Firmware Instalado", "installed_firmware": "Firmware Instalado",
"latest_explanation": "Dispositivos executando firmware reconhecido em sua versão mais recente",
"latest_version_installed": "Última versão instalada", "latest_version_installed": "Última versão instalada",
"newer_firmware_available": "Novas revisões disponíveis", "newer_firmware_available": "Novas revisões disponíveis",
"reinstall_latest": "Reinstalar", "reinstall_latest": "Reinstalar",
"release": "LANÇAMENTO",
"revision": "Revisão", "revision": "Revisão",
"show_dev": "Mostrar lançamentos de desenvolvimento", "show_dev": "Mostrar lançamentos de desenvolvimento",
"size": "Tamanho", "size": "Tamanho",
@@ -318,6 +444,7 @@
"add_tag": "Criar tag", "add_tag": "Criar tag",
"add_tag_to": "Adicionar novo dispositivo a {{name}}", "add_tag_to": "Adicionar novo dispositivo a {{name}}",
"add_venue": "Adicionar Local", "add_venue": "Adicionar Local",
"assign_ent_ven": "Atribuir à Entidade ou Local",
"assign_entity_instructions": "Você pode encontrar a entidade à qual deseja que esta tag seja atribuída usando o menu abaixo ou pode colar manualmente o UUID da entidade no campo acima.", "assign_entity_instructions": "Você pode encontrar a entidade à qual deseja que esta tag seja atribuída usando o menu abaixo ou pode colar manualmente o UUID da entidade no campo acima.",
"assign_error": "Erro ao tentar atribuir tag", "assign_error": "Erro ao tentar atribuir tag",
"assign_to_entity": "Atribuir à Entidade", "assign_to_entity": "Atribuir à Entidade",
@@ -349,6 +476,7 @@
"error_create_venue": "Erro ao criar local", "error_create_venue": "Erro ao criar local",
"error_delete_tag": "Erro ao excluir tag de inventário", "error_delete_tag": "Erro ao excluir tag de inventário",
"error_get_venue": "Erro ao recuperar locais", "error_get_venue": "Erro ao recuperar locais",
"error_pushing_config": "Erro ao tentar enviar a configuração para o dispositivo: {{error}}",
"error_retrieving": "Ocorreu um erro ao recuperar as tags de inventário", "error_retrieving": "Ocorreu um erro ao recuperar as tags de inventário",
"error_unassign": "Erro durante operação de cancelamento de atribuição", "error_unassign": "Erro durante operação de cancelamento de atribuição",
"error_update_venue": "Erro ao atualizar o local", "error_update_venue": "Erro ao atualizar o local",
@@ -394,15 +522,46 @@
"unassigned_deleted_devices": "{{number}} Dispositivos excluídos e não atribuídos", "unassigned_deleted_devices": "{{number}} Dispositivos excluídos e não atribuídos",
"unassigned_tags": "Tags não atribuídas", "unassigned_tags": "Tags não atribuídas",
"validating_import_file": "Validando arquivo de importação e dados ...", "validating_import_file": "Validando arquivo de importação e dados ...",
"venue": "Local" "venue": "Local",
"view_in_gateway": "Ver no Gateway"
},
"location": {
"add": "Adicionar Local",
"building_name": "nome do edifício",
"city": "Cidade",
"country": "País",
"create": "Criar local",
"currently_selected": "Local selecionado atualmente: {{location}}",
"delete": "Excluir localização?",
"error_assign": "Erro ao tentar atribuir local: {{error}}",
"error_creation": "Erro ao tentar criar locais: {{error}}",
"error_delete": "Erro ao excluir localização: {{error}}",
"error_fetching_single": "Erro ao tentar buscar localização: {{error}}",
"geocode": "GeoCode",
"mobiles": "Móbiles",
"no_associated": "Sem localização associada",
"phones": "Telefones",
"postal": "CEP / Código Postal",
"search": "Pesquise locais para preencher automaticamente os campos abaixo",
"state": "Estado",
"street_address": "Endereço",
"successful_creation": "Local criado com sucesso!",
"successful_delete": "Local excluído com sucesso!",
"successful_update": "Local atualizado com sucesso!",
"successfully_assigned": "Local atribuído com sucesso!",
"title": "Localizações",
"update_error": "Erro ao atualizar local: {{error}}"
}, },
"login": { "login": {
"account_verification": "Verificação de conta",
"authentication_expired": "Autenticação expirada, reinicie o processo de login",
"change_password": "Mudar senha", "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_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", "change_password_instructions": "Digite e confirme sua nova senha",
"changing_password": "Alterando senha ...", "changing_password": "Alterando senha ...",
"confirm_new_password": "confirme a nova senha", "confirm_new_password": "confirme a nova senha",
"different_passwords": "Você precisa inserir a mesma senha duas vezes", "different_passwords": "Você precisa inserir a mesma senha duas vezes",
"email_code_validation": "Verifique sua caixa de e-mail e insira o código de verificação que acabamos de enviar na caixa abaixo",
"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_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_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", "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",
@@ -411,6 +570,7 @@
"login_error": "Erro de login, certifique-se de que as informações que você está fornecendo são válidas", "login_error": "Erro de login, certifique-se de que as informações que você está fornecendo são válidas",
"new_password": "Nova senha", "new_password": "Nova senha",
"password": "Senha", "password": "Senha",
"phone_validation_explanation": "Verifique seu dispositivo móvel e insira o código de verificação que acabamos de enviar na caixa abaixo",
"please_enter_gateway": "Insira um URL uCentralSec", "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",
@@ -419,7 +579,12 @@
"sending_ellipsis": "Enviando ...", "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", "url": "URL uCentralSec",
"username": "Nome de usuário" "username": "Nome de usuário",
"verification_code": "Insira sua verificação aqui",
"wrong_code": "O código de verificação inserido não é válido."
},
"preferences": {
"provisioning": "Provisioning"
}, },
"reboot": { "reboot": {
"directions": "Quando você gostaria de reinicializar este dispositivo?", "directions": "Quando você gostaria de reinicializar este dispositivo?",
@@ -431,7 +596,7 @@
"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.",
"re_scan": "Verificar novamente", "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.", "result_directions": "Você pode clicar no botão 'Scan' no canto superior direito para $t(scan.re_scan)",
"results": "Resultados da verificação de Wi-Fi", "results": "Resultados da verificação de Wi-Fi",
"scan": "Varredura", "scan": "Varredura",
"scanning": "Scanning... ", "scanning": "Scanning... ",
@@ -440,13 +605,69 @@
"settings": { "settings": {
"title": "Definições" "title": "Definições"
}, },
"simulation": {
"add": "Adicionar Simulação",
"cancel": "Cancelar",
"cancel_success": "Simulação cancelada com sucesso!",
"check_ongoing_sims": "Seguir Sim em Curso",
"client_interval": "Intervalo do Cliente",
"delete_simulation": "Excluir Sim. {{name}}",
"end": "Terminado",
"error_creating": "Erro ao criar simulação: {{error}}",
"error_delete": "Erro ao excluir simulação: {{error}}",
"error_devices": "Dispositivos de Erro",
"error_edit": "Erro ao tentar salvar a simulação: {{error}}",
"error_edit_run": "Erro ao tentar alterar o estado de execução: {{error}}",
"error_fetching_simulations": "Erro ao buscar simulações: {{error}}",
"error_start_run": "Erro ao tentar iniciar uma simulação: {{error}}",
"gateway": "Gateway",
"healtcheck_interval": "Intervalo de verificação de saúde",
"keep_alive": "Mantenha vivo",
"last_refresh": "Última atualização",
"length": "Comprimento",
"live_devices": "Dispositivos Live",
"mac_prefix": "Prefixo MAC",
"max_associations": "Máx. Associações",
"max_clients": "Máx. Clientes",
"messages_transmitted": "Msgs TX",
"min_associations": "Min. Associações",
"min_clients": "Min. Clientes",
"pause": "pausa",
"pause_success": "Executar em pausa!",
"prefix_length": "Obrigatório, deve ter 6 caracteres",
"previous_runs": "Execuções anteriores",
"received": "recebido",
"received_messages": "Msgs RX",
"reconnect_interval": "Intervalo de reconexão",
"resume": "Currículo",
"resume_success": "Executar retomado!",
"run": "Simulação Rodada",
"run_simulation": "Executar Simulação",
"started": "Começado",
"state_interval": "Intervalo de estado",
"stop": "Pare",
"stop_success": "Run Stopped!",
"success_creating": "Simulação criada com sucesso!",
"success_run_start": "Execução iniciada com sucesso!",
"successful_delete": "Simulação excluída com sucesso!",
"successful_edit": "Simulação editada com sucesso!",
"threads": "Tópicos",
"time_full_devices": "Tempo para dispositivos completos",
"title": "Simulações",
"transmitted": "Transmitido",
"valid_cert": "Certificado Válido",
"valid_key": "Chave Válida"
},
"statistics": { "statistics": {
"data": "Dados (KB)", "data": "Dados (KB)",
"data_mb": "Dados (MB)",
"latest_statistics": "Estatísticas mais recentes", "latest_statistics": "Estatísticas mais recentes",
"lifetime_stats": "Estatísticas de vida", "lifetime_stats": "Estatísticas de vida",
"memory": "Memória",
"no_interfaces": "Nenhuma estatística de tempo de vida da interface disponível", "no_interfaces": "Nenhuma estatística de tempo de vida da interface disponível",
"show_latest": "Últimas estatísticas", "show_latest": "Últimas estatísticas",
"title": "Estatisticas" "title": "Estatisticas",
"used": "Memoria usada %"
}, },
"status": { "status": {
"connection_status": "Status", "connection_status": "Status",
@@ -458,9 +679,27 @@
"percentage_free": "{{percentage}}% de {{total}} grátis", "percentage_free": "{{percentage}}% de {{total}} grátis",
"percentage_used": "{{percentage}}% de {{total}} usado", "percentage_used": "{{percentage}}% de {{total}} usado",
"title": "#{{serialNumber}} status", "title": "#{{serialNumber}} status",
"total_memory": "Memória total",
"uptime": "Tempo de atividade", "uptime": "Tempo de atividade",
"used_total_memory": "{{used}} usado / {{total}} total" "used_total_memory": "{{used}} usado / {{total}} total"
}, },
"subscriber": {
"add_device_subscriber_explanation": "Para reivindicar outros dispositivos, você pode usar nossa barra de pesquisa ou reivindicar diretamente na tabela. Se um dispositivo já foi reivindicado por um usuário, você precisará acessar os detalhes dele e cancelar a atribuição antes de reivindicá-lo.",
"create": "Criar assinante",
"devices_one": "{{count}} Dispositivo",
"devices_other": "{{count}} dispositivos",
"edit": "Editar Assinante",
"error_create": "Erro ao criar assinante: {{error}}",
"error_delete": "Erro ao excluir assinante: {{error}}",
"error_fetching": "Erro ao buscar assinantes: {{error}}",
"error_fetching_single": "Erro ao buscar assinante: {{error}}",
"error_update": "Erro ao atualizar assinante: {{error}}",
"is_already_claimed": "já é reivindicado por",
"subscribers": "Inscritos",
"success_create": "Assinante criado com sucesso!",
"success_delete": "Assinante excluído com sucesso!",
"success_update": "Assinante atualizado com sucesso!"
},
"system": { "system": {
"error_fetching": "Erro ao buscar informações do sistema", "error_fetching": "Erro ao buscar informações do sistema",
"error_reloading": "Erro ao recarregar: {{error}}", "error_reloading": "Erro ao recarregar: {{error}}",
@@ -507,10 +746,13 @@
"waiting_for_device": "Esperando que o dispositivo se reconecte" "waiting_for_device": "Esperando que o dispositivo se reconecte"
}, },
"user": { "user": {
"add_phone_number": "ADICIONAR NÚMERO DE TELEFONE",
"avatar": "Seu avatar", "avatar": "Seu avatar",
"avatar_file": "Seu avatar (máx. De 2 MB)", "avatar_file": "Seu avatar (máx. De 2 MB)",
"check_phone": "Por favor, verifique o seu telefone para o seu código de validação",
"confirm_new_password": "confirme a nova senha",
"create": "Criar usuário", "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_failure": "Erro ao criar usuário: {{error}}",
"create_success": "Usuário criado com sucesso", "create_success": "Usuário criado com sucesso",
"creating": "Criando usuário ...", "creating": "Criando usuário ...",
"delete_avatar": "Apagar Avatar", "delete_avatar": "Apagar Avatar",
@@ -522,36 +764,51 @@
"description": "Descrição", "description": "Descrição",
"edit": "Editar usuário", "edit": "Editar usuário",
"email_address": "Endereço de e-mail", "email_address": "Endereço de e-mail",
"enter_new_phone": "Insira seu novo número de telefone:",
"error_fetching_users": "Erro ao buscar usuários: {{error}}", "error_fetching_users": "Erro ao buscar usuários: {{error}}",
"error_retrieving": "Erro ao recuperar usuário",
"error_sending_code": "Erro ao tentar enviar o código de validação. Por favor, confirme se o seu número de telefone é válido.",
"force_password_change": "Forçar mudança de senha no login", "force_password_change": "Forçar mudança de senha no login",
"id": "ID do usuário.", "id": "ID do usuário.",
"last_login": "Último login", "last_login": "Último login",
"login_id": "Identificação de usuário.", "login_id": "Identificação de usuário.",
"make_sure_same_password": "Certifique-se de que ambas as senhas são iguais e válidas",
"my_profile": "Meu perfil", "my_profile": "Meu perfil",
"name": "Nome", "name": "Nome",
"new_code_sent": "Novo código enviado!",
"nickname": "Apelido", "nickname": "Apelido",
"nickname_explanation": "Apelido (opcional)", "nickname_explanation": "Apelido (opcional)",
"not_validated": "Não validado", "not_validated": "Não validado",
"note": "Nota", "note": "Nota",
"password": "Senha", "password": "Senha",
"phone_number": "Número de telefone",
"provide_email": "Por favor, forneça um endereço de e-mail válido", "provide_email": "Por favor, forneça um endereço de e-mail válido",
"provide_password": "Forneça uma senha válida", "provide_password": "Forneça uma senha válida",
"save_avatar": "Salvar Avatar", "save_avatar": "Salvar Avatar",
"send_code": "Enviar código",
"send_code_again": "Envie o Código Novamente",
"show_hide_password": "Mostrar / ocultar senha", "show_hide_password": "Mostrar / ocultar senha",
"successful_validation": "Número de telefone validado! Clique no botão Salvar para vinculá-lo ao seu perfil",
"table_title": "Usuários administrativos",
"update_failure": "Erro ao tentar atualizar: {{error}}", "update_failure": "Erro ao tentar atualizar: {{error}}",
"update_failure_title": "Atualização falhou", "update_failure_title": "Atualização falhou",
"update_success": "Usuário atualizado com sucesso", "update_success": "Usuário atualizado com sucesso",
"update_success_title": "Sucesso", "update_success_title": "Sucesso",
"user_role": "Função", "user_role": "Função",
"users": "Comercial", "users": "Comercial",
"validated": "Validado" "validate_phone": "validar",
"validated": "Validado",
"wrong_validation_code": "Você não digitou um código válido. Por favor, tente novamente e certifique-se de ter inserido o número de telefone correto"
}, },
"wifi_analysis": { "wifi_analysis": {
"association": "Associação", "association": "Associação",
"associations": "Associações", "associations": "Associações",
"mode": "Modo", "mode": "Modo",
"network_diagram": "Diagrama de rede", "network_diagram": "Diagrama de rede",
"override_dfs": "Substituir DFS",
"radios": "Rádios", "radios": "Rádios",
"title": "Análise de Wi-Fi" "scan_warning": "Seu rádio 5G está em um canal de radar, você deve habilitar “Override DFS” para permitir a varredura de todos os canais 5G",
"title": "Análise de Wi-Fi",
"vendor": "fornecedor"
} }
} }

View File

@@ -5,6 +5,7 @@ import Router from 'router';
import { AuthProvider } from 'ucentral-libs'; import { AuthProvider } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper'; import { checkIfJson } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getItem } from 'utils/localStorageHelper';
const loading = ( const loading = (
<div className="pt-3 text-center"> <div className="pt-3 text-center">
@@ -13,9 +14,9 @@ const loading = (
); );
const App = () => { const App = () => {
const storageToken = sessionStorage.getItem('access_token'); const storageToken = getItem('access_token');
const apiEndpoints = checkIfJson(sessionStorage.getItem('gateway_endpoints')) const apiEndpoints = checkIfJson(getItem('gateway_endpoints'))
? JSON.parse(sessionStorage.getItem('gateway_endpoints')) ? JSON.parse(getItem('gateway_endpoints'))
: {}; : {};
return ( return (

BIN
src/assets/NotFound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -13,6 +13,7 @@ import {
cilArrowTop, cilArrowTop,
cilAsterisk, cilAsterisk,
cilBan, cilBan,
cilBarcode,
cilBasket, cilBasket,
cilBell, cilBell,
cilBold, cilBold,
@@ -108,6 +109,7 @@ export const icons = {
cilArrowTop, cilArrowTop,
cilAsterisk, cilAsterisk,
cilBan, cilBan,
cilBarcode,
cilBasket, cilBasket,
cilBell, cilBell,
cilBold, cilBold,

View File

@@ -0,0 +1,163 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import {
CForm,
CInput,
CLabel,
CCol,
CFormGroup,
CInvalidFeedback,
CFormText,
CRow,
CTextarea,
} from '@coreui/react';
import { CopyToClipboardButton } from 'ucentral-libs';
const AddDefaultConfigurationForm = ({
t,
disable,
fields,
updateField,
updateFieldWithKey,
deviceTypes,
}) => {
const [typeOptions, setTypeOptions] = useState([]);
const [chosenTypes, setChosenTypes] = useState([]);
const parseOptions = () => {
const options = [{ value: '*', label: 'All' }];
const newOptions = deviceTypes.map((option) => ({
value: option,
label: option,
}));
options.push(...newOptions);
setTypeOptions(options);
setChosenTypes([]);
};
const typeOnChange = (chosenArray) => {
const allIndex = chosenArray.findIndex((el) => el.value === '*');
// If the All option was chosen before, we take it out of the array
if (allIndex === 0 && chosenTypes.length > 0) {
const newResults = chosenArray.slice(1);
setChosenTypes(newResults);
updateFieldWithKey('deviceTypes', {
value: newResults.map((el) => el.value),
error: false,
notEmpty: true,
});
} else if (allIndex > 0) {
setChosenTypes([{ value: '*', label: 'All' }]);
updateFieldWithKey('deviceTypes', { value: ['*'], error: false, notEmpty: true });
} else if (chosenArray.length > 0) {
setChosenTypes(chosenArray);
updateFieldWithKey('deviceTypes', {
value: chosenArray.map((el) => el.value),
error: false,
notEmpty: true,
});
} else {
setChosenTypes([]);
updateFieldWithKey('deviceTypes', { value: [], error: false, notEmpty: true });
}
};
useEffect(() => {
parseOptions();
}, [deviceTypes]);
return (
<CForm>
<CFormGroup row className="pb-3">
<CLabel col htmlFor="name">
{t('user.name')}
</CLabel>
<CCol sm="7">
<CInput
id="name"
type="text"
required
value={fields.name.value}
onChange={updateField}
invalid={fields.name.error}
disabled={disable}
maxLength="50"
/>
<CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
</CCol>
</CFormGroup>
<CFormGroup row className="pb-3">
<CLabel col htmlFor="description">
{t('user.description')}
</CLabel>
<CCol sm="7">
<CInput
id="description"
type="text"
required
value={fields.description.value}
onChange={updateField}
invalid={fields.description.error}
disabled={disable}
maxLength="50"
/>
<CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
</CCol>
</CFormGroup>
<CRow className="pb-3">
<CLabel col htmlFor="deviceTypes">
<div>{t('configuration.supported_device_types')}:</div>
</CLabel>
<CCol sm="7">
<Select
isMulti
closeMenuOnSelect={false}
id="deviceTypes"
options={typeOptions}
onChange={typeOnChange}
value={chosenTypes}
className={`basic-multi-select ${fields.deviceTypes.error ? 'border-danger' : ''}`}
classNamePrefix="select"
/>
<CFormText hidden={!fields.deviceTypes.error} color="danger">
{t('configuration.need_device_type')}
</CFormText>
</CCol>
</CRow>
<div className="pb-3">
{t('configure.enter_new')}
<CopyToClipboardButton t={t} size="sm" content={fields.configuration.value} />
</div>
<CRow className="pb-3">
<CCol>
<CTextarea
style={{ overflowY: 'scroll', height: '500px' }}
id="configuration"
type="text"
required
value={fields.configuration.value}
onChange={updateField}
invalid={fields.configuration.error}
disabled={disable}
/>
<CFormText hidden={!fields.configuration.error} color="danger">
{t('configure.valid_json')}
</CFormText>
</CCol>
</CRow>
</CForm>
);
};
AddDefaultConfigurationForm.propTypes = {
t: PropTypes.func.isRequired,
disable: PropTypes.bool.isRequired,
fields: PropTypes.instanceOf(Object).isRequired,
updateField: PropTypes.func.isRequired,
updateFieldWithKey: PropTypes.func.isRequired,
deviceTypes: PropTypes.instanceOf(Array).isRequired,
};
export default AddDefaultConfigurationForm;

View File

@@ -0,0 +1,183 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { CModal, CModalHeader, CModalTitle, CModalBody, CButton, CPopover } from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilX, cilSave } from '@coreui/icons';
import { useToast, useFormFields, useAuth } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
import { useTranslation } from 'react-i18next';
import { checkIfJson } from 'utils/helper';
import Form from './Form';
const initialForm = {
name: {
value: '',
error: false,
required: true,
},
description: {
value: '',
error: false,
},
deviceTypes: {
value: [],
error: false,
notEmpty: true,
},
configuration: {
value: '',
error: false,
required: true,
},
};
const AddConfigurationModal = ({ show, toggle, refresh }) => {
const { t } = useTranslation();
const { addToast } = useToast();
const { currentToken, endpoints } = useAuth();
const [fields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialForm);
const [loading, setLoading] = useState(false);
const [deviceTypes, setDeviceTypes] = useState([]);
const getDeviceTypes = () => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`${endpoints.owfms}/api/v1/firmwares?deviceSet=true`, {
headers,
})
.then((response) => {
setDeviceTypes([...response.data.deviceTypes]);
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
};
const validation = () => {
let success = true;
for (const [key, field] of Object.entries(fields)) {
if (field.required && field.value === '') {
updateField(key, { error: true });
success = false;
break;
}
if (field.notEmpty && field.value.length === 0) {
updateField(key, { error: true, notEmpty: true });
success = false;
break;
}
}
if (!checkIfJson(fields.configuration.value)) {
updateField('configuration', { error: true });
success = false;
}
return success;
};
const addConfiguration = () => {
if (validation()) {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
const parameters = {
name: fields.name.value,
description: fields.description.value,
modelIds: fields.deviceTypes.value,
configuration: fields.configuration.value,
};
axiosInstance
.post(
`${endpoints.owgw}/api/v1/default_configuration/${fields.name.value}`,
parameters,
options,
)
.then(() => {
if (refresh !== null) refresh();
toggle();
addToast({
title: t('common.success'),
body: t('configuration.creation_success'),
color: 'success',
autohide: true,
});
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('entity.add_failure', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
}
};
useEffect(() => {
if (show) {
getDeviceTypes();
setFormFields(initialForm);
}
}, [show]);
return (
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('configuration.create')}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.add')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={addConfiguration}>
<CIcon content={cilSave} />
</CButton>
</CPopover>
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody className="px-5">
<Form
t={t}
disable={loading}
fields={fields}
updateField={updateFieldWithId}
updateFieldWithKey={updateField}
deviceTypes={deviceTypes}
show={show}
/>
</CModalBody>
</CModal>
);
};
AddConfigurationModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
refresh: PropTypes.func,
};
AddConfigurationModal.defaultProps = {
refresh: null,
};
export default AddConfigurationModal;

View File

@@ -0,0 +1,158 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import {
CButton,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CPopover,
CRow,
CCol,
CLabel,
CTextarea,
CInput,
CInvalidFeedback,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { useAuth, useToast } from 'ucentral-libs';
import { cilPlus, cilX } from '@coreui/icons';
import axiosInstance from 'utils/axiosInstance';
const AddToBlacklistModal = ({ show, toggle, serialNumber, refresh }) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const { addToast } = useToast();
const { endpoints, currentToken } = useAuth();
const [chosenSerialNumber, setChosenSerialNumber] = useState('');
const [reason, setReason] = useState('');
const addToBlacklist = () => {
setLoading(true);
const parameters = {
serialNumber: chosenSerialNumber,
reason,
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.post(`${endpoints.owgw}/api/v1/blacklist/${chosenSerialNumber}`, parameters, { headers })
.then(() => {
addToast({
title: t('common.success'),
body: t('device.success_added_blacklist'),
color: 'success',
autohide: true,
});
toggle();
if (refresh) refresh();
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
if (show) {
if (serialNumber) setChosenSerialNumber(serialNumber);
else setChosenSerialNumber('');
}
}, [show, serialNumber]);
return (
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('device.add_to_blacklist')}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.add')}>
<CButton
color="primary"
variant="outline"
className="ml-2"
onClick={addToBlacklist}
disabled={
chosenSerialNumber.length !== 12 ||
!chosenSerialNumber.match('^[a-fA-F0-9]+$') ||
reason === '' ||
loading
}
>
<CIcon content={cilPlus} />
</CButton>
</CPopover>
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody>
<CRow>
<CLabel col sm="3">
{t('common.serial_number')}
</CLabel>
<CCol sm="9" className="pt-1">
<CInput
id="description"
type="text"
required
value={chosenSerialNumber}
onChange={(e) => setChosenSerialNumber(e.target.value)}
invalid={
chosenSerialNumber.length !== 12 && chosenSerialNumber.match('^[a-fA-F0-9]+$')
}
disabled={loading}
maxLength="50"
/>
<CInvalidFeedback>{t('entity.valid_serial')}</CInvalidFeedback>
</CCol>
</CRow>
<CRow>
<CLabel col sm="3">
{t('common.reason')}
</CLabel>
<CCol sm="9" className="pt-2">
<CTextarea
name="reason"
id="reason"
rows="3"
type="text"
required
value={reason}
onChange={(e) => setReason(e.target.value)}
/>
</CCol>
</CRow>
</CModalBody>
</CModal>
);
};
AddToBlacklistModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
serialNumber: PropTypes.string,
refresh: PropTypes.func,
};
AddToBlacklistModal.defaultProps = {
serialNumber: '',
refresh: null,
};
export default AddToBlacklistModal;

View File

@@ -0,0 +1,210 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate';
import {
CCardBody,
CDataTable,
CButton,
CLink,
CCard,
CCardHeader,
CPopover,
CSelect,
CButtonToolbar,
} from '@coreui/react';
import { cilSearch, cilPencil, cilPlus, cilTrash } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import ReactTooltip from 'react-tooltip';
import { FormattedDate } from 'ucentral-libs';
const BlacklistTable = ({
currentPage,
devices,
toggleAddBlacklist,
toggleEditModal,
devicesPerPage,
loading,
removeFromBlacklist,
updateDevicesPerPage,
pageCount,
updatePage,
t,
}) => {
const columns = [
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } },
{ key: 'created', label: t('device.blacklisted_on'), _style: { width: '1%' } },
{ key: 'author', label: t('common.by'), filter: false, _style: { width: '15%' } },
{ key: 'reason', label: t('common.reason'), filter: false },
{ key: 'actions', label: t('actions.actions'), _style: { width: '1%' } },
];
const hideTooltips = () => ReactTooltip.hide();
const escFunction = (event) => {
if (event.keyCode === 27) {
hideTooltips();
}
};
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', escFunction, false);
};
}, []);
return (
<>
<CCard className="m-0 p-0">
<CCardHeader className="p-0 text-right">
<CPopover content={t('device.add_to_blacklist')}>
<CButton size="sm" color="primary" onClick={toggleAddBlacklist}>
<CIcon content={cilPlus} />
</CButton>
</CPopover>
</CCardHeader>
<CCardBody className="p-0">
<CDataTable
addTableClasses="ignore-overflow table-sm"
items={devices ?? []}
fields={columns}
hover
border
loading={loading}
scopedSlots={{
serialNumber: (item) => (
<td className="text-center align-middle">
<CLink
className="c-subheader-nav-link"
aria-current="page"
to={() => `/devices/${item.serialNumber}`}
>
{item.serialNumber}
</CLink>
</td>
),
created: (item) => (
<td className="text-left align-middle">
<div style={{ width: '130px' }}>
<FormattedDate date={item.created} />
</div>
</td>
),
author: (item) => <td className="align-middle">{item.author}</td>,
reason: (item) => <td className="align-middle">{item.reason}</td>,
actions: (item) => (
<td className="text-center align-middle">
<CButtonToolbar
role="group"
className="justify-content-center"
style={{ width: '130px' }}
>
<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"
className="mx-1"
style={{ width: '33px', height: '30px' }}
>
<CIcon name="cil-search" content={cilSearch} size="sm" />
</CButton>
</CLink>
</CPopover>
<CPopover content={t('device.remove_from_blacklist')}>
<CButton
onClick={() => removeFromBlacklist(item.serialNumber)}
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-1"
style={{ width: '33px', height: '30px' }}
>
<CIcon content={cilTrash} size="sm" />
</CButton>
</CPopover>
<CPopover content={t('common.edit')}>
<CButton
onClick={() => toggleEditModal(item.serialNumber)}
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-1"
style={{ width: '33px', height: '30px' }}
>
<CIcon content={cilPencil} size="sm" />
</CButton>
</CPopover>
</CButtonToolbar>
</td>
),
}}
/>
<div className="d-flex flex-row pl-3">
<div className="pr-3">
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={updatePage}
forcePage={Number(currentPage)}
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"
/>
</div>
<p className="pr-2 mt-1">{t('common.items_per_page')}</p>
<div style={{ width: '100px' }} className="px-2">
<CSelect
custom
defaultValue={devicesPerPage}
onChange={(e) => updateDevicesPerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</div>
</CCardBody>
</CCard>
</>
);
};
BlacklistTable.propTypes = {
currentPage: PropTypes.string,
devices: PropTypes.instanceOf(Array).isRequired,
toggleAddBlacklist: PropTypes.func.isRequired,
toggleEditModal: PropTypes.func.isRequired,
updateDevicesPerPage: PropTypes.func.isRequired,
pageCount: PropTypes.number.isRequired,
updatePage: PropTypes.func.isRequired,
devicesPerPage: PropTypes.string.isRequired,
removeFromBlacklist: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};
BlacklistTable.defaultProps = {
currentPage: '0',
};
export default React.memo(BlacklistTable);

View File

@@ -0,0 +1,30 @@
.firmwareTooltip {
opacity: 1 !important;
padding: 0px 0px 0px 0px !important;
border-radius: 1rem !important;
background-color: #fff !important;
border-color: #321fdb !important;
font-size: 0.875rem !important;
font-weight: 400 !important;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
width: 400px;
}
.deleteTooltip {
opacity: 1 !important;
padding: 0px 0px 0px 0px !important;
border-radius: 1rem !important;
background-color: #fff !important;
border-color: #321fdb !important;
font-size: 0.875rem !important;
font-weight: 400 !important;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
width: 200px;
}
.tooltipHeader {
padding-left: 5px;
padding-right: 10px;
border-top-left-radius: 1rem !important;
border-top-right-radius: 1rem !important;
}

View File

@@ -0,0 +1,244 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import axiosInstance from 'utils/axiosInstance';
import { getItem, setItem } from 'utils/localStorageHelper';
import { useAuth, useToast, useToggle } from 'ucentral-libs';
import AddToBlacklistModal from 'components/AddToBlacklistModal';
import EditBlacklistModal from 'components/EditBlacklistModal';
import Table from './Table';
const BlacklistTable = () => {
const { t } = useTranslation();
const { addToast } = useToast();
const history = useHistory();
const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
const { currentToken, endpoints } = useAuth();
const [deviceCount, setDeviceCount] = useState(0);
const [pageCount, setPageCount] = useState(0);
const [devicesPerPage, setDevicesPerPage] = useState(getItem('devicesPerPage') || '10');
const [devices, setDevices] = useState([]);
const [loading, setLoading] = useState(true);
const [editSerial, setEditSerial] = useState('');
const [showEditModal, setShowEditModal] = useState(false);
const [showAddModal, toggleAddModal] = useToggle(false);
const toggleEditModal = (serialNumber) => {
if (serialNumber) setEditSerial(serialNumber);
setShowEditModal(!showEditModal);
};
const getDeviceInformation = (selectedPage = page, devicePerPage = devicesPerPage) => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.owgw}/api/v1/blacklist?limit=${devicePerPage}&offset=${
devicePerPage * selectedPage
}`,
options,
)
.then((response) => {
setDevices(response.data.devices);
setLoading(false);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
setLoading(false);
});
};
const getCount = () => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/blacklist?countOnly=true`, {
headers,
})
.then((response) => {
const devicesCount = response.data.count;
const pagesCount = Math.ceil(devicesCount / devicesPerPage);
setPageCount(pagesCount);
setDeviceCount(devicesCount);
let selectedPage = page;
if (page >= pagesCount) {
history.push(`/devices?page=${pagesCount - 1}`);
selectedPage = pagesCount - 1;
}
getDeviceInformation(selectedPage);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
setLoading(false);
});
};
const refreshDevice = (serialNumber) => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
let newDevice;
axiosInstance
.get(
`${endpoints.owgw}/api/v1/blacklist?deviceWithStatus=true&select=${encodeURIComponent(
serialNumber,
)}`,
options,
)
.then(
({
data: {
devicesWithStatus: [device],
},
}) => {
newDevice = device;
return axiosInstance.get(
`${endpoints.owfms}/api/v1/firmwareAge?select=${serialNumber}`,
options,
);
},
)
.then((response) => {
newDevice.firmwareInfo = {
age: response.data.ages[0].age,
latest: response.data.ages[0].latest,
};
const foundIndex = devices.findIndex((obj) => obj.serialNumber === serialNumber);
const newList = devices;
newList[foundIndex] = newDevice;
setDevices(newList);
setLoading(false);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
setLoading(false);
});
};
const updateDevicesPerPage = (value) => {
setItem('devicesPerPage', value);
setDevicesPerPage(value);
const newPageCount = Math.ceil(deviceCount / value);
setPageCount(newPageCount);
let selectedPage = page;
if (page >= newPageCount) {
history.push(`/blacklist?page=${newPageCount - 1}`);
selectedPage = newPageCount - 1;
}
getDeviceInformation(selectedPage, value);
};
const updatePageCount = ({ selected: selectedPage }) => {
sessionStorage.setItem('deviceTable', selectedPage);
setPage(selectedPage);
getDeviceInformation(selectedPage);
};
const removeFromBlacklist = (serialNumber) => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.delete(`${endpoints.owgw}/api/v1/blacklist/${serialNumber}`, { headers })
.then(() => {
addToast({
title: t('common.success'),
body: t('device.success_removed_blacklist'),
color: 'success',
autohide: true,
});
getCount();
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_adding_blacklist', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
getCount();
}, []);
return (
<div>
<Table
currentPage={page}
t={t}
devices={devices}
loading={loading}
toggleAddBlacklist={toggleAddModal}
toggleEditModal={toggleEditModal}
updateDevicesPerPage={updateDevicesPerPage}
devicesPerPage={devicesPerPage}
pageCount={pageCount}
updatePage={updatePageCount}
pageRangeDisplayed={5}
refreshDevice={refreshDevice}
removeFromBlacklist={removeFromBlacklist}
/>
{showAddModal ? (
<AddToBlacklistModal show={showAddModal} toggle={toggleAddModal} refresh={getCount} />
) : null}
<EditBlacklistModal
show={showEditModal}
toggle={toggleEditModal}
refresh={getCount}
serialNumber={editSerial}
/>
</div>
);
};
export default BlacklistTable;

View File

@@ -5,63 +5,47 @@ import {
CModalTitle, CModalTitle,
CModalBody, CModalBody,
CModalFooter, CModalFooter,
CSwitch,
CCol, CCol,
CRow,
CFormGroup, CFormGroup,
CInputRadio, CInputRadio,
CLabel, CLabel,
CPopover, CPopover,
CRow,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { cilX } from '@coreui/icons'; import { cilX } from '@coreui/icons';
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 PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs'; import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
const BlinkModal = ({ show, toggleModal }) => { const BlinkModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice(); const { deviceSerialNumber } = useDevice();
const [isNow, setIsNow] = useState(false); const { addToast } = useToast();
const [waiting, setWaiting] = useState(false); const [waiting, setWaiting] = useState(false);
const [chosenDate, setChosenDate] = useState(new Date().toString()); const [chosenPattern, setPattern] = useState('blink');
const [chosenPattern, setPattern] = useState('on');
const [result, setResult] = useState(null); const [result, setResult] = useState(null);
const toggleNow = () => {
setIsNow(!isNow);
};
const setDate = (date) => {
if (date) {
setChosenDate(date.toString());
}
};
useEffect(() => { useEffect(() => {
if (show) { if (show) {
setWaiting(false); setWaiting(false);
setChosenDate(new Date().toString()); setPattern('blink');
setPattern('on');
setResult(null); setResult(null);
} }
}, [show]); }, [show]);
const doAction = () => { const doAction = () => {
setWaiting(true); setWaiting(true);
const utcDate = new Date(chosenDate);
const parameters = { const parameters = {
serialNumber: deviceSerialNumber, serialNumber: deviceSerialNumber,
when: isNow ? 0 : dateToUnix(utcDate), when: 0,
pattern: chosenPattern, pattern: chosenPattern,
duration: 30, duration: 30,
}; };
@@ -78,7 +62,13 @@ const BlinkModal = ({ show, toggleModal }) => {
{ headers }, { headers },
) )
.then(() => { .then(() => {
setResult('success'); addToast({
title: t('common.success'),
body: t('commands.command_success'),
color: 'success',
autohide: true,
});
toggleModal();
}) })
.catch(() => { .catch(() => {
setResult('error'); setResult('error');
@@ -106,11 +96,26 @@ const BlinkModal = ({ show, toggleModal }) => {
) : ( ) : (
<div> <div>
<CModalBody> <CModalBody>
<CFormGroup row> <CRow className="mb-3">
<CCol>{t('blink.explanation')}</CCol>
</CRow>
<CFormGroup row className="mb-0">
<CCol md="3"> <CCol md="3">
<CLabel>{t('blink.pattern')}</CLabel> <CLabel>{t('blink.pattern')}</CLabel>
</CCol> </CCol>
<CCol> <CCol>
<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>
<CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline> <CFormGroup variant="custom-radio" onClick={() => setPattern('on')} inline>
<CInputRadio <CInputRadio
custom custom
@@ -135,55 +140,12 @@ const BlinkModal = ({ show, toggleModal }) => {
{t('common.off')} {t('common.off')}
</CLabel> </CLabel>
</CFormGroup> </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> </CCol>
</CFormGroup> </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>
</CRow>
<CRow hidden={isNow} className="pt-3">
<CCol md="4" className="pt-2">
<p>{t('common.custom_date')}</p>
</CCol>
<CCol xs="12" md="8">
<DatePicker
selected={new Date(chosenDate)}
includeTime
value={new Date(chosenDate)}
placeholder="Select custom date"
disabled={waiting}
onChange={(date) => setDate(date)}
min={new Date()}
/>
</CCol>
</CRow>
</CModalBody> </CModalBody>
<CModalFooter> <CModalFooter>
<LoadingButton <LoadingButton
label={isNow ? t('blink.set_leds') : t('common.schedule')} label={t('blink.set_leds')}
isLoadingLabel={t('common.loading_ellipsis')} isLoadingLabel={t('common.loading_ellipsis')}
isLoading={waiting} isLoading={waiting}
action={doAction} action={doAction}

View File

@@ -0,0 +1,100 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
CRow,
CCol,
CCard,
CCardBody,
CCardHeader,
CLabel,
CPopover,
CSpinner,
CButton,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilSync } from '@coreui/icons';
import { useTranslation } from 'react-i18next';
import { CopyToClipboardButton, useAuth, useToast, FormattedDate } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
const CapabilitiesDisplay = ({ serialNumber }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [capabilities, setCapabilities] = useState({});
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const getCapabilities = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(serialNumber)}/capabilities`,
options,
)
.then((response) => {
setCapabilities(response.data);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
getCapabilities();
}, []);
return (
<CCard className="m-0">
<CCardHeader className="dark-header">
<div className="d-flex flex-row-reverse align-items-center">
<div className="text-right">
<CPopover content={t('common.refresh')}>
<CButton size="sm" color="info" onClick={getCapabilities}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody>
<h5>
{t('device.capabilities')}
<CopyToClipboardButton
t={t}
size="sm"
content={JSON.stringify(capabilities?.capabilities ?? {})}
/>
</h5>
<CRow>
<CCol>
<CLabel>
{t('inventory.last_modification')}: <FormattedDate date={capabilities?.lastUpdate} />
</CLabel>
</CCol>
</CRow>
{loading ? <CSpinner /> : null}
<pre className="ignore">{JSON.stringify(capabilities?.capabilities ?? {}, null, 4)}</pre>
</CCardBody>
</CCard>
);
};
CapabilitiesDisplay.propTypes = {
serialNumber: PropTypes.string.isRequired,
};
export default CapabilitiesDisplay;

View File

@@ -17,7 +17,7 @@ const DetailsModal = ({ t, show, toggle, details, commandUuid }) => (
</div> </div>
</CModalHeader> </CModalHeader>
<CModalBody> <CModalBody>
<pre className="ignore">{JSON.stringify(details, null, 4)}</pre> <pre className="ignore">{JSON.stringify(details, null, 2)}</pre>
</CModalBody> </CModalBody>
</CModal> </CModal>
); );

View File

@@ -2,23 +2,23 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
CWidgetDropdown, CCardHeader,
CRow, CCardBody,
CCol,
CButton, CButton,
CDataTable, CDataTable,
CCard, CCard,
CPopover, CPopover,
CButtonToolbar, CButtonToolbar,
CFormText,
} 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, cilCalendarCheck } from '@coreui/icons'; import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
import { prettyDate, dateToUnix } from 'utils/helper'; import { dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import ConfirmModal from 'components/ConfirmModal'; import ConfirmModal from 'components/ConfirmModal';
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs'; import { LoadingButton, useAuth, useDevice, FormattedDate } from 'ucentral-libs';
import WifiScanResultModalWidget from 'components/WifiScanResultModal'; import WifiScanResultModalWidget from 'components/WifiScanResultModal';
import DetailsModal from './DetailsModal'; import DetailsModal from './DetailsModal';
@@ -41,7 +41,9 @@ const DeviceCommands = () => {
const [commands, setCommands] = useState([]); const [commands, setCommands] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [start, setStart] = useState(''); const [start, setStart] = useState('');
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState(''); const [end, setEnd] = useState('');
const [endError, setEndError] = useState(false);
const [commandLimit, setCommandLimit] = useState(25); const [commandLimit, setCommandLimit] = useState(25);
// Load more button related // Load more button related
const [loadingMore, setLoadingMore] = useState(false); const [loadingMore, setLoadingMore] = useState(false);
@@ -65,11 +67,25 @@ const DeviceCommands = () => {
}; };
const modifyStart = (value) => { const modifyStart = (value) => {
setStart(value); try {
new Date(value).toISOString();
setStartError(false);
setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
}; };
const modifyEnd = (value) => { const modifyEnd = (value) => {
setEnd(value); try {
new Date(value).toISOString();
setEndError(false);
setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
}; };
const deleteCommandFromList = (commandUuid) => { const deleteCommandFromList = (commandUuid) => {
@@ -188,9 +204,11 @@ const DeviceCommands = () => {
}; };
const columns = [ const columns = [
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '20%' } },
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } }, { key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
{ key: 'errorCode', label: t('common.error_code'), filter: false, _style: { width: '8%' } },
{ {
key: 'show_buttons', key: 'show_buttons',
label: '', label: '',
@@ -243,140 +261,162 @@ const DeviceCommands = () => {
return ( return (
<div> <div>
<CWidgetDropdown <CCard className="m-0">
inverse="true" <CCardHeader className="dark-header">
color="gradient-primary" <div className="d-flex flex-row-reverse align-items-center">
header={t('commands.title')} <div className="pl-2">
footerSlot={ <CPopover content={t('common.refresh')}>
<div className="pb-1 px-3"> <CButton
<CRow className="mb-2"> size="sm"
<CCol> color="info"
From: onClick={getCommands}
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> disabled={startError || endError}
</CCol> >
<CCol> <CIcon content={cilSync} />
To: </CButton>
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> </CPopover>
</CCol> </div>
</CRow> <div className="pl-2">
<CCard> <DatePicker
<div className="overflow-auto" style={{ height: '200px' }}> includeTime
<CDataTable onChange={(date) => modifyEnd(date)}
border value={end ? new Date(end) : undefined}
loading={loading} />
items={commands ?? []} <CFormText color="danger" hidden={!endError}>
fields={columns} {t('common.invalid_date_explanation')}
className="text-white" </CFormText>
sorterValue={{ column: 'created', desc: 'true' }} </div>
scopedSlots={{ To:
completed: (item) => ( <div className="pl-2">
<td> <DatePicker
{item.completed && item.completed !== 0 includeTime
? prettyDate(item.completed) onChange={(date) => modifyStart(date)}
: 'Pending'} value={start ? new Date(start) : undefined}
</td> />
), <CFormText color="danger" hidden={!startError}>
submitted: (item) => ( {t('common.invalid_date_explanation')}
<td> </CFormText>
{item.submitted && item.submitted !== '' </div>
? prettyDate(item.submitted) From:
: 'Pending'}
</td>
),
show_buttons: (item, index) => (
<td>
<CButtonToolbar
role="group"
className="justify-content-flex-end"
style={{ width: '170px' }}
>
<CPopover
content={
item.command === 'trace' ? t('common.download') : t('common.result')
}
>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleDetails(item);
}}
>
{item.command === 'trace' ? (
<CIcon
name="cil-cloud-download"
content={cilCloudDownload}
size="lg"
/>
) : (
<CIcon
name="cil-calendar-check"
content={cilCalendarCheck}
size="lg"
/>
)}
</CButton>
</CPopover>
<CPopover content={t('common.details')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleResponse(item);
}}
>
<CIcon name="cilList" size="lg" />
</CButton>
</CPopover>
<CPopover content={t('common.delete')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleConfirmModal(item.UUID, index);
}}
>
<CIcon name="cilTrash" size="lg" />
</CButton>
</CPopover>
</CButtonToolbar>
</td>
),
}}
/>
{showLoadingMore && (
<div className="mb-3">
<LoadingButton
label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')}
isLoading={loadingMore}
action={showMoreCommands}
variant="outline"
/>
</div>
)}
</div>
</CCard>
</div> </div>
} </CCardHeader>
> <CCardBody className="p-1">
<div className="text-right float-right"> <div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
<CButton onClick={refreshCommands} size="sm"> <CDataTable
<CIcon name="cil-sync" content={cilSync} className="text-white" size="2xl" /> addTableClasses="ignore-overflow table-sm"
</CButton> border
</div> loading={loading}
</CWidgetDropdown> items={commands ?? []}
fields={columns}
className="text-white"
sorterValue={{ column: 'created', desc: 'true' }}
scopedSlots={{
command: (item) => <td className="align-middle">{item.command}</td>,
completed: (item) => (
<td className="align-middle">
{item.completed && item.completed !== 0 ? (
<FormattedDate date={item.completed} />
) : (
'Pending'
)}
</td>
),
executed: (item) => (
<td className="align-middle">
{item.executed && item.executed !== 0 ? (
<FormattedDate date={item.executed} />
) : (
'Pending'
)}
</td>
),
submitted: (item) => (
<td className="align-middle">
{item.submitted && item.submitted !== '' ? (
<FormattedDate date={item.submitted} />
) : (
'Pending'
)}
</td>
),
errorCode: (item) => <td className="align-middle">{item.errorCode}</td>,
show_buttons: (item, index) => (
<td className="align-middle">
<CButtonToolbar
role="group"
className="justify-content-flex-end"
style={{ width: '160px' }}
>
<CPopover
content={
item.command === 'trace' ? t('common.download') : t('common.result')
}
>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleDetails(item);
}}
>
{item.command === 'trace' ? (
<CIcon name="cil-cloud-download" content={cilCloudDownload} />
) : (
<CIcon name="cil-calendar-check" content={cilCalendarCheck} />
)}
</CButton>
</CPopover>
<CPopover content={t('common.details')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleResponse(item);
}}
>
<CIcon name="cilList" />
</CButton>
</CPopover>
<CPopover content={t('common.delete')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
onClick={() => {
toggleConfirmModal(item.UUID, index);
}}
>
<CIcon name="cilTrash" />
</CButton>
</CPopover>
</CButtonToolbar>
</td>
),
}}
/>
{showLoadingMore && (
<div className="mb-3">
<LoadingButton
label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')}
isLoading={loadingMore}
action={showMoreCommands}
variant="outline"
/>
</div>
)}
</div>
</CCardBody>
</CCard>
<WifiScanResultModalWidget <WifiScanResultModalWidget
show={showScanModal} show={showScanModal}
toggle={toggleScanModal} toggle={toggleScanModal}

View File

@@ -0,0 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CRow,
CCol,
CCard,
CCardBody,
CCardHeader,
CLabel,
CPopover,
CButton,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilSync } from '@coreui/icons';
import { prettyDate } from 'utils/helper';
import { useTranslation } from 'react-i18next';
import { CopyToClipboardButton } from 'ucentral-libs';
const ConfigurationDisplay = ({ getData, deviceConfig }) => {
const { t } = useTranslation();
return (
<CCard className="m-0">
<CCardHeader className="dark-header">
<div className="d-flex flex-row-reverse align-items-center">
<div className="text-right">
<CPopover content={t('common.refresh')}>
<CButton size="sm" color="info" onClick={getData}>
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody>
<h5>
{t('configuration.title')}
<CopyToClipboardButton
t={t}
size="sm"
content={JSON.stringify(deviceConfig?.configuration ?? {})}
/>
</h5>
<CRow>
<CCol>
<CLabel>
{t('configuration.last_configuration_change')}:{' '}
{prettyDate(deviceConfig?.lastConfigurationChange)}
</CLabel>
</CCol>
</CRow>
<pre className="ignore">{JSON.stringify(deviceConfig?.configuration ?? {}, null, 4)}</pre>
</CCardBody>
</CCard>
);
};
ConfigurationDisplay.propTypes = {
getData: PropTypes.func.isRequired,
deviceConfig: PropTypes.instanceOf(Object),
};
ConfigurationDisplay.defaultProps = {
deviceConfig: null,
};
export default ConfigurationDisplay;

View File

@@ -20,7 +20,7 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import { useAuth, useDevice } from 'ucentral-libs'; import { useAuth, useDevice, useToast } from 'ucentral-libs';
import { 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';
@@ -29,6 +29,7 @@ import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
const ConfigureModal = ({ show, toggleModal }) => { const ConfigureModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const { deviceSerialNumber } = useDevice(); const { deviceSerialNumber } = useDevice();
const [hadSuccess, setHadSuccess] = useState(false); const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false); const [hadFailure, setHadFailure] = useState(false);
@@ -91,7 +92,13 @@ const ConfigureModal = ({ show, toggleModal }) => {
{ headers }, { headers },
) )
.then(() => { .then(() => {
setHadSuccess(true); addToast({
title: t('common.success'),
body: t('commands.command_success'),
color: 'success',
autohide: true,
});
toggleModal();
}) })
.catch(() => { .catch(() => {
setResponseBody('Error while submitting command!'); setResponseBody('Error while submitting command!');

View File

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

View File

@@ -0,0 +1,88 @@
import React, { useState } from 'react';
import ReactTooltip from 'react-tooltip';
import { v4 as createUuid } from 'uuid';
import { CButton, CCardBody, CCardHeader, CRow, CCol, CPopover, CButtonClose } from '@coreui/react';
import { cilTrash } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import { LoadingButton } from 'ucentral-libs';
import PropTypes from 'prop-types';
import styles from './index.module.scss';
const DeleteButton = ({ t, config, deleteConfig, hideTooltips }) => {
const [tooltipId] = useState(createUuid());
return (
<CPopover content={t('common.delete')}>
<div className="d-inline">
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-2"
data-tip
data-for={tooltipId}
data-event="click"
style={{ width: '33px', height: '30px' }}
>
<CIcon name="cil-trash" content={cilTrash} size="sm" />
</CButton>
<ReactTooltip
id={tooltipId}
place="top"
effect="solid"
globalEventOff="click"
clickable
className={[styles.deleteTooltip, 'tooltipRight'].join(' ')}
border
borderColor="#321fdb"
arrowColor="white"
overridePosition={({ left, top }) => {
const element = document.getElementById(tooltipId);
const tooltipWidth = element ? element.offsetWidth : 0;
const newLeft = left - tooltipWidth * 0.25;
return { top, left: newLeft };
}}
>
<CCardHeader color="primary" className={styles.tooltipHeader}>
{t('configuration.delete_config')}
<CButtonClose
style={{ color: 'white' }}
onClick={(e) => {
e.target.parentNode.parentNode.classList.remove('show');
hideTooltips();
}}
/>
</CCardHeader>
<CCardBody className="py-1 px-4">
<CRow>
<CCol>
<LoadingButton
data-toggle="dropdown"
variant="outline"
color="danger"
label={t('common.confirm')}
isLoadingLabel={t('user.deleting')}
isLoading={false}
action={() => deleteConfig(config.name)}
block
disabled={false}
/>
</CCol>
</CRow>
</CCardBody>
</ReactTooltip>
</div>
</CPopover>
);
};
DeleteButton.propTypes = {
t: PropTypes.func.isRequired,
config: PropTypes.instanceOf(Object).isRequired,
deleteConfig: PropTypes.func.isRequired,
hideTooltips: PropTypes.func.isRequired,
};
export default DeleteButton;

View File

@@ -0,0 +1,184 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate';
import {
CCardBody,
CDataTable,
CButton,
CCard,
CCardHeader,
CPopover,
CSelect,
CButtonToolbar,
} from '@coreui/react';
import { cilPencil, cilPlus } from '@coreui/icons';
import ReactTooltip from 'react-tooltip';
import CIcon from '@coreui/icons-react';
import { FormattedDate } from 'ucentral-libs';
import DeleteButton from './DeleteButton';
const DefaultConfigurationTable = ({
currentPage,
configurations,
toggleAddBlacklist,
toggleEditModal,
configurationsPerPage,
loading,
deleteConfig,
updateDevicesPerPage,
pageCount,
updatePage,
t,
}) => {
const columns = [
{ key: 'name', label: t('user.name'), _style: { width: '20%' } },
{ key: 'description', label: t('user.description'), _style: { width: '20%' } },
{ key: 'created', label: t('common.created'), _style: { width: '10%' } },
{ key: 'modified', label: t('common.modified'), _style: { width: '10%' } },
{ key: 'deviceTypes', label: t('firmware.device_types'), _style: { width: '20%' } },
{ key: 'actions', label: t('actions.actions'), _style: { width: '1%' } },
];
const hideTooltips = () => ReactTooltip.hide();
const escFunction = (event) => {
if (event.keyCode === 27) {
hideTooltips();
}
};
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', escFunction, false);
};
}, []);
return (
<>
<CCard className="m-0 p-0">
<CCardHeader className="dark-header text-right">
<div className="text-value-lg float-left">
{t('configuration.default_configurations')}
</div>
<div className="text-right float-right">
<CPopover content={t('configuration.create_config')}>
<CButton size="sm" color="info" onClick={toggleAddBlacklist}>
<CIcon content={cilPlus} />
</CButton>
</CPopover>
</div>
</CCardHeader>
<CCardBody className="p-0">
<CDataTable
addTableClasses="ignore-overflow table-sm"
items={configurations ?? []}
fields={columns}
hover
border
loading={loading}
scopedSlots={{
name: (item) => <td className="align-middle">{item.name}</td>,
description: (item) => <td className="align-middle">{item.description}</td>,
deviceTypes: (item) => <td className="align-middle">{item.modelIds.join(', ')}</td>,
created: (item) => (
<td className="align-middle">
<FormattedDate date={item.created} />
</td>
),
modified: (item) => (
<td className="align-middle">
<FormattedDate date={item.lastModified} />
</td>
),
actions: (item) => (
<td className="text-center align-middle">
<CButtonToolbar
role="group"
className="justify-content-center"
style={{ width: '90px' }}
>
<DeleteButton
t={t}
config={item}
deleteConfig={deleteConfig}
hideTooltips={hideTooltips}
/>
<CPopover content={t('common.edit')}>
<CButton
onClick={() => toggleEditModal(item.name)}
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-1"
style={{ width: '33px', height: '30px' }}
>
<CIcon content={cilPencil} size="sm" />
</CButton>
</CPopover>
</CButtonToolbar>
</td>
),
}}
/>
<div className="d-flex flex-row pl-3">
<div className="pr-3">
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={updatePage}
forcePage={Number(currentPage)}
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"
/>
</div>
<p className="pr-2 mt-1">{t('common.items_per_page')}</p>
<div style={{ width: '100px' }} className="px-2">
<CSelect
custom
defaultValue={configurationsPerPage}
onChange={(e) => updateDevicesPerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</div>
</CCardBody>
</CCard>
</>
);
};
DefaultConfigurationTable.propTypes = {
currentPage: PropTypes.string,
configurations: PropTypes.instanceOf(Array).isRequired,
toggleAddBlacklist: PropTypes.func.isRequired,
toggleEditModal: PropTypes.func.isRequired,
updateDevicesPerPage: PropTypes.func.isRequired,
pageCount: PropTypes.number.isRequired,
updatePage: PropTypes.func.isRequired,
configurationsPerPage: PropTypes.string.isRequired,
deleteConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};
DefaultConfigurationTable.defaultProps = {
currentPage: '0',
};
export default React.memo(DefaultConfigurationTable);

View File

@@ -0,0 +1,32 @@
.firmwareTooltip {
opacity: 1 !important;
padding: 0px 0px 0px 0px !important;
border-radius: 1rem !important;
background-color: #fff !important;
border-color: #321fdb !important;
font-size: 0.875rem !important;
font-weight: 400 !important;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
width: 400px;
}
.deleteTooltip {
opacity: 1 !important;
padding: 0px 0px 0px 0px !important;
border-radius: 1rem !important;
background-color: #fff !important;
border-color: #321fdb !important;
font-size: 0.875rem !important;
font-weight: 400 !important;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
width: 150px;
}
.tooltipHeader {
padding-left: 5px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
border-top-left-radius: 1rem !important;
border-top-right-radius: 1rem !important;
}

View File

@@ -0,0 +1,199 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import axiosInstance from 'utils/axiosInstance';
import { getItem, setItem } from 'utils/localStorageHelper';
import { useAuth, useToast, useToggle } from 'ucentral-libs';
import AddConfigurationModal from 'components/AddConfigurationModal';
import EditConfigurationModal from 'components/EditConfigurationModal';
import Table from './Table';
const DefaultConfigurationTable = () => {
const { t } = useTranslation();
const { addToast } = useToast();
const history = useHistory();
const [page, setPage] = useState(parseInt(sessionStorage.getItem('configurationTable') ?? 0, 10));
const { currentToken, endpoints } = useAuth();
const [configurationCount, setConfigurationCount] = useState(0);
const [pageCount, setPageCount] = useState(0);
const [configurationsPerPage, setConfigurationsPerPage] = useState(
getItem('configurationsPerPage') || '10',
);
const [configurations, setConfigurations] = useState([]);
const [loading, setLoading] = useState(true);
const [editId, setEditId] = useState('');
const [showEditModal, setShowEditModal] = useState(false);
const [showAddModal, toggleAddModal] = useToggle(false);
const toggleEditModal = (serialNumber) => {
if (serialNumber) setEditId(serialNumber);
setShowEditModal(!showEditModal);
};
const getConfigurationInformation = (
selectedPage = page,
configurationPerPage = configurationsPerPage,
) => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(
`${endpoints.owgw}/api/v1/default_configurations?limit=${configurationPerPage}&offset=${
configurationPerPage * selectedPage
}`,
options,
)
.then((response) => {
setConfigurations(response.data.configurations);
setLoading(false);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('configuration.error_fetching_configurations', {
error: e.response?.data?.ErrorDescription,
}),
color: 'danger',
autohide: true,
});
setLoading(false);
});
};
const getCount = () => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/default_configurations?countOnly=true`, {
headers,
})
.then((response) => {
const configurationsCount = response.data.count;
const pagesCount = Math.ceil(configurationsCount / configurationsPerPage);
setPageCount(pagesCount);
setConfigurationCount(configurationsCount);
let selectedPage = page;
if (page >= pagesCount) {
history.push(`/defaultconfigurations?page=${pagesCount - 1}`);
selectedPage = pagesCount - 1;
}
getConfigurationInformation(selectedPage);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('configuration.error_fetching_configurations', {
error: e.response?.data?.ErrorDescription,
}),
color: 'danger',
autohide: true,
});
setLoading(false);
});
};
const updateConfigurationsPerPage = (value) => {
setItem('configurationsPerPage', value);
setConfigurationsPerPage(value);
const newPageCount = Math.ceil(configurationCount / value);
setPageCount(newPageCount);
let selectedPage = page;
if (page >= newPageCount) {
history.push(`/default_configurations?page=${newPageCount - 1}`);
selectedPage = newPageCount - 1;
}
getConfigurationInformation(selectedPage, value);
};
const updatePageCount = ({ selected: selectedPage }) => {
sessionStorage.setItem('configurationTable', selectedPage);
setPage(selectedPage);
getConfigurationInformation(selectedPage);
};
const deleteConfig = (name) => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.delete(`${endpoints.owgw}/api/v1/default_configuration/${name}`, { headers })
.then(() => {
addToast({
title: t('common.success'),
body: t('configuration.successful_delete'),
color: 'success',
autohide: true,
});
getCount();
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('configuration.error_adding_blacklist', {
error: e.response?.data?.ErrorDescription,
}),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
getCount();
}, []);
return (
<div>
<Table
currentPage={page}
t={t}
configurations={configurations}
loading={loading}
toggleAddBlacklist={toggleAddModal}
toggleEditModal={toggleEditModal}
updateConfigurationsPerPage={updateConfigurationsPerPage}
configurationsPerPage={configurationsPerPage}
pageCount={pageCount}
updatePage={updatePageCount}
pageRangeDisplayed={5}
deleteConfig={deleteConfig}
/>
{showAddModal ? (
<AddConfigurationModal show={showAddModal} toggle={toggleAddModal} refresh={getCount} />
) : null}
<EditConfigurationModal
show={showEditModal}
toggle={toggleEditModal}
refresh={getCount}
configId={editId}
/>
</div>
);
};
export default DefaultConfigurationTable;

View File

@@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
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 { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs'; import { LoadingButton, useAuth, useDevice, useToast, useToggle } from 'ucentral-libs';
import RebootModal from 'components/RebootModal'; import RebootModal from 'components/RebootModal';
import DeviceFirmwareModal from 'components/DeviceFirmwareModal'; import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
import ConfigureModal from 'components/ConfigureModal'; import ConfigureModal from 'components/ConfigureModal';
@@ -13,7 +14,7 @@ import FactoryResetModal from 'components/FactoryResetModal';
import EventQueueModal from 'components/EventQueueModal'; import EventQueueModal from 'components/EventQueueModal';
import TelemetryModal from 'components/TelemetryModal'; import TelemetryModal from 'components/TelemetryModal';
const DeviceActions = () => { const DeviceActions = ({ device }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { addToast } = useToast(); const { addToast } = useToast();
@@ -21,35 +22,16 @@ const DeviceActions = () => {
const [upgradeStatus, setUpgradeStatus] = useState({ const [upgradeStatus, setUpgradeStatus] = useState({
loading: false, loading: false,
}); });
const [device, setDevice] = useState({});
const [showRebootModal, setShowRebootModal] = useState(false);
const [showBlinkModal, setShowBlinkModal] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showTraceModal, setShowTraceModal] = useState(false);
const [showScanModal, setShowScanModal] = useState(false);
const [connectLoading, setConnectLoading] = useState(false); const [connectLoading, setConnectLoading] = useState(false);
const [showConfigModal, setConfigModal] = useState(false); const [showRebootModal, toggleRebootModal] = useToggle(false);
const [showFactoryModal, setShowFactoryModal] = useState(false); const [showBlinkModal, toggleBlinkModal] = useToggle(false);
const [showQueueModal, setShowQueueModal] = useState(false); const [showUpgradeModal, toggleUpgradeModal, setShowUpgradeModal] = useToggle(false);
const [showTelemetryModal, setShowTelemetryModal] = useState(false); const [showTraceModal, toggleTraceModal] = useToggle(false);
const [showScanModal, toggleScanModal] = useToggle(false);
const toggleRebootModal = () => setShowRebootModal(!showRebootModal); const [showConfigModal, toggleConfigModal] = useToggle(false);
const [showFactoryModal, toggleFactoryResetModal] = useToggle(false);
const toggleBlinkModal = () => setShowBlinkModal(!showBlinkModal); const [showQueueModal, toggleQueueModal] = useToggle(false);
const [showTelemetryModal, toggleTelemetryModal] = useToggle(false);
const toggleUpgradeModal = () => setShowUpgradeModal(!showUpgradeModal);
const toggleTraceModal = () => setShowTraceModal(!showTraceModal);
const toggleScanModal = () => setShowScanModal(!showScanModal);
const toggleConfigModal = () => setConfigModal(!showConfigModal);
const toggleFactoryResetModal = () => setShowFactoryModal(!showFactoryModal);
const toggleQueueModal = () => setShowQueueModal(!showQueueModal);
const toggleTelemetryModal = () => setShowTelemetryModal(!showTelemetryModal);
const getRttysInfo = () => { const getRttysInfo = () => {
setConnectLoading(true); setConnectLoading(true);
@@ -67,6 +49,7 @@ const DeviceActions = () => {
) )
.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');
if (newWindow) newWindow.opener = null; if (newWindow) newWindow.opener = null;
}) })
@@ -83,22 +66,6 @@ const DeviceActions = () => {
}); });
}; };
const getDeviceInformation = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}`, options)
.then((response) => {
setDevice(response.data);
})
.catch(() => {});
};
useEffect(() => { useEffect(() => {
if (upgradeStatus.result !== undefined) { if (upgradeStatus.result !== undefined) {
addToast({ addToast({
@@ -116,55 +83,57 @@ const DeviceActions = () => {
} }
}, [upgradeStatus]); }, [upgradeStatus]);
useEffect(() => {
getDeviceInformation();
}, [deviceSerialNumber]);
return ( return (
<CCard> <CCard>
<CCardHeader> <CCardHeader className="dark-header">
<div className="text-value-lg">{t('actions.title')}</div> <div className="text-value-lg">{t('actions.title')}</div>
</CCardHeader> </CCardHeader>
<CCardBody> <CCardBody>
<CRow> <CRow>
<CCol> <CCol>
<CButton block onClick={toggleRebootModal} color="primary"> <CButton block disabled={device === null} onClick={toggleRebootModal} color="primary">
{t('actions.reboot')} {t('actions.reboot')}
</CButton> </CButton>
</CCol> </CCol>
<CCol> <CCol>
<CButton block onClick={toggleBlinkModal} color="primary"> <CButton block disabled={device === null} onClick={toggleBlinkModal} color="primary">
{t('actions.blink')} {t('actions.blink')}
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className="mt-4"> <CRow className="my-1">
<CCol> <CCol>
<CButton block color="primary" onClick={toggleUpgradeModal}> <CButton block disabled={device === null} color="primary" onClick={toggleUpgradeModal}>
{t('actions.firmware_upgrade')} {t('actions.firmware_upgrade')}
</CButton> </CButton>
</CCol> </CCol>
<CCol> <CCol>
<CButton block color="primary" onClick={toggleTraceModal}> <CButton block disabled={device === null} color="primary" onClick={toggleTraceModal}>
{t('actions.trace')} {t('actions.trace')}
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className="mt-4"> <CRow className="my-1">
<CCol> <CCol>
<CButton block color="primary" onClick={toggleScanModal}> <CButton block disabled={device === null} color="primary" onClick={toggleScanModal}>
{t('actions.wifi_scan')} {t('actions.wifi_scan')}
</CButton> </CButton>
</CCol> </CCol>
<CCol> <CCol>
<CButton block color="primary" onClick={toggleFactoryResetModal}> <CButton
block
disabled={device === null}
color="primary"
onClick={toggleFactoryResetModal}
>
{t('actions.factory_reset')} {t('actions.factory_reset')}
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className="mt-4"> <CRow className="my-1">
<CCol> <CCol>
<LoadingButton <LoadingButton
disabled={device === null}
isLoading={connectLoading} isLoading={connectLoading}
label={t('actions.connect')} label={t('actions.connect')}
isLoadingLabel={t('actions.connecting')} isLoadingLabel={t('actions.connecting')}
@@ -172,19 +141,24 @@ const DeviceActions = () => {
/> />
</CCol> </CCol>
<CCol> <CCol>
<CButton block color="primary" onClick={toggleConfigModal}> <CButton block disabled={device === null} color="primary" onClick={toggleConfigModal}>
{t('actions.configure')} {t('actions.configure')}
</CButton> </CButton>
</CCol> </CCol>
</CRow> </CRow>
<CRow className="mt-4"> <CRow className="my-1">
<CCol> <CCol>
<CButton block color="primary" onClick={toggleQueueModal}> <CButton block disabled={device === null} color="primary" onClick={toggleQueueModal}>
{t('commands.event_queue')} {t('commands.event_queue')}
</CButton> </CButton>
</CCol> </CCol>
<CCol> <CCol>
<CButton block color="primary" onClick={toggleTelemetryModal}> <CButton
block
disabled={device === null}
color="primary"
onClick={toggleTelemetryModal}
>
{t('actions.telemetry')} {t('actions.telemetry')}
</CButton> </CButton>
</CCol> </CCol>
@@ -212,4 +186,12 @@ const DeviceActions = () => {
); );
}; };
DeviceActions.propTypes = {
device: PropTypes.instanceOf(Object),
};
DeviceActions.defaultProps = {
device: null,
};
export default DeviceActions; export default DeviceActions;

View File

@@ -19,7 +19,7 @@ const DeviceConfigurationModal = ({ show, toggle, configuration }) => (
<CModalTitle className="text-dark">{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, 2)}</pre>
</CModalBody> </CModalBody>
<CModalFooter> <CModalFooter>
<CButton color="secondary" onClick={toggle}> <CButton color="secondary" onClick={toggle}>

View File

@@ -0,0 +1,418 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CCard,
CCardBody,
CCardHeader,
CCol,
CPopover,
CRow,
CSpinner,
CWidgetIcon,
} from '@coreui/react';
import { CChartBar, CChartHorizontalBar, CChartPie } from '@coreui/react-chartjs';
import { cilClock, cilInfo, cilMedicalCross, cilThumbUp, cilWarning } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import { FormattedDate } from 'ucentral-libs';
import styles from './index.module.scss';
const getColor = (health) => {
const numberHealth = health ? Number(health.replace('%', '')) : 0;
if (numberHealth >= 90) return 'success';
if (numberHealth >= 60) return 'warning';
return 'danger';
};
const getIcon = (health) => {
const numberHealth = health ? Number(health.replace('%', '')) : 0;
if (numberHealth >= 90) return <CIcon width={36} name="cil-thumbs-up" content={cilThumbUp} />;
if (numberHealth >= 60) return <CIcon width={36} name="cil-warning" content={cilWarning} />;
return <CIcon width={36} name="cil-medical-cross" content={cilMedicalCross} />;
};
const DeviceDashboard = ({ t, data, loading }) => (
<div style={{ position: 'relative' }}>
<div style={{ opacity: loading ? '20%' : '100%' }}>
<CRow className="mt-3">
<CCol>
<CWidgetIcon
text={t('common.last_dashboard_refresh')}
header={data.snapshot ? <FormattedDate date={data.snapshot} size="lg" /> : <h2>-</h2>}
color="info"
iconPadding={false}
>
<CIcon width={36} name="cil-clock" content={cilClock} />
</CWidgetIcon>
</CCol>
<CCol>
<CWidgetIcon
text={
<div>
<div className="float-left">{t('common.overall_health')}</div>
<div className="float-left ml-2">
<CPopover content={t('device.health_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
}
header={<h2>{data.overallHealth}</h2>}
color={getColor(data.overallHealth)}
iconPadding={false}
>
{getIcon(data.overallHealth)}
</CWidgetIcon>
</CCol>
<CCol>
<CWidgetIcon
text={t('common.devices')}
header={<h2>{data.numberOfDevices}</h2>}
color="primary"
iconPadding={false}
>
<CIcon width={36} name="cil-router" />
</CWidgetIcon>
</CCol>
</CRow>
<CRow>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">{t('common.device_status')}</CCardHeader>
<CCardBody className="p-1">
<CChartPie
datasets={data.status.datasets}
labels={data.status.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) => `${ds.datasets[0].data[item.index]}%`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">
<div>
<div className="float-left">{t('common.device_health')}</div>
<div className="float-left ml-2">
<CPopover content={t('device.health_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody className="p-1">
<CChartPie
datasets={data.healths.datasets}
labels={data.healths.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]}${t('common.of_connected')}`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">
{data.totalAssociations}{' '}
{data.totalAssociations === 1
? t('wifi_analysis.association')
: t('wifi_analysis.associations')}
</CCardHeader>
<CCardBody className="p-1">
<CChartPie
datasets={data.associations.datasets}
labels={data.associations.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]}% of ${
data.totalAssociations
} associations`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">{t('common.vendors')}</CCardHeader>
<CCardBody className="p-1">
<CChartHorizontalBar
datasets={data.vendors.datasets}
labels={data.vendors.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]} ${t('common.devices')}`,
},
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
xAxes: [
{
ticks: {
maxTicksLimit: 5,
beginAtZero: true,
stepSize: 1,
},
},
],
yAxes: [
{
ticks: {
callback: (value) => value.split(' ')[0],
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">{t('firmware.device_types')}</CCardHeader>
<CCardBody className="p-1">
<CChartPie
datasets={data.deviceType.datasets}
labels={data.deviceType.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]} ${t('common.devices')}`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">
<div>
<div className="float-left">{t('common.uptimes')}</div>
<div className="float-left ml-2">
<CPopover content={t('device.uptimes_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody className="p-1">
<CChartBar
datasets={data.upTimes.datasets}
labels={data.upTimes.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]} ${t('common.devices')}`,
},
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
yAxes: [
{
ticks: {
maxTicksLimit: 5,
beginAtZero: true,
stepSize: 1,
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">
<div>
<div className="float-left">{t('common.certificates')}</div>
<div className="float-left ml-2">
<CPopover content={t('device.certificate_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody className="p-1">
<CChartPie
datasets={data.certificates.datasets}
labels={data.certificates.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]}${t('common.of_connected')}`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">{t('common.commands')}</CCardHeader>
<CCardBody className="p-1">
<CChartBar
datasets={data.commands.datasets}
labels={data.commands.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
yAxes: [
{
ticks: {
maxTicksLimit: 5,
beginAtZero: true,
stepSize: 1,
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol lg="6" xl="4">
<CCard>
<CCardHeader className="dark-header">
<div>
<div className="float-left">{t('common.memory_used')}</div>
<div className="float-left ml-2">
<CPopover content={t('device.memory_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody className="p-1">
<CChartBar
datasets={data.memoryUsed.datasets}
labels={data.memoryUsed.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
yAxes: [
{
ticks: {
maxTicksLimit: 10,
beginAtZero: true,
stepSize: 1,
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
</CRow>
</div>
{loading ? (
<div className={styles.centerContainer}>
<CSpinner className={styles.spinner} />
</div>
) : null}
</div>
);
DeviceDashboard.propTypes = {
t: PropTypes.func.isRequired,
data: PropTypes.instanceOf(Object).isRequired,
loading: PropTypes.bool.isRequired,
};
export default React.memo(DeviceDashboard);

View File

@@ -0,0 +1,10 @@
.centerContainer {
position: absolute;
top: 5%;
right: 50%;
}
.spinner {
height: 50px;
width: 50px;
}

View File

@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { DeviceDashboard as Dashboard, useAuth, COLOR_LIST } from 'ucentral-libs'; import { useAuth, COLOR_LIST } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import Dashboard from './Dashboard';
const DeviceDashboard = () => { const DeviceDashboard = () => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -0,0 +1,145 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
CButton,
CDataTable,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CRow,
CCol,
CInput,
CPopover,
CSwitch,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilX } from '@coreui/icons';
import { LoadingButton } from 'ucentral-libs';
import { cleanBytesString, prettyDate } from 'utils/helper';
const DeviceFirmwareModal = ({
t,
device,
show,
toggle,
firmwareVersions,
upgradeToVersion,
loading,
upgradeStatus,
keepRedirector,
toggleRedirector,
}) => {
const [filter, setFilter] = useState('');
const fields = [
{ key: 'imageDate', label: t('firmware.image_date'), _style: { width: '17%' }, filter: false },
{ key: 'size', label: t('firmware.size'), _style: { width: '8%' }, filter: false },
{ key: 'revision', label: t('firmware.revision'), _style: { width: '60%' } },
{ key: 'show_details', label: '', _style: { width: '15%' }, filter: false },
];
useEffect(() => {
setFilter('');
}, [show]);
return (
<CModal show={show} onClose={toggle} size="xl">
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">#{device?.serialNumber}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody>
{show ? (
<div>
<CRow>
<CCol sm="2" className="pt-2">
{t('firmware.installed_firmware')}
</CCol>
<CCol className="pt-2">{device.firmware}</CCol>
</CRow>
<CRow className="mt-3">
<CCol sm="2" className="pt-2">
{t('factory_reset.redirector')}
</CCol>
<CCol className="pt-2">
<CSwitch
color="primary"
defaultChecked={keepRedirector}
onClick={toggleRedirector}
labelOn="Yes"
labelOff="No"
/>
</CCol>
</CRow>
<CRow className="my-4">
<CCol sm="5">
<CInput
type="text"
placeholder="Search"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</CCol>
<CCol />
</CRow>
<CRow className="mb-4">
<CCol>
<div className="overflow-auto" style={{ height: '600px' }}>
<CDataTable
addTableClasses="table-sm"
items={firmwareVersions}
fields={fields}
loading={loading}
hover
tableFilterValue={filter}
border
scopedSlots={{
imageDate: (item) => <td>{prettyDate(item.imageDate)}</td>,
size: (item) => <td>{cleanBytesString(item.size)}</td>,
show_details: (item) => (
<td className="text-center">
<LoadingButton
label={t('firmware.upgrade')}
isLoadingLabel={t('firmware.upgrading')}
isLoading={false}
action={() => upgradeToVersion(item.uri)}
block={false}
disabled={upgradeStatus.loading}
/>
</td>
),
}}
/>
</div>
</CCol>
</CRow>
</div>
) : (
<div />
)}
</CModalBody>
</CModal>
);
};
DeviceFirmwareModal.propTypes = {
t: PropTypes.func.isRequired,
device: PropTypes.instanceOf(Object).isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
firmwareVersions: PropTypes.instanceOf(Array).isRequired,
upgradeToVersion: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
upgradeStatus: PropTypes.instanceOf(Object).isRequired,
keepRedirector: PropTypes.bool.isRequired,
toggleRedirector: PropTypes.func.isRequired,
};
export default React.memo(DeviceFirmwareModal);

View File

@@ -1,9 +1,10 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { DeviceFirmwareModal as Modal, useAuth, useToast } from 'ucentral-libs'; import { useAuth, useToast, useToggle } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Modal from './Modal';
const DeviceFirmwareModal = ({ const DeviceFirmwareModal = ({
device, device,
@@ -17,6 +18,7 @@ const DeviceFirmwareModal = ({
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [firmwareVersions, setFirmwareVersions] = useState([]); const [firmwareVersions, setFirmwareVersions] = useState([]);
const [keepRedirector, toggleKeepRedirector, setKeepRedirector] = useToggle(true);
const getPartialFirmware = async (offset) => { const getPartialFirmware = async (offset) => {
const headers = { const headers = {
@@ -48,7 +50,7 @@ const DeviceFirmwareModal = ({
const allFirmwares = []; const allFirmwares = [];
let continueFirmware = true; let continueFirmware = true;
let i = 1; let i = 0;
while (continueFirmware) { while (continueFirmware) {
const newFirmwares = await getPartialFirmware(i); const newFirmwares = await getPartialFirmware(i);
if (newFirmwares === null || newFirmwares.length === 0) continueFirmware = false; if (newFirmwares === null || newFirmwares.length === 0) continueFirmware = false;
@@ -78,6 +80,7 @@ const DeviceFirmwareModal = ({
const parameters = { const parameters = {
serialNumber: device.serialNumber, serialNumber: device.serialNumber,
keepRedirector,
when: 0, when: 0,
uri, uri,
}; };
@@ -108,6 +111,7 @@ const DeviceFirmwareModal = ({
useEffect(() => { useEffect(() => {
if (show && device.compatible) getFirmwareList(); if (show && device.compatible) getFirmwareList();
if (show) setKeepRedirector(true);
}, [device, show]); }, [device, show]);
return ( return (
@@ -120,6 +124,8 @@ const DeviceFirmwareModal = ({
upgradeToVersion={upgradeToVersion} upgradeToVersion={upgradeToVersion}
loading={loading} loading={loading}
upgradeStatus={upgradeStatus} upgradeStatus={upgradeStatus}
keepRedirector={keepRedirector}
toggleRedirector={toggleKeepRedirector}
/> />
); );
}; };

View File

@@ -1,36 +1,35 @@
/* eslint-disable-rule prefer-destructuring */ /* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
CWidgetDropdown, CCardBody,
CCollapse,
CButton, CButton,
CDataTable, CDataTable,
CCard, CCardHeader,
CCardBody,
CRow,
CCol,
CProgress,
CPopover, CPopover,
CCard,
CFormText,
CBadge,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { cilTrash } from '@coreui/icons'; import { cilSync, cilTrash } from '@coreui/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import { prettyDate, dateToUnix } from 'utils/helper'; import { dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs'; import { LoadingButton, useAuth, useDevice, FormattedDate } from 'ucentral-libs';
import DeleteLogModal from 'components/DeleteLogModal'; import DeleteLogModal from 'components/DeleteLogModal';
const DeviceHealth = () => { const DeviceHealth = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice(); const { deviceSerialNumber } = useDevice();
const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [healthChecks, setHealthChecks] = useState([]); const [healthChecks, setHealthChecks] = useState([]);
const [start, setStart] = useState(''); const [start, setStart] = useState('');
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState(''); const [end, setEnd] = useState('');
const [endError, setEndError] = useState(false);
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);
@@ -43,13 +42,26 @@ const DeviceHealth = () => {
}; };
const modifyStart = (value) => { const modifyStart = (value) => {
setStart(value); try {
new Date(value).toISOString();
setStartError(false);
setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
}; };
const modifyEnd = (value) => { const modifyEnd = (value) => {
setEnd(value); try {
new Date(value).toISOString();
setEndError(false);
setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
}; };
const showMoreLogs = () => { const showMoreLogs = () => {
setLogLimit(logLimit + 50); setLogLimit(logLimit + 50);
}; };
@@ -95,35 +107,11 @@ const DeviceHealth = () => {
}); });
}; };
const toggleDetails = (index) => {
const position = details.indexOf(index);
let newDetails = details.slice();
if (position !== -1) {
newDetails.splice(position, 1);
} else {
newDetails = [...details, index];
}
setDetails(newDetails);
};
const getDetails = (index, healthCheckDetails) => {
if (details.includes(index))
return <pre className="ignore">{JSON.stringify(healthCheckDetails, null, 4)}</pre>;
return <pre className="ignore" />;
};
const columns = [ const columns = [
{ key: 'UUID', label: t('common.config_id') }, { key: 'recorded', label: t('common.recorded'), _style: { width: '15%' } },
{ key: 'recorded', label: t('common.recorded') }, { key: 'UUID', label: t('common.config_id'), _style: { width: '10%' } },
{ key: 'sanity', label: t('health.sanity') }, { key: 'sanity', label: t('health.sanity'), _style: { width: '5%' } },
{ { key: 'checkDetails', label: t('common.details'), _style: { width: '65%' } },
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false,
},
]; ];
useEffect(() => { useEffect(() => {
@@ -155,14 +143,14 @@ const DeviceHealth = () => {
const tempSanityLevel = sortedHealthchecks[healthChecks.length - 1].sanity; const tempSanityLevel = sortedHealthchecks[healthChecks.length - 1].sanity;
setSanityLevel(tempSanityLevel); setSanityLevel(tempSanityLevel);
if (tempSanityLevel === 100) { if (tempSanityLevel === 100) {
setBarColor('gradient-success'); setBarColor('success');
} else if (tempSanityLevel >= 90) { } else if (tempSanityLevel >= 90) {
setBarColor('gradient-warning'); setBarColor('warning');
} else { } else {
setBarColor('gradient-danger'); setBarColor('danger');
} }
} else { } else {
setBarColor('gradient-dark'); setBarColor('dark');
} }
}, [healthChecks]); }, [healthChecks]);
@@ -183,95 +171,104 @@ const DeviceHealth = () => {
}, []); }, []);
return ( return (
<CWidgetDropdown <CCard className="m-0">
header={t('health.title')} <CCardHeader className="dark-header">
text={sanityLevel ? `${sanityLevel}%` : t('common.unknown')} <div className="float-left align-middle pt-1">
value={sanityLevel ?? 100} <h4>
color={barColor} <CBadge color={barColor} className="my-0">
inverse="true" {sanityLevel ? `${sanityLevel}%` : `${t('common.unknown')} Sanity Level`}
footerSlot={ </CBadge>
<div className="pb-1 px-3"> </h4>
<CProgress className="mb-3" color="white" value={sanityLevel ?? 0} /> </div>
<CRow className="mb-3"> <div className="d-flex flex-row-reverse align-items-center">
<CCol> <div className="pl-2">
{t('common.from')} <CPopover content={t('common.refresh')}>
: <CButton
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> size="sm"
</CCol> color="info"
<CCol> onClick={getDeviceHealth}
{t('common.to')} disabled={startError || endError}
: >
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> <CIcon content={cilSync} />
</CCol> </CButton>
</CRow> </CPopover>
<CCard className="p-0"> </div>
<div className="overflow-auto" style={{ height: '200px' }}> <div className="pl-2">
<CDataTable <DatePicker
border includeTime
items={healthChecks ?? []} onChange={(date) => modifyEnd(date)}
fields={columns} value={end ? new Date(end) : undefined}
className="text-white" />
loading={loading} <CFormText color="danger" hidden={!endError}>
sorterValue={{ column: 'recorded', desc: 'true' }} {t('common.invalid_date_explanation')}
scopedSlots={{ </CFormText>
UUID: (item) => <td className="align-middle">{item.UUID}</td>, </div>
recorded: (item) => <td className="align-middle">{prettyDate(item.recorded)}</td>, To:
sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>, <div className="pl-2">
show_details: (item, index) => ( <DatePicker
<td className="align-middle"> includeTime
<CButton onChange={(date) => modifyStart(date)}
color="primary" value={start ? new Date(start) : undefined}
variant={details.includes(index) ? '' : 'outline'} />
shape="square" <CFormText color="danger" hidden={!startError}>
size="sm" {t('common.invalid_date_explanation')}
onClick={() => { </CFormText>
toggleDetails(index); </div>
}} From:
> <div className="px-2">
<CIcon name="cilList" size="lg" /> <CPopover content={t('common.delete')}>
</CButton> <CButton onClick={toggleDeleteModal} size="sm" color="danger">
</td> <CIcon name="cil-trash" content={cilTrash} />
), </CButton>
details: (item, index) => ( </CPopover>
<CCollapse show={details.includes(index)}> </div>
<CCardBody> </div>
<h5>{t('common.details')}</h5> </CCardHeader>
<div>{getDetails(index, item.values)}</div> <CCardBody className="p-1">
</CCardBody> <div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
</CCollapse> <CDataTable
), addTableClasses="ignore-overflow table-sm"
}} border
items={healthChecks ?? []}
fields={columns}
className="text-white"
loading={loading}
sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{
UUID: (item) => <td className="align-middle">{item.UUID}</td>,
recorded: (item) => (
<td className="align-middle">
<FormattedDate date={item.recorded} />
</td>
),
sanity: (item) => <td className="align-middle">{`${item.sanity}%`}</td>,
checkDetails: (item) => (
<td>
<pre className="my-0">{JSON.stringify(item.values)}</pre>
</td>
),
}}
/>
{showLoadingMore && (
<div className="mb-3">
<LoadingButton
label={t('common.view_more')}
isLoadingLabel={t('common.loading_more_ellipsis')}
isLoading={loadingMore}
action={showMoreLogs}
variant="outline"
/> />
{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> </div>
</CCard> )}
<DeleteLogModal <DeleteLogModal
serialNumber={deviceSerialNumber} serialNumber={deviceSerialNumber}
object="healthchecks" object="logs"
show={showDeleteModal} show={showDeleteModal}
toggle={toggleDeleteModal} toggle={toggleDeleteModal}
/> />
</div> </div>
} </CCardBody>
> </CCard>
<div className="text-right float-right">
<CPopover content={t('common.delete')}>
<CButton onClick={toggleDeleteModal} size="sm">
<CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" />
</CButton>
</CPopover>
</div>
</CWidgetDropdown>
); );
}; };

View File

@@ -0,0 +1,456 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate';
import {
CCardBody,
CDataTable,
CButton,
CLink,
CCard,
CCardHeader,
CRow,
CCol,
CPopover,
CSelect,
CButtonClose,
} from '@coreui/react';
import {
cilSync,
cilArrowCircleTop,
cilCheckCircle,
cilTerminal,
cilTrash,
cilSearch,
} from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import ReactTooltip from 'react-tooltip';
import { v4 as createUuid } from 'uuid';
import { cleanBytesString } from 'utils/helper';
import { DeviceBadge, LoadingButton } from 'ucentral-libs';
import styles from './index.module.scss';
const DeviceListTable = ({
currentPage,
devices,
searchBar,
devicesPerPage,
loading,
updateDevicesPerPage,
pageCount,
updatePage,
refreshDevice,
t,
toggleFirmwareModal,
toggleHistoryModal,
upgradeToLatest,
upgradeStatus,
deviceIcons,
connectRtty,
deleteDevice,
deleteStatus,
}) => {
const columns = [
{ key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '1%' } },
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } },
{ key: 'firmware', label: t('firmware.revision') },
{ key: 'firmware_button', label: '', filter: false, _style: { width: '1%' } },
{ key: 'compatible', label: t('common.type'), filter: false, _style: { width: '13%' } },
{ key: 'txBytes', label: 'Tx', filter: false, _style: { width: '14%' } },
{ key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '14%' } },
{ key: 'ipAddress', label: t('IP'), _style: { width: '10%' } },
{ key: 'twoG', label: t('2G'), _style: { width: '10%' } },
{ key: 'fiveG', label: t('5G'), _style: { width: '10%' } },
{ key: 'actions', label: t('actions.actions'), _style: { width: '10%' } },
];
const hideTooltips = () => ReactTooltip.hide();
const escFunction = (event) => {
if (event.keyCode === 27) {
hideTooltips();
}
};
const getShortRevision = (revision) => {
if (revision.includes(' / ')) {
return revision.split(' / ')[1];
}
return revision;
};
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', escFunction, false);
};
}, []);
const getFirmwareButton = (latest, device) => {
const tooltipId = createUuid();
let text = t('firmware.unknown_firmware_status');
let upgradeText = t('firmware.upgrade_to_latest');
let icon = <CIcon name="cil-arrow-circle-top" content={cilArrowCircleTop} />;
let color = 'secondary';
if (latest !== undefined) {
text = t('firmware.newer_firmware_available');
color = 'warning';
if (latest) {
icon = <CIcon name="cil-check-circle" content={cilCheckCircle} />;
text = t('firmware.latest_version_installed');
upgradeText = t('firmware.reinstall_latest');
color = 'success';
}
}
return (
<div>
<CButton size="sm" color={color} data-tip data-for={tooltipId} data-event="click">
{icon}
</CButton>
<ReactTooltip
id={tooltipId}
place="top"
effect="solid"
globalEventOff="click"
clickable
className={[styles.firmwareTooltip, 'tooltipLeft'].join(' ')}
border
borderColor="#321fdb"
arrowColor="white"
overridePosition={({ left, top }) => {
const element = document.getElementById(tooltipId);
const tooltipWidth = element ? element.offsetWidth : 0;
const newLeft = left + tooltipWidth * 0.25;
return { top, left: newLeft };
}}
>
<CCardHeader color="primary" className={styles.tooltipHeader}>
{text}
<CButtonClose
style={{ color: 'white' }}
onClick={(e) => {
e.target.parentNode.parentNode.classList.remove('show');
hideTooltips();
}}
/>
</CCardHeader>
<CCardBody>
<CRow>
<CCol>
<LoadingButton
variant="outline"
label={upgradeText}
isLoadingLabel={t('firmware.upgrading')}
isLoading={upgradeStatus.loading}
action={() => upgradeToLatest(device)}
block
disabled={
upgradeStatus.loading && upgradeStatus.serialNumber === device.serialNumber
}
/>
</CCol>
<CCol>
<CButton
block
variant="outline"
color="primary"
onClick={() => {
toggleFirmwareModal(device);
}}
>
{t('firmware.choose_custom')}
</CButton>
</CCol>
<CCol>
<CButton
block
variant="outline"
color="primary"
onClick={() => {
toggleHistoryModal(device);
}}
>
{t('firmware.history_title')}
</CButton>
</CCol>
</CRow>
</CCardBody>
</ReactTooltip>
</div>
);
};
const deleteButton = (serialNumber) => {
const tooltipId = createUuid();
return (
<>
<CPopover content={t('common.delete_device')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-1 d-inline"
data-tip
data-for={tooltipId}
data-event="click"
style={{ width: '33px', height: '30px' }}
>
<CIcon name="cil-trash" content={cilTrash} size="sm" />
</CButton>
</CPopover>
<ReactTooltip
id={tooltipId}
place="top"
effect="solid"
globalEventOff="click"
clickable
className={[styles.deleteTooltip, 'tooltipRight'].join(' ')}
border
borderColor="#321fdb"
arrowColor="white"
overridePosition={({ left, top }) => {
const element = document.getElementById(tooltipId);
const tooltipWidth = element ? element.offsetWidth : 0;
const newLeft = left - tooltipWidth * 0.25;
return { top, left: newLeft };
}}
>
<CCardHeader color="primary" className={styles.tooltipHeader}>
{t('common.device_delete', { serialNumber })}
<CButtonClose
className="p-0 mb-1"
style={{ color: 'white' }}
onClick={(e) => {
e.target.parentNode.parentNode.classList.remove('show');
hideTooltips();
}}
/>
</CCardHeader>
<CCardBody>
<CRow>
<CCol>
<LoadingButton
data-toggle="dropdown"
variant="outline"
color="danger"
label={t('common.confirm')}
isLoadingLabel={t('user.deleting')}
isLoading={deleteStatus.loading}
action={(e) => {
e.target.parentNode.parentNode.parentNode.parentNode.classList.remove('show');
hideTooltips();
deleteDevice(serialNumber);
}}
block
disabled={deleteStatus.loading}
/>
</CCol>
</CRow>
</CCardBody>
</ReactTooltip>
</>
);
};
return (
<>
<CCard className="m-0 p-0">
<CCardHeader className="p-0">
<div className="float-left" style={{ width: '400px' }}>
{searchBar}
</div>
</CCardHeader>
<CCardBody className="p-0">
<CDataTable
addTableClasses="ignore-overflow table-sm"
items={devices ?? []}
fields={columns}
hover
border
loading={loading}
scopedSlots={{
deviceType: (item) => (
<td className="align-middle text-center">
<DeviceBadge t={t} device={item} deviceIcons={deviceIcons} />
</td>
),
serialNumber: (item) => (
<td className="text-center align-middle">
<CLink
className="c-subheader-nav-link"
aria-current="page"
to={() => `/devices/${item.serialNumber}`}
>
{item.serialNumber}
</CLink>
</td>
),
firmware: (item) => (
<td className="align-middle">
<CPopover
content={item.firmware ? item.firmware : t('common.na')}
placement="top"
>
<div style={{ width: 'calc(10vw)' }} className="text-truncate align-middle">
{getShortRevision(item.firmware)}
</div>
</CPopover>
</td>
),
firmware_button: (item) => (
<td className="text-center align-middle">
{item.firmwareInfo
? getFirmwareButton(item.firmwareInfo.latest, item)
: getFirmwareButton(undefined, item)}
</td>
),
compatible: (item) => (
<td className="align-middle">
<CPopover
content={item.compatible ? item.compatible : t('common.na')}
placement="top"
>
<div style={{ width: 'calc(10vw)' }} className="text-truncate align-middle">
{item.compatible}
</div>
</CPopover>
</td>
),
txBytes: (item) => <td className="align-middle">{cleanBytesString(item.txBytes)}</td>,
rxBytes: (item) => <td className="align-middle">{cleanBytesString(item.rxBytes)}</td>,
ipAddress: (item) => (
<td className="align-middle">
<CPopover
content={item.ipAddress ? item.ipAddress : t('common.na')}
placement="top"
>
<div style={{ width: 'calc(8vw)' }} className="text-truncate align-middle">
{item.ipAddress}
</div>
</CPopover>
</td>
),
twoG: (item) => <td className="align-middle">{item.associations_2G ?? 0}</td>,
fiveG: (item) => <td className="align-middle">{item.associations_5G ?? 0}</td>,
actions: (item) => (
<td className="text-center align-middle">
<div role="group" className="justify-content-center" style={{ width: '190px' }}>
<CPopover content={t('actions.connect')}>
<CButton
className="mx-1 d-inline"
color="primary"
variant="outline"
shape="square"
size="sm"
onClick={() => connectRtty(item.serialNumber)}
style={{ width: '33px', height: '30px' }}
>
<CIcon name="cil-terminal" content={cilTerminal} size="sm" />
</CButton>
</CPopover>
{deleteButton(item.serialNumber)}
<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"
className="mx-1 d-inline"
style={{ width: '33px', height: '30px' }}
>
<CIcon name="cil-search" content={cilSearch} size="sm" />
</CButton>
</CLink>
</CPopover>
<CPopover content={t('common.refresh_device')}>
<CButton
onClick={() => refreshDevice(item.serialNumber)}
color="primary"
variant="outline"
shape="square"
size="sm"
className="mx-1 d-inline"
style={{ width: '33px', height: '30px' }}
>
<CIcon name="cil-sync" content={cilSync} size="sm" />
</CButton>
</CPopover>
</div>
</td>
),
}}
/>
<div className="d-flex flex-row pl-3">
<div className="pr-3">
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={updatePage}
forcePage={Number(currentPage)}
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"
/>
</div>
<p className="pr-2 mt-1">{t('common.items_per_page')}</p>
<div style={{ width: '100px' }} className="px-2">
<CSelect
custom
defaultValue={devicesPerPage}
onChange={(e) => updateDevicesPerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</div>
</CCardBody>
</CCard>
</>
);
};
DeviceListTable.propTypes = {
currentPage: PropTypes.oneOf(['string', 'number']),
devices: PropTypes.instanceOf(Array).isRequired,
searchBar: PropTypes.node.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,
toggleFirmwareModal: PropTypes.func.isRequired,
toggleHistoryModal: PropTypes.func.isRequired,
upgradeToLatest: PropTypes.func.isRequired,
upgradeStatus: PropTypes.instanceOf(Object).isRequired,
deviceIcons: PropTypes.instanceOf(Object).isRequired,
connectRtty: PropTypes.func.isRequired,
deleteDevice: PropTypes.func.isRequired,
deleteStatus: PropTypes.instanceOf(Object).isRequired,
};
DeviceListTable.defaultProps = {
currentPage: '0',
};
export default React.memo(DeviceListTable);

View File

@@ -0,0 +1,30 @@
.firmwareTooltip {
opacity: 1 !important;
padding: 0px 0px 0px 0px !important;
border-radius: 1rem !important;
background-color: #fff !important;
border-color: #321fdb !important;
font-size: 0.875rem !important;
font-weight: 400 !important;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
width: 400px;
}
.deleteTooltip {
opacity: 1 !important;
padding: 0px 0px 0px 0px !important;
border-radius: 1rem !important;
background-color: #fff !important;
border-color: #321fdb !important;
font-size: 0.875rem !important;
font-weight: 400 !important;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2) !important;
width: 200px;
}
.tooltipHeader {
padding-left: 5px;
padding-right: 10px;
border-top-left-radius: 1rem !important;
border-top-right-radius: 1rem !important;
}

View File

@@ -1,12 +1,13 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { getItem, setItem } from 'utils/localStorageHelper'; import { getItem, setItem } from 'utils/localStorageHelper';
import DeviceSearchBar from 'components/DeviceSearchBar'; import DeviceSearchBar from 'components/DeviceSearchBar';
import DeviceFirmwareModal from 'components/DeviceFirmwareModal'; import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
import FirmwareHistoryModal from 'components/FirmwareHistoryModal'; import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
import { DeviceListTable, useAuth, useToast } from 'ucentral-libs'; import { useAuth, useToast } from 'ucentral-libs';
import Table from './Table';
import meshIcon from '../../assets/icons/Mesh.png'; import meshIcon from '../../assets/icons/Mesh.png';
import apIcon from '../../assets/icons/AP.png'; import apIcon from '../../assets/icons/AP.png';
import internetSwitch from '../../assets/icons/Switch.png'; import internetSwitch from '../../assets/icons/Switch.png';
@@ -16,8 +17,7 @@ const DeviceList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { addToast } = useToast(); const { addToast } = useToast();
const history = useHistory(); const history = useHistory();
const { search } = useLocation(); const [page, setPage] = useState(parseInt(sessionStorage.getItem('deviceTable') ?? 0, 10));
const page = new URLSearchParams(search).get('page');
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const [upgradeStatus, setUpgradeStatus] = useState({ const [upgradeStatus, setUpgradeStatus] = useState({
loading: false, loading: false,
@@ -69,7 +69,7 @@ const DeviceList = () => {
axiosInstance axiosInstance
.get( .get(
`${endpoints.owgw}/api/v1/devices?deviceWithStatus=true&limit=${devicePerPage}&offset=${ `${endpoints.owgw}/api/v1/devices?deviceWithStatus=true&limit=${devicePerPage}&offset=${
devicePerPage * selectedPage + 1 devicePerPage * selectedPage
}`, }`,
options, options,
) )
@@ -225,13 +225,15 @@ const DeviceList = () => {
}; };
const updatePageCount = ({ selected: selectedPage }) => { const updatePageCount = ({ selected: selectedPage }) => {
history.push(`/devices?page=${selectedPage}`); sessionStorage.setItem('deviceTable', selectedPage);
setPage(selectedPage);
getDeviceInformation(selectedPage); getDeviceInformation(selectedPage);
}; };
const upgradeToLatest = (device) => { const upgradeToLatest = (device) => {
setUpgradeStatus({ setUpgradeStatus({
loading: true, loading: true,
serialNumber: device.serialNumber,
}); });
const options = { const options = {
@@ -354,9 +356,6 @@ const DeviceList = () => {
}; };
useEffect(() => { useEffect(() => {
if (page === undefined || page === null || Number.isNaN(page)) {
history.push(`/devices?page=0`);
}
getCount(); getCount();
}, []); }, []);
@@ -379,7 +378,7 @@ const DeviceList = () => {
return ( return (
<div> <div>
<DeviceListTable <Table
currentPage={page} currentPage={page}
t={t} t={t}
searchBar={<DeviceSearchBar />} searchBar={<DeviceSearchBar />}

View File

@@ -1,24 +1,23 @@
/* eslint-disable-rule prefer-destructuring */ /* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
CWidgetDropdown, CCardHeader,
CRow, CCardBody,
CCol,
CCollapse, CCollapse,
CButton, CButton,
CDataTable, CDataTable,
CCard, CCard,
CCardBody,
CPopover, CPopover,
CFormText,
} from '@coreui/react'; } from '@coreui/react';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { cilTrash } from '@coreui/icons'; import { cilSync, cilTrash } from '@coreui/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import DatePicker from 'react-widgets/DatePicker'; import DatePicker from 'react-widgets/DatePicker';
import { prettyDate, dateToUnix } from 'utils/helper'; import { dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs'; import { LoadingButton, useAuth, useDevice, FormattedDate } from 'ucentral-libs';
import DeleteLogModal from 'components/DeleteLogModal'; import DeleteLogModal from 'components/DeleteLogModal';
const DeviceLogs = () => { const DeviceLogs = () => {
@@ -29,7 +28,9 @@ const DeviceLogs = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [start, setStart] = useState(''); const [start, setStart] = useState('');
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState(''); const [end, setEnd] = useState('');
const [endError, setEndError] = useState(false);
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);
@@ -40,11 +41,25 @@ const DeviceLogs = () => {
}; };
const modifyStart = (value) => { const modifyStart = (value) => {
setStart(value); try {
new Date(value).toISOString();
setStartError(false);
setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
}; };
const modifyEnd = (value) => { const modifyEnd = (value) => {
setEnd(value); try {
new Date(value).toISOString();
setEndError(false);
setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
}; };
const showMoreLogs = () => { const showMoreLogs = () => {
@@ -106,14 +121,15 @@ const DeviceLogs = () => {
const getDetails = (index, logDetails) => { const getDetails = (index, logDetails) => {
if (details.includes(index)) if (details.includes(index))
return <pre className="ignore">{JSON.stringify(logDetails, null, 4)}</pre>; return <pre className="ignore">{JSON.stringify(logDetails, null, 2)}</pre>;
return <pre className="ignore" />; return <pre className="ignore" />;
}; };
const columns = [ const columns = [
{ key: 'recorded', label: t('common.recorded'), _style: { width: '15%' } },
{ key: 'UUID', label: t('common.config_id'), _style: { width: '10%' } },
{ key: 'severity', label: t('device_logs.severity'), _style: { width: '5%' } },
{ key: 'log', label: t('device_logs.log') }, { key: 'log', label: t('device_logs.log') },
{ key: 'severity', label: t('device_logs.severity') },
{ key: 'recorded', label: t('common.recorded') },
{ {
key: 'show_details', key: 'show_details',
label: '', label: '',
@@ -166,81 +182,105 @@ const DeviceLogs = () => {
return ( return (
<div> <div>
<CWidgetDropdown <CCard className="m-0">
inverse="true" <CCardHeader className="dark-header">
color="gradient-info" <div className="d-flex flex-row-reverse align-items-center">
header={t('device_logs.title')} <div className="pl-2">
footerSlot={ <CPopover content={t('common.refresh')}>
<div className="pb-1 px-3"> <CButton size="sm" color="info" onClick={getLogs} disabled={startError || endError}>
<CRow className="mb-3"> <CIcon content={cilSync} />
<CCol> </CButton>
{t('common.from')} </CPopover>
<DatePicker includeTime onChange={(date) => modifyStart(date)} /> </div>
</CCol> <div className="pl-2">
<CCol> <DatePicker
{t('common.to')} includeTime
<DatePicker includeTime onChange={(date) => modifyEnd(date)} /> onChange={(date) => modifyEnd(date)}
</CCol> value={end ? new Date(end) : undefined}
</CRow> />
<CCard> <CFormText color="danger" hidden={!endError}>
<div className="overflow-auto" style={{ height: '250px' }}> {t('common.invalid_date_explanation')}
<CDataTable </CFormText>
items={logs ?? []} </div>
fields={columns} To:
loading={loading} <div className="pl-2">
className="text-white" <DatePicker
sorterValue={{ column: 'recorded', desc: 'true' }} includeTime
scopedSlots={{ onChange={(date) => modifyStart(date)}
recorded: (item) => <td>{prettyDate(item.recorded)}</td>, value={start ? new Date(start) : undefined}
show_details: (item, index) => ( />
<td className="py-2"> <CFormText color="danger" hidden={!startError}>
<CButton {t('common.invalid_date_explanation')}
color="primary" </CFormText>
variant={details.includes(index) ? '' : 'outline'} </div>
shape="square" From:
size="sm" <div className="px-2">
onClick={() => { <CPopover content={t('common.delete')}>
toggleDetails(index); <CButton onClick={toggleDeleteModal} size="sm" color="danger">
}} <CIcon name="cil-trash" content={cilTrash} />
> </CButton>
<CIcon name="cilList" size="lg" /> </CPopover>
</CButton> </div>
</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>
</div> </div>
} </CCardHeader>
> <CCardBody className="p-1">
<div className="text-right float-right"> <div className="overflow-auto" style={{ height: 'calc(100vh - 620px)' }}>
<CPopover content={t('common.delete')}> <CDataTable
<CButton onClick={toggleDeleteModal} size="sm"> addTableClasses="ignore-overflow table-sm"
<CIcon name="cil-trash" content={cilTrash} className="text-white" size="2xl" /> border
</CButton> items={logs ?? []}
</CPopover> fields={columns}
</div> loading={loading}
</CWidgetDropdown> className="text-white"
sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{
recorded: (item) => (
<td className="align-middle">
<FormattedDate date={item.recorded} />
</td>
),
UUID: (item) => <td className="align-middle">{item.UUID}</td>,
severity: (item) => <td className="align-middle">{item.severity}</td>,
log: (item) => <td className="align-middle">{item.log}</td>,
show_details: (item, index) => (
<td className="align-middle">
<CButton
color="primary"
variant={details.includes(index) ? '' : 'outline'}
shape="square"
size="sm"
onClick={() => {
toggleDetails(index);
}}
>
<CIcon name="cilList" />
</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>
</CCardBody>
</CCard>
<DeleteLogModal <DeleteLogModal
serialNumber={deviceSerialNumber} serialNumber={deviceSerialNumber}
object="logs" object="logs"

View File

@@ -1,10 +1,11 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs'; import { useAuth, DeviceSearchBar as SearchBar } from 'ucentral-libs';
import { checkIfJson } from 'utils/helper'; import { checkIfJson } from 'utils/helper';
const DeviceSearchBar = () => { const DeviceSearchBar = ({ action }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
@@ -14,7 +15,7 @@ const DeviceSearchBar = () => {
const search = (value) => { const search = (value) => {
if (socket.readyState === WebSocket.OPEN) { if (socket.readyState === WebSocket.OPEN) {
if (value.length > 0 && value.match('^[a-fA-F0-9]+$')) { if (value.length > 1 && value.match('^[a-fA-F0-9-*]+$')) {
setWaitingSearch(''); setWaitingSearch('');
socket.send( socket.send(
JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }), JSON.stringify({ command: 'serial_number_search', serial_prefix: value.toLowerCase() }),
@@ -65,7 +66,15 @@ const DeviceSearchBar = () => {
} }
}, []); }, []);
return <SearchBar t={t} search={search} results={results} history={history} />; return <SearchBar t={t} search={search} results={results} history={history} action={action} />;
};
DeviceSearchBar.propTypes = {
action: PropTypes.func,
};
DeviceSearchBar.defaultProps = {
action: null,
}; };
export default DeviceSearchBar; export default DeviceSearchBar;

View File

@@ -1,100 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance';
import { DeviceStatusCard as Card, useDevice, useAuth, useToast } from 'ucentral-libs';
const DeviceStatusCard = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
const { addToast } = useToast();
const [lastStats, setLastStats] = useState(null);
const [status, setStatus] = useState(null);
const [deviceConfig, setDeviceConfig] = useState(null);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const getDevice = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/device/${encodeURIComponent(deviceSerialNumber)}`, options)
.then((response) => {
setDeviceConfig(response.data);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_device', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
});
};
const getData = () => {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
const lastStatsRequest = axiosInstance.get(
`${endpoints.owgw}/api/v1/device/${encodeURIComponent(
deviceSerialNumber,
)}/statistics?lastOnly=true`,
options,
);
const statusRequest = axiosInstance.get(
`${endpoints.owgw}/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);
});
};
const refresh = () => {
getData();
getDevice();
};
useEffect(() => {
setError(false);
if (deviceSerialNumber) {
getDevice();
getData();
}
}, [deviceSerialNumber]);
return (
<Card
t={t}
loading={loading}
error={error}
deviceSerialNumber={deviceSerialNumber}
getData={refresh}
deviceConfig={deviceConfig}
status={status}
lastStats={lastStats}
/>
);
};
export default DeviceStatusCard;

View File

@@ -0,0 +1,154 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import {
CButton,
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CPopover,
CRow,
CCol,
CLabel,
CTextarea,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { useAuth, useToast } from 'ucentral-libs';
import { cilSave, cilX } from '@coreui/icons';
import axiosInstance from 'utils/axiosInstance';
const EditBlacklistModal = ({ show, toggle, serialNumber, refresh }) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const { addToast } = useToast();
const { endpoints, currentToken } = useAuth();
const [reason, setReason] = useState('');
const getBlacklistInfo = () => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/blacklist/${serialNumber}`, {
headers,
})
.then((response) => {
setReason(response.data.reason);
setLoading(false);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_fetching_devices', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
setLoading(false);
toggle();
});
};
const save = () => {
setLoading(true);
const parameters = {
reason,
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.put(`${endpoints.owgw}/api/v1/blacklist/${serialNumber}`, parameters, { headers })
.then(() => {
addToast({
title: t('common.success'),
body: t('device.success_edit_blacklist'),
color: 'success',
autohide: true,
});
toggle();
if (refresh) refresh();
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('device.error_edit_blacklist', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
if (show) getBlacklistInfo();
}, [show, serialNumber]);
return (
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('device.edit_blacklist')}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.add')}>
<CButton
color="primary"
variant="outline"
className="ml-2"
onClick={save}
disabled={loading || reason === ''}
>
<CIcon content={cilSave} />
</CButton>
</CPopover>
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody>
<CRow>
<CLabel col sm="3">
{t('common.reason')}
</CLabel>
<CCol sm="9" className="pt-2">
<CTextarea
name="reason"
id="reason"
rows="3"
type="text"
required
value={reason}
onChange={(e) => setReason(e.target.value)}
/>
</CCol>
</CRow>
</CModalBody>
</CModal>
);
};
EditBlacklistModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
serialNumber: PropTypes.string,
refresh: PropTypes.func,
};
EditBlacklistModal.defaultProps = {
serialNumber: '',
refresh: null,
};
export default EditBlacklistModal;

View File

@@ -0,0 +1,163 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import {
CForm,
CInput,
CLabel,
CCol,
CFormGroup,
CInvalidFeedback,
CFormText,
CRow,
CTextarea,
} from '@coreui/react';
import { CopyToClipboardButton } from 'ucentral-libs';
const EditDefaultConfigurationForm = ({
t,
disable,
fields,
updateField,
updateFieldWithKey,
deviceTypes,
editing,
}) => {
const [typeOptions, setTypeOptions] = useState([]);
const [chosenTypes, setChosenTypes] = useState([]);
const parseOptions = () => {
const options = [{ value: '*', label: 'All' }];
const newOptions = deviceTypes.map((option) => ({
value: option,
label: option,
}));
options.push(...newOptions);
setTypeOptions(options);
setChosenTypes([]);
const newChosenTypes = fields.modelIds.value.map((dType) => ({
value: dType,
label: dType === '*' ? 'All' : dType,
}));
setChosenTypes(newChosenTypes);
};
const typeOnChange = (chosenArray) => {
const allIndex = chosenArray.findIndex((el) => el.value === '*');
// If the All option was chosen before, we take it out of the array
if (allIndex === 0 && chosenTypes.length > 0) {
const newResults = chosenArray.slice(1);
setChosenTypes(newResults);
updateFieldWithKey('modelIds', {
value: newResults.map((el) => el.value),
error: false,
notEmpty: true,
});
} else if (allIndex > 0) {
setChosenTypes([{ value: '*', label: 'All' }]);
updateFieldWithKey('modelIds', { value: ['*'], error: false, notEmpty: true });
} else if (chosenArray.length > 0) {
setChosenTypes(chosenArray);
updateFieldWithKey('modelIds', {
value: chosenArray.map((el) => el.value),
error: false,
notEmpty: true,
});
} else {
setChosenTypes([]);
updateFieldWithKey('modelIds', { value: [], error: false, notEmpty: true });
}
};
useEffect(() => {
parseOptions();
}, [deviceTypes, fields.name.value]);
return (
<CForm>
<CFormGroup row className="pb-3">
<CLabel col htmlFor="name">
{t('user.name')}
</CLabel>
<CCol sm="7" className="pt-2">
{fields.name.value}
</CCol>
</CFormGroup>
<CFormGroup row className="pb-3">
<CLabel col htmlFor="description">
{t('user.description')}
</CLabel>
<CCol sm="7">
<CInput
id="description"
type="text"
required
value={fields.description.value}
onChange={updateField}
invalid={fields.description.error}
disabled={disable || !editing}
maxLength="50"
/>
<CInvalidFeedback>{t('common.required')}</CInvalidFeedback>
</CCol>
</CFormGroup>
<CRow className="pb-3">
<CLabel col htmlFor="deviceTypes">
<div>{t('configuration.supported_device_types')}:</div>
</CLabel>
<CCol sm="7">
<Select
isMulti
closeMenuOnSelect={false}
id="deviceTypes"
options={typeOptions}
onChange={typeOnChange}
value={chosenTypes}
className={`basic-multi-select ${fields.modelIds.error ? 'border-danger' : ''}`}
classNamePrefix="select"
isDisabled={disable || !editing}
/>
<CFormText hidden={!fields.modelIds.error} color="danger">
{t('configuration.need_device_type')}
</CFormText>
</CCol>
</CRow>
<div className="pb-3">
{t('configuration.title')}
<CopyToClipboardButton t={t} size="sm" content={fields.configuration.value} />
</div>
<CRow className="pb-3">
<CCol>
<CTextarea
style={{ overflowY: 'scroll', height: '500px' }}
id="configuration"
type="text"
required
value={fields.configuration.value}
onChange={updateField}
invalid={fields.configuration.error}
disabled={disable || !editing}
/>
<CFormText hidden={!fields.configuration.error} color="danger">
{t('common.required')}
</CFormText>
</CCol>
</CRow>
</CForm>
);
};
EditDefaultConfigurationForm.propTypes = {
t: PropTypes.func.isRequired,
disable: PropTypes.bool.isRequired,
fields: PropTypes.instanceOf(Object).isRequired,
updateField: PropTypes.func.isRequired,
updateFieldWithKey: PropTypes.func.isRequired,
deviceTypes: PropTypes.instanceOf(Array).isRequired,
editing: PropTypes.bool.isRequired,
};
export default EditDefaultConfigurationForm;

View File

@@ -0,0 +1,243 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { CModal, CModalHeader, CModalTitle, CModalBody, CButton, CPopover } from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilX, cilSave, cilPencil } from '@coreui/icons';
import { useToast, useFormFields, useAuth } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance';
import { useTranslation } from 'react-i18next';
import { checkIfJson } from 'utils/helper';
import Form from './Form';
const initialForm = {
name: {
value: '',
error: false,
required: true,
},
description: {
value: '',
error: false,
},
modelIds: {
value: [],
error: false,
notEmpty: true,
},
configuration: {
value: '',
error: false,
required: true,
},
};
const EditConfigurationModal = ({ show, toggle, refresh, configId }) => {
const { t } = useTranslation();
const { addToast } = useToast();
const { currentToken, endpoints } = useAuth();
const [fields, updateFieldWithId, updateField, setFormFields] = useFormFields(initialForm);
const [loading, setLoading] = useState(false);
const [deviceTypes, setDeviceTypes] = useState([]);
const [editing, setEditing] = useState(false);
const getConfig = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/default_configuration/${configId}`, options)
.then((response) => {
const newConfig = {};
for (const key of Object.keys(response.data)) {
if (key in initialForm) {
newConfig[key] = {
...initialForm[key],
value: response.data[key],
};
}
}
newConfig.configuration.value = JSON.stringify(response.data.configuration, null, 2);
setFormFields(newConfig);
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('configuration.error_fetching_config', {
error: e.response?.data?.ErrorDescription,
}),
color: 'danger',
autohide: true,
});
toggle();
});
};
const getDeviceTypes = () => {
setLoading(true);
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
};
axiosInstance
.get(`${endpoints.owfms}/api/v1/firmwares?deviceSet=true`, {
headers,
})
.then((response) => {
setDeviceTypes([...response.data.deviceTypes]);
})
.catch(() => {})
.finally(() => {
setLoading(false);
});
};
const validation = () => {
let success = true;
for (const [key, field] of Object.entries(fields)) {
if (field.required && field.value === '') {
updateField(key, { error: true });
success = false;
break;
}
if (field.notEmpty && field.value.length === 0) {
updateField(key, { error: true, notEmpty: true });
success = false;
break;
}
}
if (!checkIfJson(fields.configuration.value)) {
updateField('configuration', { error: true });
success = false;
}
return success;
};
const save = () => {
if (validation()) {
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
const parameters = {
name: fields.name.value,
description: fields.description.value,
modelIds: fields.modelIds.value,
configuration: fields.configuration.value,
};
axiosInstance
.put(`${endpoints.owgw}/api/v1/default_configuration/${configId}`, parameters, options)
.then(() => {
if (refresh !== null) refresh();
toggle();
addToast({
title: t('common.success'),
body: t('configuration.success_update'),
color: 'success',
autohide: true,
});
})
.catch((e) => {
addToast({
title: t('common.error'),
body: t('configuration.error_update', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
})
.finally(() => {
setLoading(false);
});
}
};
const toggleEditing = () => {
if (editing) getConfig();
setEditing(!editing);
};
useEffect(() => {
if (show) {
setEditing(false);
getConfig();
getDeviceTypes();
}
}, [show]);
return (
<CModal className="text-dark" size="lg" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('configuration.edit_configuration')}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.save')}>
<CButton
color="primary"
variant="outline"
className="ml-2"
onClick={save}
disabled={!editing}
>
<CIcon content={cilSave} />
</CButton>
</CPopover>
<CPopover content={t('common.edit')}>
<CButton
color="primary"
variant="outline"
className="ml-2"
onClick={toggleEditing}
disabled={editing}
>
<CIcon content={cilPencil} />
</CButton>
</CPopover>
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody className="px-5">
<Form
t={t}
disable={loading}
fields={fields}
editing={editing}
updateField={updateFieldWithId}
updateFieldWithKey={updateField}
deviceTypes={deviceTypes}
show={show}
/>
</CModalBody>
</CModal>
);
};
EditConfigurationModal.propTypes = {
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
refresh: PropTypes.func,
configId: PropTypes.string,
};
EditConfigurationModal.defaultProps = {
refresh: null,
configId: '',
};
export default EditConfigurationModal;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CCardBody, CCol, CInput, CRow } from '@coreui/react';
import { prettyDate, cleanBytesString } from 'utils/helper';
const FirmwareDetailsForm = ({ t, fields, updateFieldsWithId, editing }) => (
<CCardBody className="p-1">
<CRow>
<CCol sm="2">{t('firmware.release')}</CCol>
<CCol sm="4">{fields.release.value}</CCol>
<CCol sm="2">{t('common.created')}</CCol>
<CCol sm="4">{prettyDate(fields.created.value)}</CCol>
</CRow>
<CRow className="my-3">
<CCol sm="2">{t('firmware.image_date')}</CCol>
<CCol sm="4">{prettyDate(fields.imageDate.value)}</CCol>
<CCol sm="2">{t('firmware.size')}</CCol>
<CCol sm="4">{cleanBytesString(fields.size.value)}</CCol>
</CRow>
<CRow className="my-3">
<CCol sm="2">{t('firmware.image')}</CCol>
<CCol sm="4">{fields.image.value}</CCol>
<CCol sm="2">{t('firmware.revision')}</CCol>
<CCol sm="4">{fields.revision.value}</CCol>
</CRow>
<CRow className="my-3">
<CCol sm="2">URI</CCol>
<CCol sm="4">{fields.uri.value}</CCol>
<CCol sm="2" className="mt-2">
{t('user.description')}
</CCol>
<CCol sm="4">
{editing ? (
<CInput
id="description"
value={fields.description.value}
onChange={updateFieldsWithId}
maxLength="50"
/>
) : (
<p className="mt-2 mb-0">{fields.description.value}</p>
)}
</CCol>
</CRow>
</CCardBody>
);
FirmwareDetailsForm.propTypes = {
t: PropTypes.func.isRequired,
fields: PropTypes.instanceOf(Object).isRequired,
updateFieldsWithId: PropTypes.func.isRequired,
editing: PropTypes.bool.isRequired,
};
export default FirmwareDetailsForm;

View File

@@ -0,0 +1,262 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import {
CButton,
CModal,
CModalBody,
CModalHeader,
CModalTitle,
CPopover,
CNav,
CNavLink,
CTabPane,
CTabContent,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilPencil, cilSave, cilX } from '@coreui/icons';
import axiosInstance from 'utils/axiosInstance';
import { useFormFields, useAuth, useToast, DetailedNotesTable } from 'ucentral-libs';
import Form from './Form';
const initialState = {
created: {
value: '',
error: false,
editable: false,
},
release: {
value: false,
error: false,
editable: false,
},
image: {
value: '',
error: false,
editable: true,
},
imageDate: {
value: '',
error: false,
editable: true,
},
size: {
value: '',
error: false,
editable: true,
},
revision: {
value: '',
error: false,
editable: false,
},
uri: {
value: '',
error: false,
editable: true,
},
description: {
value: '',
error: false,
editable: true,
},
notes: {
value: [],
editable: false,
},
};
const EditFirmwareModal = ({ show, toggle, firmwareId, refreshTable }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const [editing, setEditing] = useState(false);
const [index, setIndex] = useState(0);
const [firmware, updateWithId, updateWithKey, setFirmware] = useFormFields(initialState);
const getFirmware = () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.owfms}/api/v1/firmware/${firmwareId}`, options)
.then((response) => {
const newFirmware = {};
for (const key of Object.keys(response.data)) {
if (key in initialState && key !== 'currentPassword') {
newFirmware[key] = {
...initialState[key],
value: response.data[key],
};
}
}
setFirmware({ ...initialState, ...newFirmware });
})
.catch(() => {});
};
const toggleEditing = () => {
if (editing) {
getFirmware();
}
setEditing(!editing);
};
const toggleModal = () => {
toggleEditing();
toggle();
};
const updateFirmware = () => {
setLoading(true);
const parameters = {
id: firmwareId,
description: firmware.description.value,
};
const newNotes = [];
for (let i = 0; i < firmware.notes.value.length; i += 1) {
if (firmware.notes.value[i].new) newNotes.push({ note: firmware.notes.value[i].note });
}
parameters.notes = newNotes;
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.put(`${endpoints.owfms}/api/v1/firmware/${firmwareId}`, parameters, options)
.then(() => {
addToast({
title: t('firmware.update_success_title'),
body: t('firmware.update_success'),
color: 'success',
autohide: true,
});
refreshTable();
toggle();
})
.catch((e) => {
addToast({
title: t('firmware.update_failure_title'),
body: t('firmware.update_failure', { error: e.response?.data?.ErrorDescription }),
color: 'danger',
autohide: true,
});
getFirmware();
})
.finally(() => {
setLoading(false);
});
};
const addNote = (currentNote) => {
const newNotes = [...firmware.notes.value];
newNotes.unshift({
note: currentNote,
new: true,
created: new Date().getTime() / 1000,
createdBy: '',
});
updateWithKey('notes', { value: newNotes });
};
useEffect(() => {
if (show) {
getFirmware();
setEditing(false);
setIndex(0);
}
}, [show]);
return (
<CModal show={show} onClose={toggle} size="xl">
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">
{t('firmware.details_title', { image: firmware.image.value })}
</CModalTitle>
<div className="text-right">
<CPopover content={t('common.save')}>
<CButton color="primary" variant="outline" onClick={updateFirmware} disabled={loading}>
<CIcon content={cilSave} />
</CButton>
</CPopover>
<CPopover content={t('common.edit')}>
<CButton
disabled={editing}
color="primary"
variant="outline"
onClick={toggleEditing}
className="ml-2"
>
<CIcon name="cil-pencil" content={cilPencil} />
</CButton>
</CPopover>
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggleModal}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody className="px-3 pt-0">
<CNav variant="tabs" className="mb-0 p-0">
<CNavLink
className="font-weight-bold"
href="#"
active={index === 0}
onClick={() => setIndex(0)}
>
{t('common.main')}
</CNavLink>
<CNavLink
className="font-weight-bold"
href="#"
active={index === 1}
onClick={() => setIndex(1)}
>
{t('configuration.notes')}
</CNavLink>
</CNav>
<CTabContent>
<CTabPane active={index === 0} className="pt-2">
{index === 0 ? (
<Form t={t} fields={firmware} updateFieldsWithId={updateWithId} editing={editing} />
) : null}
</CTabPane>
<CTabPane active={index === 1}>
{index === 1 ? (
<DetailedNotesTable
t={t}
notes={firmware.notes.value}
addNote={addNote}
loading={loading}
editable={editing}
/>
) : null}
</CTabPane>
</CTabContent>
</CModalBody>
</CModal>
);
};
EditFirmwareModal.propTypes = {
firmwareId: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
refreshTable: PropTypes.func.isRequired,
};
export default React.memo(EditFirmwareModal);

View File

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

View File

@@ -0,0 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CModal,
CModalBody,
CModalHeader,
CModalTitle,
CSpinner,
CPopover,
CButton,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilX } from '@coreui/icons';
const EventQueueModal = ({ t, show, toggle, loading, result }) => (
<CModal size="lg" show={show} onClose={toggle}>
<CModalHeader className="p-1">
<CModalTitle className="pl-1 pt-1">{t('commands.event_queue')}</CModalTitle>
<div className="text-right">
<CPopover content={t('common.close')}>
<CButton color="primary" variant="outline" className="ml-2" onClick={toggle}>
<CIcon content={cilX} />
</CButton>
</CPopover>
</div>
</CModalHeader>
<CModalBody className="text-center">
{loading ? (
<CSpinner color="primary" size="lg" />
) : (
<pre className="ignore text-left">{JSON.stringify(result, null, 4)}</pre>
)}
</CModalBody>
</CModal>
);
EventQueueModal.propTypes = {
t: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
result: PropTypes.instanceOf(Object).isRequired,
};
export default EventQueueModal;

View File

@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { EventQueueModal as Modal, useAuth, useDevice, useToast } from 'ucentral-libs'; import { useAuth, useDevice, useToast } from 'ucentral-libs';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import Modal from './Modal';
const EventQueueModal = ({ show, toggle }) => { const EventQueueModal = ({ show, toggle }) => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -0,0 +1,329 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CCard,
CCardBody,
CCardHeader,
CCol,
CDataTable,
CPopover,
CRow,
CSpinner,
CWidgetIcon,
} from '@coreui/react';
import { CChartBar, CChartHorizontalBar, CChartPie } from '@coreui/react-chartjs';
import { cilClock, cilHappy, cilMeh, cilFrown, cilBirthdayCake, cilInfo } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import { FormattedDate } from 'ucentral-libs';
import styles from './index.module.scss';
const getLatestColor = (percent = 0) => {
const numberPercent = percent ? Number(percent.replace('%', '')) : 0;
if (numberPercent >= 90) return 'success';
if (numberPercent > 60) return 'warning';
return 'danger';
};
const getLatestIcon = (percent = 0) => {
const numberPercent = percent ? Number(percent.replace('%', '')) : 0;
if (numberPercent >= 90) return <CIcon width={36} name="cil-happy" content={cilHappy} />;
if (numberPercent > 60) return <CIcon width={36} name="cil-meh" content={cilMeh} />;
return <CIcon width={36} name="cil-frown" content={cilFrown} />;
};
const FirmwareDashboard = ({ t, data, loading }) => {
const columns = [
{ key: 'endpoint', label: t('common.endpoint'), filter: false, sorter: false },
{ key: 'devices', label: t('common.devices') },
{ key: 'percent', label: '' },
];
return (
<div style={{ position: 'relative' }}>
<div style={{ opacity: loading ? '20%' : '100%' }}>
<CRow className="mt-3">
<CCol>
<CWidgetIcon
text={t('common.last_dashboard_refresh')}
header={data.snapshot ? <FormattedDate date={data.snapshot} size="lg" /> : <h2>-</h2>}
color="info"
iconPadding={false}
>
<CIcon width={36} name="cil-clock" content={cilClock} />
</CWidgetIcon>
</CCol>
<CCol>
<CWidgetIcon
text={t('common.up_to_date')}
header={<h2>{data.latestSoftwareRate}</h2>}
color={getLatestColor(data.latestSoftwareRate)}
iconPadding={false}
>
{getLatestIcon(data.latestSoftwareRate)}
</CWidgetIcon>
</CCol>
<CCol>
<CWidgetIcon
text={t('common.devices')}
header={<h2>{data.numberOfDevices}</h2>}
color="primary"
iconPadding={false}
>
<CIcon width={36} name="cil-router" />
</CWidgetIcon>
</CCol>
<CCol>
<CWidgetIcon
text={
<div>
<div className="float-left">{t('firmware.average_age')}</div>
<div className="float-left ml-2">
<CPopover content={t('firmware.age_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
}
header={<h2>{data.averageFirmwareAge}</h2>}
color="dark"
iconPadding={false}
>
<CIcon width={36} content={cilBirthdayCake} />
</CWidgetIcon>
</CCol>
</CRow>
<CRow>
<CCol>
<CCard>
<CCardHeader className="dark-header">{t('common.firmware_installed')}</CCardHeader>
<CCardBody>
<CChartPie
datasets={data.firmwareDistribution.datasets}
labels={data.firmwareDistribution.labels}
options={{
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol>
<CCard>
<CCardHeader className="dark-header">
<div>
<div className="float-left">{t('common.devices_using_latest')}</div>
<div className="float-left ml-2">
<CPopover content={t('firmware.latest_explanation')}>
<CIcon content={cilInfo} />
</CPopover>
</div>
</div>
</CCardHeader>
<CCardBody>
<CChartBar
datasets={data.latest.datasets}
labels={data.latest.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
yAxes: [
{
ticks: {
maxTicksLimit: 5,
beginAtZero: true,
stepSize: 1,
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol>
<CCard>
<CCardHeader className="dark-header">Unknown Firmware</CCardHeader>
<CCardBody>
<CChartHorizontalBar
datasets={data.unknownFirmwares.datasets}
labels={data.unknownFirmwares.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
xAxes: [
{
ticks: {
maxTicksLimit: 5,
beginAtZero: true,
stepSize: 1,
},
},
],
yAxes: [
{
ticks: {
callback: (value) => value.split(' ')[0],
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
</CRow>
<CRow>
<CCol>
<CCard>
<CCardHeader className="dark-header">{t('common.device_status')}</CCardHeader>
<CCardBody>
<CChartPie
datasets={data.status.datasets}
labels={data.status.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) => `${ds.datasets[0].data[item.index]}%`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol>
<CCard>
<CCardHeader className="dark-header">{t('firmware.device_types')}</CCardHeader>
<CCardBody>
<CChartPie
datasets={data.deviceType.datasets}
labels={data.deviceType.labels}
options={{
tooltips: {
callbacks: {
title: (item, ds) => ds.labels[item[0].index],
label: (item, ds) =>
`${ds.datasets[0].data[item.index]} ${t('common.devices')}`,
},
},
legend: {
display: true,
position: 'right',
},
}}
/>
</CCardBody>
</CCard>
</CCol>
<CCol>
<CCard>
<CCardHeader className="dark-header">OUIs</CCardHeader>
<CCardBody>
<CChartHorizontalBar
datasets={data.ouis.datasets}
labels={data.ouis.labels}
options={{
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'index',
intersect: false,
},
legend: {
display: false,
position: 'right',
},
scales: {
xAxes: [
{
ticks: {
maxTicksLimit: 5,
beginAtZero: true,
stepSize: 1,
},
},
],
yAxes: [
{
ticks: {
callback: (value) => value.split(' ')[0],
},
},
],
},
}}
/>
</CCardBody>
</CCard>
</CCol>
</CRow>
<CRow>
<CCol>
<CCard>
<CCardHeader className="dark-header">{t('common.endpoints')}</CCardHeader>
<CCardBody>
<CDataTable
addTableClasses="table-sm"
items={data.endpoints ?? []}
fields={columns}
hover
border
/>
</CCardBody>
</CCard>
</CCol>
<CCol />
<CCol />
</CRow>
</div>
{loading ? (
<div className={styles.centerContainer}>
<CSpinner className={styles.spinner} />
</div>
) : null}
</div>
);
};
FirmwareDashboard.propTypes = {
t: PropTypes.func.isRequired,
data: PropTypes.instanceOf(Object).isRequired,
loading: PropTypes.bool.isRequired,
};
export default React.memo(FirmwareDashboard);

View File

@@ -0,0 +1,10 @@
.centerContainer {
position: absolute;
top: 5%;
right: 50%;
}
.spinner {
height: 50px;
width: 50px;
}

View File

@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FirmwareDashboard as Dashboard, useAuth, COLOR_LIST } from 'ucentral-libs'; import { useAuth, COLOR_LIST } from 'ucentral-libs';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import Dashboard from './Dashboard';
const FirmwareDashboard = () => { const FirmwareDashboard = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -134,7 +135,7 @@ const FirmwareDashboard = () => {
if ( if (
parsedData.numberOfDevices === undefined || parsedData.numberOfDevices === undefined ||
Number.isNaN(parsedData.numberOfDevices) || Number.isNaN(parsedData.numberOfDevices) ||
parsedData === 0 parsedData.numberOfDevices === 0
) { ) {
parsedData.latestSoftwareRate = '-'; parsedData.latestSoftwareRate = '-';
} else { } else {
@@ -150,10 +151,19 @@ const FirmwareDashboard = () => {
? parsedData.unknownFirmwares.reduce((acc, firmware) => acc + firmware.value, 0) ? parsedData.unknownFirmwares.reduce((acc, firmware) => acc + firmware.value, 0)
: 0; : 0;
const devicesForAverage = parsedData.numberOfDevices - usingUnknownFirmwareFromArray; const devicesForAverage = parsedData.numberOfDevices - usingUnknownFirmwareFromArray;
parsedData.averageFirmwareAge =
parsedData.totalSecondsOld[0].value / if (devicesForAverage !== 0 && parsedData.totalSecondsOld.length > 0) {
(devicesForAverage > 0 ? devicesForAverage : 1) / parsedData.averageFirmwareAge = Math.round(
(24 * 60 * 60); parsedData.totalSecondsOld[0].value /
(devicesForAverage > 0 ? devicesForAverage : 1) /
(24 * 60 * 60),
);
parsedData.averageFirmwareAge = `${parsedData.averageFirmwareAge} ${
Math.round(parsedData.averageFirmwareAge) > 0 ? t('common.days') : t('common.day')
}`;
} else {
parsedData.averageFirmwareAge = '-';
}
// Latest firmware distribution // Latest firmware distribution
const latestDs = []; const latestDs = [];

View File

@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CDataTable } from '@coreui/react';
import { prettyDate } from 'utils/helper';
const FirmwareHistoryModal = ({ t, loading, data }) => {
const columns = [
{ key: 'date', label: '#', _style: { width: '20%' } },
{ key: 'fromRelease', label: t('firmware.from_release'), sorter: false },
{ key: 'toRelease', label: t('firmware.to_release'), sorter: false },
];
return (
<CDataTable
addTableClasses="ignore-overflow table-sm"
fields={columns}
items={data}
hover
border
loading={loading}
sorter
sorterValue={{ column: 'radio', asc: true }}
scopedSlots={{
date: (item) => <td>{prettyDate(item.upgraded)}</td>,
}}
/>
);
};
FirmwareHistoryModal.propTypes = {
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
data: PropTypes.instanceOf(Array).isRequired,
};
export default React.memo(FirmwareHistoryModal);

View File

@@ -10,7 +10,8 @@ import {
CModalFooter, CModalFooter,
CModalTitle, CModalTitle,
} from '@coreui/react'; } from '@coreui/react';
import { FirmwareHistoryTable, useAuth } from 'ucentral-libs'; import { useAuth } from 'ucentral-libs';
import Modal from './Modal';
const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => { const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -51,7 +52,7 @@ const FirmwareHistoryModal = ({ serialNumber, show, toggle }) => {
</CModalTitle> </CModalTitle>
</CModalHeader> </CModalHeader>
<CModalBody> <CModalBody>
<FirmwareHistoryTable t={t} loading={loading} data={data} /> <Modal t={t} loading={loading} data={data} />
</CModalBody> </CModalBody>
<CModalFooter> <CModalFooter>
<CButton color="secondary" onClick={toggle}> <CButton color="secondary" onClick={toggle}>

View File

@@ -12,4 +12,4 @@ DeviceStatisticsChart.propTypes = {
chart: PropTypes.instanceOf(Object).isRequired, chart: PropTypes.instanceOf(Object).isRequired,
}; };
export default DeviceStatisticsChart; export default React.memo(DeviceStatisticsChart);

View File

@@ -51,7 +51,7 @@ const LatestStatisticsModal = ({ show, toggle }) => {
</div> </div>
</CModalHeader> </CModalHeader>
<CModalBody> <CModalBody>
<pre className="ignore">{JSON.stringify(latestStats, null, 4)}</pre> <pre className="ignore">{JSON.stringify(latestStats, null, 2)}</pre>
</CModalBody> </CModalBody>
</CModal> </CModal>
); );

View File

@@ -1,37 +1,104 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { CSpinner } from '@coreui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { v4 as createUuid } from 'uuid'; import { v4 as createUuid } from 'uuid';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import { useAuth, useDevice } from 'ucentral-libs'; import { useAuth, useDevice } from 'ucentral-libs';
import { unixToTime, capitalizeFirstLetter } from 'utils/helper'; import {
import eventBus from 'utils/eventBus'; capitalizeFirstLetter,
datesSameDay,
dateToUnix,
prettyDate,
unixToTime,
} from 'utils/helper';
import DeviceStatisticsChart from './DeviceStatisticsChart'; import DeviceStatisticsChart from './DeviceStatisticsChart';
const StatisticsChartList = () => { const StatisticsChartList = ({ setOptions, section, setStart, setEnd, time }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice(); const { deviceSerialNumber } = useDevice();
const [statOptions, setStatOptions] = useState({ const [statOptions, setStatOptions] = useState({
interfaceList: [], interfaceList: [],
memory: [],
settings: {}, settings: {},
}); });
const transformIntoDataset = (data) => { const transformIntoDataset = (data) => {
const sortedData = data.sort((a, b) => { let sortedData = data.sort((a, b) => {
if (a.recorded > b.recorded) return 1; if (a.recorded > b.recorded) return 1;
if (b.recorded > a.recorded) return -1; if (b.recorded > a.recorded) return -1;
return 0; return 0;
}); });
const dataLength = sortedData.length;
if (dataLength > 1000 && dataLength < 3000) {
sortedData = sortedData.filter((dat, index) => index % 4 === 0);
} else if (dataLength >= 3000 && dataLength < 5000) {
sortedData = sortedData.filter((dat, index) => index % 8 === 0);
} else if (dataLength >= 5000 && dataLength < 7000) {
sortedData = sortedData.filter((dat, index) => index % 12 === 0);
} else if (dataLength > 7000) {
sortedData = sortedData.filter((dat, index) => index % 20 === 0);
}
// Looping through data to build our memory graph data
const memoryUsed = [
{
titleName: t('statistics.memory'),
name: 'Used',
backgroundColor: 'rgb(228,102,81,0.9)',
data: [],
fill: true,
},
{
titleName: t('statistics.memory'),
name: 'Buffered',
backgroundColor: 'rgb(228,102,81,0.9)',
data: [],
fill: true,
},
{
titleName: t('statistics.memory'),
name: 'Cached',
backgroundColor: 'rgb(228,102,81,0.9)',
data: [],
fill: true,
},
];
for (const log of sortedData) {
memoryUsed[0].data.push(
Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024),
);
memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024));
memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024));
}
const newUsed = memoryUsed[0].data;
if (newUsed.length > 0) newUsed.shift();
memoryUsed[0].data = newUsed;
const newBuff = memoryUsed[1].data;
if (newBuff.length > 0) newBuff.shift();
memoryUsed[1].data = newBuff;
const newCached = memoryUsed[2].data;
if (newCached.length > 0) newCached.shift();
memoryUsed[2].data = newCached;
// This dictionary will have a key that is the interface name and a value of it's index in the final array // This dictionary will have a key that is the interface name and a value of it's index in the final array
const interfaceTypes = {}; const interfaceTypes = {};
const interfaceList = []; const interfaceList = [];
const categories = []; const categories = [];
let i = 0; let i = 0;
const areSameDay = datesSameDay(
new Date(sortedData[0].recorded * 1000),
new Date(sortedData[sortedData.length - 1].recorded * 1000),
);
// Just building the array for all the interfaces // Just building the array for all the interfaces
for (const log of sortedData) { for (const log of sortedData) {
categories.push(unixToTime(log.recorded)); categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded));
for (const logInterface of log.data.interfaces) { for (const logInterface of log.data.interfaces) {
if (interfaceTypes[logInterface.name] === undefined) { if (interfaceTypes[logInterface.name] === undefined) {
interfaceTypes[logInterface.name] = i; interfaceTypes[logInterface.name] = i;
@@ -57,22 +124,68 @@ const StatisticsChartList = () => {
} }
// Looping through all the data // Looping through all the data
let prevTx = 0;
let prevRx = 0;
for (const log of sortedData) { for (const log of sortedData) {
// Looping through the interfaces of the log // Looping through the interfaces of the log
const version = log.data.version ?? 0;
for (const inter of log.data.interfaces) { for (const inter of log.data.interfaces) {
interfaceList[interfaceTypes[inter.name]][0].data.push( if (inter.ssids?.length > 0) {
inter.counters?.tx_bytes ? Math.floor(inter.counters.tx_bytes / 1024) : 0, let totalTx = 0;
); let totalRx = 0;
interfaceList[interfaceTypes[inter.name]][1].data.push( for (const ssid of inter.ssids) {
inter.counters?.rx_bytes ? Math.floor(inter.counters.rx_bytes / 1024) : 0, if (ssid.associations) {
); for (const assoc of ssid.associations) {
if (version === 0) {
if (assoc.deltas) {
totalTx += assoc.deltas?.tx_bytes ?? 0;
totalRx += assoc.deltas?.rx_bytes ?? 0;
} else {
totalTx += assoc.tx_bytes ?? 0;
totalRx += assoc.rx_bytes ?? 0;
}
} else {
totalTx += assoc.tx_bytes ?? 0;
totalRx += assoc.rx_bytes ?? 0;
}
}
}
}
if (version > 0) {
const tx = Math.floor(totalTx / 1024);
const rx = Math.floor(totalRx / 1024);
interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(tx - prevTx, 0));
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(rx - prevRx, 0));
prevTx = tx;
prevRx = rx;
} else {
interfaceList[interfaceTypes[inter.name]][0].data.push(Math.floor(totalTx / 1024));
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.floor(totalRx / 1024));
}
} else {
interfaceList[interfaceTypes[inter.name]][0].data.push(
inter.counters ? Math.floor(inter.counters.tx_bytes) : 0,
);
interfaceList[interfaceTypes[inter.name]][1].data.push(
inter.counters ? Math.floor(inter.counters.rx_bytes) : 0,
);
}
} }
} }
const options = { for (let y = 0; y < interfaceList.length; y += 1) {
for (let z = 0; z < interfaceList[y].length; z += 1) {
const newArray = interfaceList[y][z].data;
if (newArray.length > 0) newArray.shift();
interfaceList[y][z].data = newArray;
}
}
const newCategories = categories;
if (newCategories.length > 0) newCategories.shift();
const interfaceOptions = {
chart: { chart: {
id: 'chart', id: 'chart',
group: 'txrx',
}, },
stroke: { stroke: {
curve: 'smooth', curve: 'smooth',
@@ -84,8 +197,8 @@ const StatisticsChartList = () => {
fontSize: '15px', fontSize: '15px',
}, },
}, },
categories, categories: newCategories,
tickAmount: 20, tickAmount: areSameDay ? 15 : 10,
}, },
yaxis: { yaxis: {
labels: { labels: {
@@ -105,76 +218,174 @@ const StatisticsChartList = () => {
}, },
}; };
const memoryOptions = {
chart: {
id: 'chart',
},
stroke: {
curve: 'smooth',
},
xaxis: {
tickAmount: areSameDay ? 15 : 10,
title: {
text: 'Time',
style: {
fontSize: '15px',
},
},
categories,
},
yaxis: {
tickAmount: 5,
title: {
text: t('statistics.data_mb'),
style: {
fontSize: '15px',
},
},
},
legend: {
position: 'top',
horizontalAlign: 'right',
float: true,
},
};
const newOptions = { const newOptions = {
interfaceList, interfaceList,
settings: options, memory: [memoryUsed],
interfaceOptions,
memoryOptions,
start: new Date(sortedData[0].recorded * 1000).toISOString(),
end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(),
}; };
if (statOptions !== newOptions) { if (statOptions !== newOptions) {
setStatOptions(newOptions); const sectionOptions = newOptions.interfaceList.map((opt) => ({
value: opt[0].titleName,
label: opt[0].titleName,
}));
setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]);
setStatOptions({ ...newOptions });
if (sortedData.length > 0) {
setStart(new Date(sortedData[0].recorded * 1000));
setEnd(new Date(sortedData[sortedData.length - 1].recorded * 1000));
}
} }
}; };
const getInterface = useCallback(() => {
if (statOptions.interfaceList.length === 0) return <p>N/A</p>;
const interfaceToShow = statOptions.interfaceList.find(
(inter) => inter[0].titleName === section,
);
if (interfaceToShow) {
const options = {
data: interfaceToShow,
options: {
...statOptions.interfaceOptions,
title: {
text: capitalizeFirstLetter(interfaceToShow[0].titleName),
align: 'left',
style: {
fontSize: '25px',
},
},
},
};
return (
<div key={createUuid()}>
<DeviceStatisticsChart chart={options} />
</div>
);
}
return <p>N/A</p>;
}, [statOptions, section]);
const getStatistics = () => { const getStatistics = () => {
setLoading(true);
const options = { const options = {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${currentToken}`, Authorization: `Bearer ${currentToken}`,
}, },
params: { params: {},
serialNumber: '24f5a207a130',
},
}; };
let extraParams = '';
if (time.start !== null && time.end !== null) {
const utcStart = new Date(time.start).toISOString();
const utcEnd = new Date(time.end).toISOString();
options.params.startDate = dateToUnix(utcStart);
options.params.endDate = dateToUnix(utcEnd);
options.params.limit = 10000;
} else {
extraParams = '?newest=true&limit=50';
}
axiosInstance axiosInstance
.get( .get(
`${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/statistics?newest=true&limit=50`, `${endpoints.owgw}/api/v1/device/${deviceSerialNumber}/statistics${extraParams}`,
options, options,
) )
.then((response) => { .then((response) => {
transformIntoDataset(response.data.data); transformIntoDataset(response.data.data);
}) })
.catch(() => {}); .catch(() => {})
.finally(() => setLoading(false));
}; };
useEffect(() => { useEffect(() => {
if (deviceSerialNumber) { if (deviceSerialNumber) {
getStatistics(); getStatistics();
} }
}, [deviceSerialNumber]); }, [deviceSerialNumber, time.refreshId]);
useEffect(() => {
eventBus.on('refreshInterfaceStatistics', () => getStatistics());
return () => {
eventBus.remove('refreshInterfaceStatistics');
};
}, []);
if (loading) {
return (
<div className="text-center">
<CSpinner size="xl" />
</div>
);
}
return ( return (
<div> <div>
{statOptions.interfaceList.map((data) => { {section !== 'memory' && !loading && getInterface()}
const options = { {section === 'memory' &&
data, !loading &&
options: { statOptions.memory.map((data) => {
...statOptions.settings, const options = {
title: { data,
text: capitalizeFirstLetter(data[0].titleName), options: {
align: 'left', ...statOptions.memoryOptions,
style: { title: {
fontSize: '25px', text: capitalizeFirstLetter(data[0].titleName),
align: 'left',
style: {
fontSize: '25px',
},
}, },
}, },
}, };
}; return (
return ( <div key={createUuid()}>
<div key={createUuid()}> <DeviceStatisticsChart chart={options} section={section} />
<DeviceStatisticsChart chart={options} /> </div>
</div> );
); })}
})}
</div> </div>
); );
}; };
StatisticsChartList.propTypes = {
setOptions: PropTypes.func.isRequired,
section: PropTypes.string.isRequired,
time: PropTypes.instanceOf(Object).isRequired,
setStart: PropTypes.func.isRequired,
setEnd: PropTypes.func.isRequired,
};
export default React.memo(StatisticsChartList); export default React.memo(StatisticsChartList);

View File

@@ -1,73 +1,132 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom'; import { v4 as createUuid } from 'uuid';
import { CCard, CCardHeader, CCardBody, CPopover, CButton } from '@coreui/react'; import {
CCard,
CCardHeader,
CCardBody,
CPopover,
CButton,
CSelect,
CFormText,
} from '@coreui/react';
import DatePicker from 'react-widgets/DatePicker';
import { cilSync } from '@coreui/icons'; import { cilSync } from '@coreui/icons';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import eventBus from 'utils/eventBus';
import LifetimeStatsmodal from 'components/LifetimeStatsModal';
import StatisticsChartList from './StatisticsChartList'; import StatisticsChartList from './StatisticsChartList';
import LatestStatisticsmodal from './LatestStatisticsModal'; import LatestStatisticsmodal from './LatestStatisticsModal';
const DeviceStatisticsCard = () => { const DeviceStatisticsCard = () => {
const history = useHistory();
const { deviceId } = useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [showLatestModal, setShowLatestModal] = useState(false); const [showLatestModal, setShowLatestModal] = useState(false);
const [showLifetimeModal, setShowLifetimeModal] = useState(false); const [options, setOptions] = useState([]);
const [section, setSection] = useState('');
const [start, setStart] = useState(null);
const [startError, setStartError] = useState(false);
const [end, setEnd] = useState(null);
const [endError, setEndError] = useState(false);
const [time, setTime] = useState({ refreshId: '0', start: null, end: null });
const toggleLatestModal = () => { const toggleLatestModal = () => {
setShowLatestModal(!showLatestModal); setShowLatestModal(!showLatestModal);
}; };
const toggleLifetimeModal = () => { const modifyStart = (value) => {
setShowLifetimeModal(!showLifetimeModal); try {
new Date(value).toISOString();
setStartError(false);
setStart(value);
} catch (e) {
setStart('');
setStartError(true);
}
}; };
const goToAnalysis = () => { const modifyEnd = (value) => {
history.push(`/devices/${deviceId}/wifianalysis`); try {
new Date(value).toISOString();
setEndError(false);
setEnd(value);
} catch (e) {
setEnd('');
setEndError(true);
}
}; };
const refresh = () => { const refresh = () => {
eventBus.dispatch('refreshInterfaceStatistics', { message: 'Refresh interface statistics' }); setTime({ refreshId: createUuid(), start, end });
}; };
useEffect(() => {
if (section === '' && options.length > 0) setSection(options[0].value);
}, [options]);
return ( return (
<div> <div>
<CCard> <CCard className="m-0">
<CCardHeader> <CCardHeader className="dark-header">
<div className="d-flex flex-row-reverse align-items-center"> <div className="d-flex flex-row-reverse align-items-center">
<div className="pl-2"> <div className="pl-2">
<CPopover content={t('common.refresh')}> <CPopover content={t('common.refresh')}>
<CButton color="primary" variant="outline" onClick={refresh}> <CButton size="sm" color="info" onClick={refresh} disabled={startError || endError}>
<CIcon content={cilSync} /> <CIcon content={cilSync} />
</CButton> </CButton>
</CPopover> </CPopover>
</div> </div>
<div className="pl-2"> <div className="pl-2">
<CButton color="primary" variant="outline" onClick={toggleLifetimeModal}> <DatePicker
Lifetime Statistics includeTime
</CButton> onChange={(date) => modifyEnd(date)}
value={end ? new Date(end) : undefined}
/>
<CFormText color="danger" hidden={!endError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
To:
<div className="pl-2">
<DatePicker
includeTime
onChange={(date) => modifyStart(date)}
value={start ? new Date(start) : undefined}
/>
<CFormText color="danger" hidden={!startError}>
{t('common.invalid_date_explanation')}
</CFormText>
</div>
From:
<div className="px-2">
<CSelect
custom
value={section}
disabled={options.length === 0}
onChange={(e) => setSection(e.target.value)}
>
{options.map((opt) => (
<option value={opt.value} key={createUuid()}>
{opt.label}
</option>
))}
</CSelect>
</div> </div>
<div className="pl-2"> <div className="pl-2">
<CButton color="primary" variant="outline" onClick={toggleLatestModal}> <CButton size="sm" color="info" onClick={toggleLatestModal}>
{t('statistics.show_latest')} {t('statistics.show_latest')}
</CButton> </CButton>
</div> </div>
<div>
<CButton color="primary" variant="outline" onClick={goToAnalysis}>
{t('wifi_analysis.title')}
</CButton>
</div>
<div className="text-value-lg mr-auto">{t('statistics.title')}</div>
</div> </div>
</CCardHeader> </CCardHeader>
<CCardBody className="p-5"> <CCardBody className="p-1">
<StatisticsChartList /> <StatisticsChartList
setOptions={setOptions}
section={section}
time={time}
setStart={setStart}
setEnd={setEnd}
/>
</CCardBody> </CCardBody>
</CCard> </CCard>
<LatestStatisticsmodal show={showLatestModal} toggle={toggleLatestModal} /> <LatestStatisticsmodal show={showLatestModal} toggle={toggleLatestModal} />
<LifetimeStatsmodal show={showLifetimeModal} toggle={toggleLifetimeModal} />
</div> </div>
); );
}; };

View File

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

View File

@@ -0,0 +1,55 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import ReactFlow, { removeElements, MiniMap, Controls, Background } from 'react-flow-renderer';
const NetworkDiagram = ({ show, elements, setElements }) => {
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const onElementsRemove = (elementsToRemove) => {
setElements((els) => removeElements(elementsToRemove, els));
};
const onLoad = (instance) => {
setReactFlowInstance(instance);
};
useEffect(() => {
if (show && reactFlowInstance !== null && elements.length > 0) {
setTimeout(() => reactFlowInstance.fitView(), 100);
}
}, [show, reactFlowInstance, elements]);
return (
<div style={{ height: '80vh', width: '100%' }}>
<ReactFlow
elements={elements}
onElementsRemove={onElementsRemove}
onLoad={onLoad}
snapToGrid
snapGrid={[20, 20]}
>
<MiniMap
nodeColor={(n) => {
if (n.style?.background) return n.style.background;
return '#fff';
}}
nodeBorderRadius={5}
/>
<Controls />
<Background color="#aaa" gap={20} />
</ReactFlow>
</div>
);
};
NetworkDiagram.propTypes = {
show: PropTypes.bool,
elements: PropTypes.instanceOf(Array).isRequired,
setElements: PropTypes.func.isRequired,
};
NetworkDiagram.defaultProps = {
show: true,
};
export default React.memo(NetworkDiagram);

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CRow, CCol } from '@coreui/react'; import { CRow, CCol } from '@coreui/react';
import { NetworkDiagram as Graph } from 'ucentral-libs';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import createLayoutedElements from './dagreAdapter'; import createLayoutedElements from './dagreAdapter';
import Graph from './Graph';
const associationStyle = { const associationStyle = {
background: '#3399ff', background: '#3399ff',

View File

@@ -20,17 +20,18 @@ import { dateToUnix } from 'utils/helper';
import 'react-widgets/styles.css'; import 'react-widgets/styles.css';
import axiosInstance from 'utils/axiosInstance'; import axiosInstance from 'utils/axiosInstance';
import eventBus from 'utils/eventBus'; import eventBus from 'utils/eventBus';
import { LoadingButton, useAuth, useDevice } from 'ucentral-libs'; import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody'; import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
const ActionModal = ({ show, toggleModal }) => { const ActionModal = ({ show, toggleModal }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentToken, endpoints } = useAuth(); const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice(); const { deviceSerialNumber } = useDevice();
const { addToast } = useToast();
const [waiting, setWaiting] = useState(false); const [waiting, setWaiting] = useState(false);
const [result, setResult] = useState(null); const [result, setResult] = useState(null);
const [chosenDate, setChosenDate] = useState(new Date().toString()); const [chosenDate, setChosenDate] = useState(new Date().toString());
const [isNow, setIsNow] = useState(false); const [isNow, setIsNow] = useState(true);
const toggleNow = () => { const toggleNow = () => {
setIsNow(!isNow); setIsNow(!isNow);
@@ -47,6 +48,7 @@ const ActionModal = ({ show, toggleModal }) => {
setResult(null); setResult(null);
setWaiting(false); setWaiting(false);
setChosenDate(new Date().toString()); setChosenDate(new Date().toString());
setIsNow(true);
} }
}, [show]); }, [show]);
@@ -72,7 +74,13 @@ const ActionModal = ({ show, toggleModal }) => {
{ headers }, { headers },
) )
.then(() => { .then(() => {
setResult('success'); addToast({
title: t('common.success'),
body: t('commands.command_success'),
color: 'success',
autohide: true,
});
toggleModal();
}) })
.catch(() => { .catch(() => {
setResult('error'); setResult('error');

View File

@@ -24,6 +24,7 @@ import { checkIfJson } from 'utils/helper';
const typeOptions = [ const typeOptions = [
{ value: 'wifi-frames', label: 'wifi-frames' }, { value: 'wifi-frames', label: 'wifi-frames' },
{ value: 'dhcp-snooping', label: 'dhcp-snooping' }, { value: 'dhcp-snooping', label: 'dhcp-snooping' },
{ value: 'state', label: 'state' },
]; ];
const TelemetryModal = ({ show, toggle }) => { const TelemetryModal = ({ show, toggle }) => {
@@ -192,7 +193,7 @@ const TelemetryModal = ({ show, toggle }) => {
</CRow> </CRow>
<CRow> <CRow>
<CCol> <CCol>
<pre>{JSON.stringify(lastMessage, null, '\t')}</pre> <pre>{JSON.stringify(lastMessage, null, 2)}</pre>
</CCol> </CCol>
</CRow> </CRow>
<CRow> <CRow>

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