1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2025-07-15 17:33:29 +02:00

Compare commits

..

481 Commits

Author SHA1 Message Date
Aleksandr Gumroian
b0e2f62a41 Add Switch example to the docs 2022-09-16 16:10:44 +02:00
Aleksandr Gumroian
caf8af44d1 Improve docs development section 2022-09-16 16:10:44 +02:00
Aleksandr Gumroian
fd7cd49790 Add custom logo & favicon 2022-09-16 16:10:43 +02:00
Aleksandr Gumroian
d95fdf8517 Restructure styleguide configuration file
* Add version of the library
* Change colors
* Set tocMode  to "collapse"
2022-09-16 16:10:43 +02:00
Aleksandr Gumroian
68c560078b Improve docs introduction section 2022-09-16 16:10:42 +02:00
Aleksandr Gumroian
83caf505d9 Improve description in package.json 2022-09-16 16:10:42 +02:00
Aleksandr Gumroian
d588291f1c Merge branch 'update-peer-deps' into 'dev'
Bump v5.4.1

See merge request turris/reforis/foris-js!198
2022-06-02 18:31:05 +02:00
Aleksandr Gumroian
bc044df7a8 Bump v5.4.1
* Add Weblate translations
* Update PropType peer dependency
* NPM audit fix
2022-06-02 11:12:05 +02:00
Aleksandr Gumroian
b4c6a7fb70 NPM audit fix 2022-06-02 11:12:04 +02:00
Aleksandr Gumroian
d6563d2ffd Update PropTypes library to v15.8.1
As reForis had updated library, there was a conflict in npm
between different versions of the library.

```
npm ERR! While resolving: reforis_js@1.2.1
npm ERR! Found: prop-types@15.8.1
npm ERR! node_modules/prop-types
npm ERR!   prop-types@"^15.8.1" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer prop-types@"15.7.2" from foris@5.4.0
npm ERR! node_modules/foris
npm ERR!   foris@"5.4.0" from the root project
```
2022-06-02 11:08:02 +02:00
Aleksandr Gumroian
af90d8d09d Merge branch 'update-translations' into 'master'
Add Weblate translations

See merge request turris/reforis/foris-js!199
2022-05-31 15:32:51 +02:00
Atec
006d6ce8d9 Translated using Weblate (Slovak)
Currently translated at 100.0% (67 of 67 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2022-05-30 08:14:50 +02:00
Алексей Леньшин
cee08f48ce Translated using Weblate (Russian)
Currently translated at 100.0% (67 of 67 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2022-05-28 11:19:05 +02:00
Allan Nordhøy
2d0ca58057 Translated using Weblate (Norwegian Bokmål)
Currently translated at 68.6% (46 of 67 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nb_NO/
2022-05-25 15:20:48 +02:00
Aleksandr Gumroian
db4a6fb763 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!197
2022-05-20 16:28:35 +02:00
Aleksandr Gumroian
caaa154d21 Merge branch 'bump-540' into 'dev'
Bump v5.4.0

See merge request turris/reforis/foris-js!196
2022-05-20 16:25:58 +02:00
Aleksandr Gumroian
518fa30306 Bump v5.4.0
* Add & update translations
* Add CopyInput bootstrap component
* Update WiFiForm labels and description for wifi ax
* Make WS path in lighttpd mode configurable
* Fix Wi-Fi password helptext string
* NPM audit fix
2022-05-20 16:11:12 +02:00
Aleksandr Gumroian
fb3562373a NPM audit fix 2022-05-20 16:10:53 +02:00
Aleksandr Gumroian
5afbbac74c Merge branch 'fix-password-helptext' into 'dev'
Fix Wi-Fi password help text string & Add translations

See merge request turris/reforis/foris-js!195
2022-05-20 16:06:17 +02:00
Aleksandr Gumroian
f25832432b Update translation messages 2022-05-20 15:43:48 +02:00
Aleksandr Gumroian
926cb2505f Create translation messages 2022-05-20 15:43:12 +02:00
Aleksandr Gumroian
985fd08b46 Update Snapshots 2022-05-20 15:42:37 +02:00
Aleksandr Gumroian
da019b6d86 Fix Wi-Fi password helptext string 2022-05-20 15:41:59 +02:00
Aleksandr Gumroian
514f02a5f6 Merge branch 'master' into fix-password-helptext 2022-05-20 15:41:39 +02:00
Aleksandr Gumroian
c6557aae5e Merge branch 'update-translations' into 'master'
Update translations

See merge request turris/reforis/foris-js!194
2022-05-20 15:36:47 +02:00
Aleksandr Gumroian
92ed7f1ee7 Merge branch 'add-copyinput' into 'dev'
Add CopyInput bootstrap component

See merge request turris/reforis/foris-js!192
2022-05-19 16:09:31 +02:00
Aleksandr Gumroian
46ce6ebbb9 Add CopyInput bootstrap component 2022-05-19 15:56:10 +02:00
Aleksandr Gumroian
1a4ba03ff5 Wrap Input component with forwardRef
In order to pass `ref` to the child <input> DOM element.
2022-05-19 15:56:09 +02:00
Aleksandr Gumroian
e24cd76009 Merge branch 'feature/update-wifi-ax-labels-and-description' into 'dev'
Update WiFiForm labels and description for wifi ax

See merge request turris/reforis/foris-js!193
2022-04-19 16:13:46 +02:00
Aleksandr Gumroian
ae6b495683 Update Snapshots 2022-04-19 16:06:11 +02:00
Martin Matějek
272c97dc8a Update WiFiForm labels and description for wifi ax 2022-04-19 15:38:51 +02:00
Aleksandr Gumroian
fd25ae04a8 Merge branch 'feature/make-ws-path-in-ligttpd-mode-configurable' into 'dev'
Feature/make ws path in lighttpd mode configurable

See merge request turris/reforis/foris-js!191
2022-04-08 16:10:40 +02:00
Stepan Henek
cc1b0b3f81 Make WS path in lighttpd mode configurable 2022-03-23 11:12:46 +01:00
Atec
3ba279f42c Translated using Weblate (Slovak)
Currently translated at 100.0% (65 of 65 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2022-03-17 18:58:25 +01:00
Koli
0167b7ce66 Translated using Weblate (Czech)
Currently translated at 78.4% (51 of 65 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/cs/
2022-03-15 23:41:00 +01:00
Алексей Леньшин
9db509ace3 Translated using Weblate (Russian)
Currently translated at 100.0% (65 of 65 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2022-03-12 07:58:23 +01:00
Atec
d42347f169 Translated using Weblate (Slovak)
Currently translated at 100.0% (65 of 65 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2022-03-11 00:09:51 +01:00
Allan Nordhøy
82c34e5d42 Translated using Weblate (Norwegian Bokmål)
Currently translated at 67.6% (44 of 65 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nb_NO/
2022-03-11 00:09:51 +01:00
Aleksandr Gumroian
7867a1a494 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!185
2022-02-28 17:28:56 +01:00
Aleksandr Gumroian
1bb8abd633 Merge branch 'bump-530' into 'dev'
Bump v5.3.0

See merge request turris/reforis/foris-js!187
2022-02-28 17:01:33 +01:00
Aleksandr Gumroian
536ccc0f03 Bump v5.3.0
* Add & update translations
* Add rest of the props to DownloadButton component
* Add hostname validation
* Add wifi 802.11ax HE modes
* Set best Wi-Fi HT mode depending on the checked frequency
* Improve domain name RegEx pattern
* Remove customOrder prop in Select component
* Fix Wi-Fi translation strings
* Fix autocomplete attribute in PasswordInput
* Fix WiFi password max length check
* Fix documentation build
* Fix access token in publish script
* Refine & restructure Makefile
* Update GitLab CI image to Node.js v16
* NPM update (several dependencies)
* NPM audit fix
2022-02-28 16:57:37 +01:00
Aleksandr Gumroian
671c711a33 Fix access token in publish script
As npm has a new access token format
https://github.blog/changelog/2021-09-23-npm-has-a-new-access-token-format/
2022-02-28 16:47:52 +01:00
Aleksandr Gumroian
e2f3e6857e NPM update (several dependencies)
* qrcode.react to v1.0.1
* react-datetime to v3.1.1
* moment-timezone to v0.5.34
2022-02-28 15:49:09 +01:00
Aleksandr Gumroian
fe4ab298d8 NPM audit fix 2022-02-28 15:49:08 +01:00
Aleksandr Gumroian
0f7f89dfc0 Merge branch 'refine-makefile' into 'dev'
Refine Makefile & update GitLab CI image

See merge request turris/reforis/foris-js!189
2022-02-28 15:40:53 +01:00
Aleksandr Gumroian
be2e3fe3f0 Update snapshots 2022-02-22 17:03:35 +01:00
Aleksandr Gumroian
577ad70c06 Update translation messages 2022-02-22 16:46:22 +01:00
Aleksandr Gumroian
d17638eb6e Create translation messages 2022-02-22 16:43:31 +01:00
Aleksandr Gumroian
13869336db Fix Wi-Fi translation strings 2022-02-22 16:38:29 +01:00
Aleksandr Gumroian
7c46abcd5d gitlab-ci: Update Node.js image to v16 2022-02-22 16:38:19 +01:00
Aleksandr Gumroian
894d92b683 Makefile: Fix spelling mistakes in echo statements 2022-02-22 16:38:10 +01:00
Aleksandr Gumroian
ca335ab3a5 Makefile: Add test-js-watch recipe 2022-02-22 16:37:52 +01:00
Aleksandr Gumroian
2161fc0b32 Makefile: Divide phony targets & restructure recipes 2022-02-22 16:37:45 +01:00
Aleksandr Gumroian
0a23506a38 Fix forisjs.pot template's header comment 2022-02-22 16:37:35 +01:00
Aleksandr Gumroian
d23c7cb790 Merge branch 'master' into refine-makefile 2022-02-22 16:35:15 +01:00
Aleksandr Gumroian
129327cfcf Merge branch 'update-translations' into 'master'
Add & update translations

See merge request turris/reforis/foris-js!188
2022-02-21 17:58:04 +01:00
Aleksandr Gumroian
0a143e7de6 Merge branch 'add-hostname-validation' into 'dev'
Add hostname validation

See merge request turris/reforis/foris-js!181
2022-02-21 14:09:16 +01:00
Aleksandr Gumroian
7ec1c46a63 Add tests for hostname validation 2022-02-21 13:57:34 +01:00
Aleksandr Gumroian
7ceccd5222 Add hostname RegEx pattern & validateHostname() function 2022-02-21 13:57:24 +01:00
Aleksandr Gumroian
f868881b3d Improve domain name RegEx pattern
Previously validateDomain() function was used for hostname validations
but was weak in a chain of validations, for example, domain -> ipv4
as it accepts invalid IPv4 addresses.

So we had to split it, improve the domain name RegEx pattern and add a
hostname validation pattern.
2022-02-21 13:56:32 +01:00
Aleksandr Gumroian
188ed87fc0 Merge branch 'feature/wifi-ax-he-modes' into 'dev'
Add wifi 802.11ax HE modes

See merge request turris/reforis/foris-js!184
2022-02-21 13:31:45 +01:00
Aleksandr Gumroian
c0f64e8c6c Fix tests
"Post form: 2.4 GHz" test was failing because of added new functionality
in the previous 0194684 commit.
2022-02-21 11:28:25 +01:00
Aleksandr Gumroian
b95cfb664d Set best Wi-Fi HT mode depending on the checked frequency 2022-02-21 11:28:25 +01:00
Aleksandr Gumroian
52cdaf5bc5 Update Snapshots 2022-02-21 11:28:24 +01:00
Aleksandr Gumroian
175a07a865 Remove customOrder prop
As some options of Select component should be ordered by values or keys,
it was decided to handle sorting not in options, but locally in
corresponding lists.
2022-02-21 11:28:24 +01:00
Martin Matějek
f952e25205 Add wifi 802.11ax HE modes 2022-02-21 11:28:24 +01:00
Aleksandr Gumroian
01eeb06f9e Merge branch 'improve-password-inputs' into 'dev'
Fix autocomplete attribute in PasswordInput

Closes #23

See merge request turris/reforis/foris-js!179
2022-02-18 17:42:58 +01:00
Aleksandr Gumroian
839e227feb Update Snapshots 2022-02-18 17:40:48 +01:00
Aleksandr Gumroian
4b239ed195 Fix autocomplete attribute in PasswordInput 2022-02-18 17:40:48 +01:00
Aleksandr Gumroian
2bcbe0ae59 Merge branch 'add-props-downbutton' into 'dev'
Add rest of the props to DownloadButton component

See merge request turris/reforis/foris-js!180
2022-02-11 15:51:19 +01:00
Aleksandr Gumroian
b1ff608337 Add rest of the props to DownloadButton component 2022-02-11 15:49:39 +01:00
Aleksandr Gumroian
b662ba5b52 Merge branch 'fix-styleguidist' into 'dev'
Fix react-styleguidist build

See merge request turris/reforis/foris-js!182
2022-02-11 13:15:22 +01:00
Aleksandr Gumroian
a4115245fe NPM audit fix 2022-02-08 15:16:04 +01:00
Aleksandr Gumroian
e1a893874a Update webpack & react-styleguidist dependencies 2022-02-08 15:16:03 +01:00
Aleksandr Gumroian
dd383ef1b2 Merge branch 'fix-wifi-password-maxlength' into 'dev'
Fix WiFi password max length check

Closes #25

See merge request turris/reforis/foris-js!178
2022-01-20 10:11:12 +01:00
Aleksandr Gumroian
b6e2cb71bb Add tests for password length validation 2022-01-20 12:08:30 +03:00
048e686185 Fix WiFi password max length check
The WiFi password cannot be longer than 63 symbols.
2022-01-20 12:08:06 +03:00
eacb2f66a3 Translated using Weblate (French)
Currently translated at 100.0% (58 of 58 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/fr/
2022-01-14 06:53:49 +01:00
c10l
2433641f56 Translated using Weblate (Portuguese (Brazil))
Currently translated at 8.6% (5 of 58 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/pt_BR/
2021-12-21 13:52:41 +01:00
c10l
185d2e6436 Added translation using Weblate (Portuguese (Brazil)) 2021-12-20 11:31:53 +01:00
Atec
7f262d4941 Translated using Weblate (Slovak)
Currently translated at 100.0% (58 of 58 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2021-12-18 01:07:51 +01:00
Алексей Леньшин
72356bb6c1 Translated using Weblate (Russian)
Currently translated at 100.0% (58 of 58 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2021-12-18 01:07:50 +01:00
Aleksandr Gumroian
13c94caeb5 Merge branch 'add-translations' into 'master'
Add & update Weblate translations

See merge request turris/reforis/foris-js!175
2021-12-15 17:20:16 +01:00
Aleksandr Gumroian
c24e58fae8 Update translation messages 2021-12-15 19:18:22 +03:00
Aleksandr Gumroian
6329b5a104 Create translation messages 2021-12-15 19:17:57 +03:00
J. Lavoie
fbac503586 Translated using Weblate (French)
Currently translated at 100.0% (52 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/fr/
2021-12-15 17:12:50 +01:00
Allan Nordhøy
550af8967c Translated using Weblate (Norwegian Bokmål)
Currently translated at 82.6% (43 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nb_NO/
2021-12-15 17:12:50 +01:00
Allan Nordhøy
3640d6a90a Translated using Weblate (Norwegian Bokmål)
Currently translated at 80.7% (42 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nb_NO/
2021-12-15 17:12:50 +01:00
Kristoffer Grundström
7b2bc43f3f Translated using Weblate (Swedish)
Currently translated at 51.9% (27 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sv/
2021-12-15 17:12:50 +01:00
CryptKid
1e693b0963 Translated using Weblate (German)
Currently translated at 92.3% (48 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/de/
2021-12-15 17:12:50 +01:00
Atec
afde04c662 Translated using Weblate (Slovak)
Currently translated at 100.0% (52 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2021-12-15 17:12:50 +01:00
Lukas Jelinek
22fb7dcf58 Translated using Weblate (Czech)
Currently translated at 100.0% (52 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/cs/
2021-12-15 17:12:50 +01:00
Алексей Леньшин
b557b67308 Translated using Weblate (Russian)
Currently translated at 100.0% (52 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2021-12-15 17:12:50 +01:00
Atec
cc6e5e2933 Translated using Weblate (Slovak)
Currently translated at 100.0% (52 of 52 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2021-12-15 17:12:50 +01:00
Aleksandr Gumroian
60f850a095 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!177
2021-12-15 17:12:43 +01:00
Aleksandr Gumroian
a1e9f23620 Merge branch 'bump-520' into 'dev'
Bump v5.2.0

See merge request turris/reforis/foris-js!176
2021-12-15 17:09:05 +01:00
Aleksandr Gumroian
579ed5ea8c Bump v5.2.0
* Remove login page
* NPM audit fix
2021-12-15 19:05:53 +03:00
Aleksandr Gumroian
c2eda33998 NPM audit fix 2021-12-15 19:05:26 +03:00
Aleksandr Gumroian
f49529018c Merge branch 'remove-login' into 'dev'
Remove reForis login page

Closes reforis#355

See merge request turris/reforis/foris-js!167
2021-12-15 16:59:46 +01:00
Marek Sašek
a66a2f4708 Remove reForis login page 2021-12-13 15:51:56 +01:00
Marek Sasek
2e473003bd Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!174
2021-11-18 18:07:17 +01:00
Marek Sašek
43cb5bff50 Bump v5.1.16
* NPM audit fix
2021-11-18 17:49:13 +01:00
Marek Sašek
c67ea089fd NPM audit fix 2021-11-18 17:49:12 +01:00
Marek Sašek
4b25f6eafc Revert "NPM audit fix"
This reverts commit 6e6c349866.
2021-11-18 17:49:12 +01:00
Marek Sasek
c1e807bc74 Merge branch 'dev' into 'master'
Bump v5.1.15

See merge request turris/reforis/foris-js!171
2021-11-03 15:28:21 +01:00
Marek Sasek
69da5afffe Merge branch 'merging-dev' into 'dev'
Bump v5.1.15

See merge request turris/reforis/foris-js!170
2021-11-03 15:23:03 +01:00
Marek Sašek
1669ac8576 Bump v5.1.15
* Add WPA3 option
* Add custom order ability of Select options
* NPM audit fix
2021-11-03 13:40:40 +01:00
Marek Sašek
6e6c349866 NPM audit fix 2021-11-03 13:38:54 +01:00
Marek Sašek
5207029462 Update snapshots 2021-11-03 13:31:10 +01:00
Marek Sašek
53aec6372d Update tests 2021-11-03 13:31:09 +01:00
Marek Sašek
a7d7e59028 Add custom order ability of Select options 2021-11-03 13:31:09 +01:00
Marek Sašek
0beb1f0418 Add WPA3 option 2021-11-03 13:31:08 +01:00
Aleksandr Gumroian
2644f6fd70 Merge branch 'update-translations' into 'master'
Add & update translations

See merge request turris/reforis/foris-js!165
2021-07-30 14:10:28 +00:00
Aleksandr Gumroian
585fec4e3e Bump v5.1.14
* Add & update translations
* Fix infinity redirect loop when WS error occurs
* NPM audit fix
2021-07-30 17:02:51 +03:00
Aleksandr Gumroian
682abc126a Update translation messages 2021-07-30 16:59:02 +03:00
Aleksandr Gumroian
a9f3f77bd5 Create translation messages 2021-07-30 16:58:34 +03:00
Kristoffer Grundström
4703721c5c Translated using Weblate (Swedish)
Currently translated at 52.0% (26 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sv/
2021-07-30 15:56:22 +02:00
Atec
aff1ba7b6d Translated using Weblate (Slovak)
Currently translated at 100.0% (50 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2021-07-30 15:56:22 +02:00
Atec
9eb7197035 Translated using Weblate (Slovak)
Currently translated at 100.0% (50 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sk/
2021-07-30 15:56:22 +02:00
Aleksandr Gumroian
462a86b31d Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!166
2021-07-30 13:56:18 +00:00
Aleksandr Gumroian
cbce4c1ec1 Merge branch 'fix-redirect-loop' into 'dev'
Fix infinity redirect loop when WS error occurs

See merge request turris/reforis/foris-js!161
2021-07-30 13:48:38 +00:00
Aleksandr Gumroian
aee19694b5 NPM audit fix 2021-07-30 13:49:52 +03:00
Aleksandr Gumroian
f3b1ef741a Fix infinity redirect loop when WS error occurs
There were situations when reForis infinity loop had occurred when the user was
already logged in, but the client rejected the `wss` connection. The location
path was not `/login`, and an infinity redirect took place. This should fix it.
2021-07-27 11:29:59 +03:00
Aleksandr Gumroian
c35a4a8236 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!164
2021-06-30 12:34:05 +00:00
Aleksandr Gumroian
67b8386cd0 Merge branch 'bump-5113' into 'dev'
Bump v5.1.13

See merge request turris/reforis/foris-js!163
2021-06-30 11:56:44 +00:00
Aleksandr Gumroian
f67edc39e1 Bump v5.1.13
* Add sentinelAgreement endpoint to forisUrls
* NPM audit fix
2021-06-30 10:51:03 +02:00
Aleksandr Gumroian
6f0f344eb4 NPM audit fix 2021-06-30 10:45:30 +02:00
Aleksandr Gumroian
3a39e44c34 Merge branch 'add-data-collection-endpoint' into 'dev'
Add sentinelAgreement endpoint to forisUrls

See merge request turris/reforis/foris-js!162
2021-06-30 08:41:09 +00:00
Aleksandr Gumroian
cff5f1e5e1 Add sentinelAgreement endpoint to forisUrls 2021-06-29 17:39:53 +02:00
Aleksandr Gumroian
b7bab92d5d Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!160
2021-05-14 11:55:44 +00:00
Aleksandr Gumroian
75dd0fec92 Merge branch 'fix-wifi' into 'dev'
Expend library with the ResetWifiSettings function

Closes #20

See merge request turris/reforis/foris-js!155
2021-05-14 11:46:20 +00:00
Aleksandr Gumroian
3619532124 Bump v5.1.12
* Add & update translations
* Add & fix obsolete links
* Expend library with the ResetWifiSettings function
* Fix switching Wi-Fi modes depending on bands in WiFiForm
* Fix translation sources in WiFiForm
* NPM audit fix
* Other small improvements
2021-05-14 13:38:01 +02:00
Aleksandr Gumroian
ce62fd1043 Update Snapshots 2021-05-14 13:38:00 +02:00
Aleksandr Gumroian
c5bac99d8e NPM audit fix 2021-05-14 13:38:00 +02:00
Aleksandr Gumroian
f7146e3b14 Fix obsolete rebootPage link in forisUrls 2021-05-14 13:37:59 +02:00
Aleksandr Gumroian
18ba90567c Fix fuzzy translation messages in English catalog 2021-05-14 13:37:59 +02:00
Aleksandr Gumroian
2e9da55df7 Fix ids in wifiSettings fixture 2021-05-14 13:37:59 +02:00
Aleksandr Gumroian
da10a34d64 Revert "Fix reForis infinity redirect loop when WS error occurs"
It turned out that this fix doesn't work as expected in some cases.

This reverts commit 7505302875.
2021-05-14 13:37:58 +02:00
Aleksandr Gumroian
764a6c86cd Expend library with the ResetWifiSettings function
Use named export instead of the default for ResetWifiSettings,
as we want to use it not only inside the WiFiSettings component.
2021-05-14 13:37:58 +02:00
Aleksandr Gumroian
6059ce9e7b Merge branch 'fix-wifi-modes' into 'dev'
Fix switching Wi-Fi modes depending on bands in WiFiForm

Closes reforis#292

See merge request turris/reforis/foris-js!159
2021-05-14 11:11:01 +00:00
Marek Sašek
4368bea2c2 Update tests 2021-05-14 13:06:48 +02:00
Marek Sašek
9dd6bbca90 Fix switching Wi-Fi modes depending on bands in WiFiForm 2021-05-14 13:06:48 +02:00
Marek Sasek
d5bb99570c Merge branch 'add-packages-url' into 'dev'
Add Packages URL

See merge request turris/reforis/foris-js!158
2021-04-29 15:48:13 +00:00
Marek Sašek
e1260a5ea1 Add Packages URL into forisUrls 2021-04-21 14:10:23 +02:00
Josef Schlehofer
02f2c5be4f Merge branch 'update-translations' into 'dev'
Add & update translations

See merge request turris/reforis/foris-js!156
2021-04-08 18:06:53 +00:00
Aleksandr Gumroian
ce04f6c27e Merge remote-tracking branch 'weblate/master' into update-translations 2021-04-08 15:57:34 +02:00
Aleksandr Gumroian
80d4dd914d Merge branch 'fix-ios-redirect-loop' into 'dev'
Fix iOS redirect loop

Closes #19

See merge request turris/reforis/foris-js!154
2021-03-26 10:31:58 +00:00
Aleksandr Gumroian
7f82b2e73c NPM audit fix 2021-03-26 11:11:13 +01:00
Aleksandr Gumroian
ac8646a4e7 Update package-lock.json by npm v7
The lockfile v2 is used by npm v7, which is backwards compatible
to v1 lockfiles.

For more information:
https://docs.npmjs.com/cli/v7/configuring-npm/package-lock-json#lockfileversion
2021-03-26 11:11:13 +01:00
Aleksandr Gumroian
7505302875 Fix reForis infinity redirect loop when WS error occurs 2021-03-26 11:11:12 +01:00
Aleksandr Gumroian
adc6bbca14 Merge branch 'fix-translation-sources' into 'dev'
Fix translation sources in WiFiForm

See merge request turris/reforis/foris-js!153
2021-03-24 10:25:19 +00:00
Aleksandr Gumroian
86f98148c6 Fix translation sources in WiFiForm 2021-03-03 13:18:09 +01:00
Алексей Леньшин
f623b98acc Translated using Weblate (Russian)
Currently translated at 100.0% (50 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2021-02-19 06:50:27 +01:00
Lukas Jelinek
3be1213b3b Translated using Weblate (Czech)
Currently translated at 100.0% (50 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/cs/
2021-02-17 15:50:37 +01:00
Artem
09007b922e Translated using Weblate (Russian)
Currently translated at 98.0% (49 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2021-02-14 16:50:25 +01:00
Martin Novák
f6231370b9 Translated using Weblate (Czech)
Currently translated at 86.0% (43 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/cs/
2021-02-14 16:50:25 +01:00
Yuraï Slovaque
449b93ce41 Translated using Weblate (French)
Currently translated at 56.0% (28 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/fr/
2021-02-09 17:50:37 +01:00
Artem
764c8dedd8 Translated using Weblate (Russian)
Currently translated at 96.0% (48 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2021-02-09 17:50:37 +01:00
Michalis
9bfd20ef0c Translated using Weblate (Greek)
Currently translated at 8.0% (4 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/el/
2021-02-09 17:50:37 +01:00
Martin Novák
0289c5010f Translated using Weblate (Czech)
Currently translated at 80.0% (40 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/cs/
2021-02-09 17:50:37 +01:00
Artem
1733b8609b Translated using Weblate (Russian)
Currently translated at 88.0% (44 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2021-02-06 00:41:58 +01:00
Michalis
d5c3365fdb Translated using Weblate (Greek)
Currently translated at 8.0% (4 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/el/
2021-02-06 00:41:57 +01:00
Allan Nordhøy
0ba4814275 Translated using Weblate (Norwegian Bokmål)
Currently translated at 82.0% (41 of 50 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nb_NO/
2021-02-06 00:41:57 +01:00
Aleksandr Gumroian
fca410ec82 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!152
2021-02-04 13:11:19 +01:00
Aleksandr Gumroian
4f09c2da9a Merge branch 'fix-translations' into 'dev'
Fix translations

See merge request turris/reforis/foris-js!151
2021-02-04 12:43:20 +01:00
Aleksandr Gumroian
57ef9c4ea0 Bump v5.1.11
* Remove duplicated file for Norwegian language
* Fix translations inconsistency
2021-02-04 12:14:29 +01:00
Aleksandr Gumroian
b7695cc854 Remove duplicated file for Norwegian language
I noticed thanks to Weblate that there are two files for the same
language and I found this site:
http://people.skolelinux.org/pere/blog/Spr_kkoder_for_POSIX_locale_i_Norge.html

We should use nb_NO and remove nb folder.
2021-02-04 12:12:39 +01:00
Aleksandr Gumroian
fd8b8b926a Merge remote-tracking branch 'weblate/master' into fix-translations 2021-02-03 18:09:37 +01:00
Aleksandr Gumroian
b91ec527d1 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!150
2021-02-03 14:41:59 +01:00
Aleksandr Gumroian
7369d906b5 Merge branch 'bump-5110' into 'dev'
Bump v5.1.10

See merge request turris/reforis/foris-js!149
2021-02-02 10:26:00 +01:00
Aleksandr Gumroian
45fee77426 Bump v5.1.10
* Add and update translations
2021-01-29 17:14:03 +01:00
Aleksandr Gumroian
b12cba893e Merge branch 'new-translations' into 'dev'
Add new translations

See merge request turris/reforis/foris-js!148
2021-01-29 13:49:51 +01:00
Aleksandr Gumroian
09d1698647 Update translation messages 2021-01-28 11:45:37 +01:00
Aleksandr Gumroian
83c05c6c89 Create translation messages 2021-01-28 11:44:40 +01:00
Aleksandr Gumroian
a08de54ca1 Makefile: update Python version 2021-01-28 11:43:36 +01:00
Zoli
cb5fa4ce34 Translated using Weblate (Hungarian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/hu/
2021-01-28 11:37:10 +01:00
Adam Stańczyk
fb32c84dc2 Translated using Weblate (Polish)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/pl/
2021-01-28 11:37:00 +01:00
Johan van de Wetering
4060b3c916 Translated using Weblate (Dutch)
Currently translated at 5.5% (1 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nl/
2021-01-28 11:36:43 +01:00
Eduardo Cuthbert
7abfd627e4 Translated using Weblate (Spanish)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/es/
2021-01-28 11:36:14 +01:00
Aleksandr Gumroian
0fbc3df247 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!147
2021-01-21 10:57:46 +01:00
Aleksandr Gumroian
bc9c00d3a1 Merge branch 'bump-519' into 'dev'
Bump v5.1.9

See merge request turris/reforis/foris-js!146
2021-01-21 10:30:32 +01:00
Aleksandr Gumroian
8d75b5ec6e Bump v5.1.9
* Fix trailing space in Modal classes
* Change formFieldsSize of ResetWiFiSettings card
* Increase bottom margin of formFieldsSize
2021-01-20 11:42:24 +01:00
Aleksandr Gumroian
c1aa1948b4 Merge branch 'fix-wifi-layout' into 'dev'
Fix Wi-Fi layout

See merge request turris/reforis/foris-js!145
2021-01-19 16:42:17 +01:00
Aleksandr Gumroian
8c110ebf52 NPM audit fix 2021-01-18 23:31:43 +01:00
Aleksandr Gumroian
abb5be53aa Fix trailing space in Modal classes 2021-01-18 23:10:39 +01:00
Aleksandr Gumroian
af0fb80e45 Update Snapshots 2021-01-18 23:10:39 +01:00
Aleksandr Gumroian
688192504f Change formFieldsSize of ResetWiFiSettings card 2021-01-18 22:06:20 +01:00
Aleksandr Gumroian
b8e5dbec8d Increase bottom margin of formFieldsSize 2021-01-18 22:05:56 +01:00
Zoli
bcb5365d08 Translated using Weblate (Hungarian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/hu/
2021-01-07 02:26:22 +01:00
Adam Stańczyk
037d1993c8 Translated using Weblate (Polish)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/pl/
2020-12-23 13:29:13 +01:00
Marek Sasek
2287ddc420 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!144
2020-12-20 18:54:48 +01:00
Marek Sasek
fde751a25f Merge branch 'check-installed-plugins' into 'dev'
Add isPluginInstalled function

See merge request turris/reforis/foris-js!143
2020-12-20 18:47:06 +01:00
Marek Sašek
79006cfb99 Bump v5.1.8 2020-12-19 00:25:44 +01:00
Marek Sašek
de398901f3 Add isPluginInstalled function 2020-12-19 00:14:05 +01:00
Johan van de Wetering
bea429d6ac Translated using Weblate (Dutch)
Currently translated at 5.5% (1 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nl/
2020-11-29 20:29:03 +01:00
Marek Sasek
e818120986 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!142
2020-11-27 17:55:01 +01:00
Marek Sasek
56173d4959 Merge branch 'add-storage-link' into 'dev'
Add storage link

See merge request turris/reforis/foris-js!141
2020-11-27 17:09:55 +01:00
Marek Sašek
7c837d041e Bump v5.1.7 2020-11-27 15:37:07 +01:00
Marek Sašek
473c81f9a4 Add storage link 2020-11-27 15:37:02 +01:00
Aleksandr Gumroian
ba9abca5cf Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!140
2020-11-27 13:03:14 +01:00
Aleksandr Gumroian
15567a7dde Merge branch 'release-516' into 'dev'
Bump v5.1.6

Closes #18

See merge request turris/reforis/foris-js!139
2020-11-27 12:30:39 +01:00
Aleksandr Gumroian
e2695d49a1 Bump v5.1.6
* NPM audit fix
* Add displayCard function to utils
* Add optional sizes to Modal
* Add information about optional sizes to docs
* Remove redundant merge.py
2020-11-25 23:29:42 +01:00
Aleksandr Gumroian
a87e6858bf Remove redundant merge.py 2020-11-25 23:29:31 +01:00
Aleksandr Gumroian
e864de5a24 Merge branch 'add-optional-sizes-modals' into 'dev'
Add optional sizes to Modals

See merge request turris/reforis/foris-js!138
2020-11-23 22:25:18 +01:00
Aleksandr Gumroian
5469e6ec80 Add displayCard function to utils 2020-11-22 23:45:27 +01:00
Aleksandr Gumroian
4898016388 Update Snapshots 2020-11-20 17:02:10 +01:00
Aleksandr Gumroian
e0fab75c69 NPM audit fix 2020-11-20 17:00:26 +01:00
Aleksandr Gumroian
6480a39cdb Add information about optional sizes to docs 2020-11-20 16:56:25 +01:00
Aleksandr Gumroian
6f05d5d136 Add optional sizes to Modal 2020-11-20 16:56:16 +01:00
Aleksandr Gumroian
96150fe230 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!137
2020-09-29 11:01:27 +02:00
Aleksandr Gumroian
0892a1534a Merge branch 'release-v5.1.5' into 'dev'
Release v5.1.5

See merge request turris/reforis/foris-js!136
2020-09-29 10:56:55 +02:00
Aleksandr Gumroian
1bac60e054 Bump v5.1.5 2020-09-25 19:27:58 +02:00
Aleksandr Gumroian
328e568ab3 NPM audit fix 2020-09-25 19:26:57 +02:00
Aleksandr Gumroian
c68389359e Update Snapshots 2020-09-25 18:52:25 +02:00
Aleksandr Gumroian
e03e0f44cc Fix extra empty space in Switch's classes 2020-09-25 18:50:04 +02:00
Aleksandr Gumroian
1e04d34645 Fix DateTime import 2020-09-25 18:50:04 +02:00
Aleksandr Gumroian
187ecc54e5 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!135
2020-09-25 17:53:36 +02:00
Aleksandr Gumroian
ed7cf34e76 Merge branch 'release-v5.1.4' into 'dev'
Release v5.1.4

See merge request turris/reforis/foris-js!133
2020-09-25 17:43:10 +02:00
Aleksandr Gumroian
aaf4087c96 Update Snapshots 2020-09-25 17:32:04 +02:00
Aleksandr Gumroian
240db88661 Bump v5.1.4 2020-09-25 17:27:47 +02:00
Aleksandr Gumroian
913a7d7b75 Add closing bootstrap modal using ESC 2020-09-25 17:27:47 +02:00
Aleksandr Gumroian
bdc8726791 Change reboot modal's heading to "Warning!" 2020-09-25 17:27:46 +02:00
Aleksandr Gumroian
1c986519f6 Fix Alert's dismissible class condition 2020-09-25 17:27:46 +02:00
Aleksandr Gumroian
defc363f01 Add inline option to Wi-Fi's RadioSet 2020-09-14 18:48:30 +02:00
Marek Sasek
ef66fb43cc Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!131
2020-09-11 18:14:34 +02:00
Marek Sasek
69723f6b0b Merge branch 'new-bump' into 'dev'
Bump v5.1.3

See merge request turris/reforis/foris-js!130
2020-09-11 18:08:32 +02:00
Marek Sašek
c32137e29a Bump v5.1.3 2020-09-11 18:00:05 +02:00
Marek Sasek
03cf73be6e Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!129
2020-09-11 17:50:07 +02:00
Marek Sasek
be7349661f Merge branch 'ssid-validation' into 'dev'
Ssid validation

Closes reforis#218

See merge request turris/reforis/foris-js!128
2020-09-11 17:39:05 +02:00
Marek Sašek
5186385b9f Update snapshots 2020-09-11 17:32:46 +02:00
Marek Sašek
002786d073 Add test 2020-09-11 17:32:46 +02:00
Marek Sašek
4d246540c1 Add SSID validation for bytes count 2020-09-11 17:32:45 +02:00
Marek Sašek
35b97ec0fe Add validation for SSID with diacritic 2020-09-11 17:32:45 +02:00
Aleksandr Gumroian
d2688532af Merge branch 'dev' into 'master'
Bump v5.1.2

See merge request turris/reforis/foris-js!127
2020-09-08 18:40:57 +02:00
Aleksandr Gumroian
e1d75d8328 Merge branch 'release-v5.1.2' into 'dev'
Bump v5.1.2

See merge request turris/reforis/foris-js!126
2020-09-08 18:36:28 +02:00
Aleksandr Gumroian
0f85713483 Bump v5.1.2 2020-09-08 18:26:15 +02:00
Aleksandr Gumroian
c3cdafce13 Merge branch 'fix-ws-loop' into 'dev'
Fix infinity loop caused by WebSockets

Closes #17

See merge request turris/reforis/foris-js!125
2020-09-08 18:11:05 +02:00
Aleksandr Gumroian
b96b434a3e Update Snapshots 2020-09-02 17:55:53 +02:00
Aleksandr Gumroian
0ea5f7de84 Decrease Switch's margin-bottom with headings 2020-09-02 17:55:44 +02:00
Aleksandr Gumroian
0c7997f6c0 Fix Reboot page URL in respective dropdown 2020-09-02 17:55:44 +02:00
Aleksandr Gumroian
90ce866869 Fix infinity loop caused by WebSockets 2020-09-02 17:55:25 +02:00
Aleksandr Gumroian
ad99a2034d Merge branch 'dev' into 'master'
Add "inline" option to RadioSet

See merge request turris/reforis/foris-js!124
2020-08-31 18:24:42 +02:00
Aleksandr Gumroian
4ff814f0fd Merge branch 'small-fixes' into 'dev'
Add "inline" option to RadioSet

See merge request turris/reforis/foris-js!123
2020-08-31 18:19:12 +02:00
Aleksandr Gumroian
896277b62a Bump v5.1.1 2020-08-31 16:04:03 +02:00
Aleksandr Gumroian
b0365e3b06 NPM audit fix 2020-08-31 15:56:01 +02:00
Aleksandr Gumroian
8bd71a08af Update Snapshots 2020-08-31 15:56:01 +02:00
Aleksandr Gumroian
1903016f13 Add "inline" option to RadioSet 2020-08-31 15:56:00 +02:00
Aleksandr Gumroian
443f14d26c Add ability to select switch's form-group 2020-08-31 15:56:00 +02:00
Aleksandr Gumroian
f1feffb4bb Merge branch 'dev' into 'master'
Release v5.1.0

See merge request turris/reforis/foris-js!122
2020-08-26 11:55:07 +02:00
Aleksandr Gumroian
61b349c6cc Merge branch 'fluid-aid' into 'dev'
Add auxiliary features in order to support Fluid Layout

See merge request turris/reforis/foris-js!121
2020-08-25 17:35:28 +02:00
Aleksandr Gumroian
7a98ab0c2d Bump v5.1.0 2020-08-25 17:32:24 +02:00
Aleksandr Gumroian
5de05fe4eb NPM audit fix 2020-08-25 17:32:24 +02:00
Aleksandr Gumroian
50943e0b11 fixup! Fix buttons size outside of form's card layout 2020-08-25 17:32:23 +02:00
Aleksandr Gumroian
f64419c643 Add tests for Switch 2020-08-18 17:37:08 +02:00
Josef Schlehofer
a0f7a312e5 .gitlab.ci: update to node 10 2020-08-18 16:17:00 +02:00
Aleksandr Gumroian
f8726e6012 Format all files with Prettier 2020-08-18 16:17:00 +02:00
Aleksandr Gumroian
e41da48b1a Integrate Prettier + ESLint + reForis Style Guide 2020-08-18 16:17:00 +02:00
Aleksandr Gumroian
a434ecac18 Update Snapshots 2020-08-18 15:41:05 +02:00
Aleksandr Gumroian
5ae129b0f5 Fix tests 2020-08-18 15:41:05 +02:00
Aleksandr Gumroian
a2acac255d Swap checkboxes for switches on Wi-Fi page 2020-08-18 15:41:04 +02:00
Aleksandr Gumroian
c1b1d8c079 Add Switch component 2020-08-18 15:41:03 +02:00
Aleksandr Gumroian
e422acc92f Add testUtils to .gitignore 2020-08-10 15:57:06 +02:00
Aleksandr Gumroian
705ed5ac80 Update Snapshots 2020-08-06 17:25:49 +02:00
Aleksandr Gumroian
1dd1805ae0 Fix buttons size outside of form's card layout 2020-08-06 17:25:38 +02:00
Aleksandr Gumroian
e858b30994 Add appropriate links to dropdown headers 2020-08-05 14:37:14 +02:00
Aleksandr Gumroian
8a56d71c51 Add semantic & accessibility structure for headings 2020-08-05 14:36:54 +02:00
Aleksandr Gumroian
d34c465787 Update Snapshots 2020-07-30 11:57:50 +02:00
Aleksandr Gumroian
cbf37dd747 Fix overview & notifications URLs 2020-07-30 11:40:11 +02:00
Aleksandr Gumroian
f9cfb248d3 Decrease button width on different breakpoints 2020-07-22 16:07:41 +02:00
Aleksandr Gumroian
9be880aeaa Remove form's offset & extend it on 12 columns 2020-07-22 15:59:49 +02:00
Aleksandr Gumroian
a4bb41d585 Merge branch 'dev' into 'master'
Release v5.0.1

See merge request turris/reforis/foris-js!120
2020-07-21 12:52:29 +02:00
Aleksandr Gumroian
c3b09b01e5 Merge branch 'release-v5.0.1' into 'dev'
Release v5.0.1

See merge request turris/reforis/foris-js!119
2020-07-21 12:20:53 +02:00
Aleksandr Gumroian
12b862c568 Bump v5.0.1 2020-07-21 11:59:13 +02:00
Aleksandr Gumroian
54f9f984f1 NPM audit fix & update of packages 2020-07-21 11:59:12 +02:00
Aleksandr Gumroian
5dbc58d44b Merge branch 'new-channel-bandwidth' into 'dev'
New channel bandwidth & Natural Sort of options

Closes reforis#200

See merge request turris/reforis/foris-js!118
2020-07-17 16:27:27 +02:00
Aleksandr Gumroian
e7f9fbca96 Merge branch 'dev' into 'new-channel-bandwidth'
# Conflicts:
#   src/common/WiFiSettings/WiFiForm.js
2020-07-17 16:24:43 +02:00
Aleksandr Gumroian
8d40dbb841 Merge branch 'additional-wifi-module-fix' into 'dev'
Fix Wi-Fi Form bug with additional Wi-Fi modules

Closes reforis#204

See merge request turris/reforis/foris-js!117
2020-07-17 14:38:24 +02:00
Aleksandr Gumroian
cea8aa0c12 Fix a Wi-Fi Form bug with additional Wi-Fi modules 2020-07-17 14:33:35 +02:00
Aleksandr Gumroian
16a7a6c52d Update Snapshots 2020-07-17 14:19:56 +02:00
Aleksandr Gumroian
597b6fcf4c Add Natural sort order for list of options 2020-07-17 14:19:56 +02:00
Aleksandr Gumroian
5eb6b90ed4 Add 802.11ac 160 MHz wide channel to constants 2020-07-17 13:38:30 +02:00
Aleksandr Gumroian
48c323c1a1 Fix Wi-Fi Form bug with additional Wi-Fi modules 2020-07-17 12:26:46 +02:00
Marek Sasek
3d57b38808 Merge branch 'dev' into 'master'
Merging Dev into Master

See merge request turris/reforis/foris-js!116
2020-07-16 16:07:04 +02:00
Marek Sasek
ae8baddbdd Merge branch 'one-wifi-module-fix' into 'dev'
Fix form submission button with one Wi-Fi module.

Closes reforis#192

See merge request turris/reforis/foris-js!115
2020-07-13 19:38:44 +02:00
Marek Sašek
67e4abe4d1 Add test suites for a Wi-Fi form submission 2020-07-07 11:35:58 +02:00
Aleksandr Gumroian
57f1ccced8 Fix form submission button for one or more Wi-Fi modules. 2020-06-29 13:24:42 +02:00
Bogdan Bodnar
1e95bff7ff Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!114
2020-06-04 22:56:37 +02:00
Bogdan Bodnar
0f253ecc19 Merge branch 'docs-update' into 'dev'
Docs update

See merge request turris/reforis/foris-js!113
2020-06-04 22:52:24 +02:00
Bogdan Bodnar
a5e096dc00 Fix and update docs. 2020-06-04 22:52:24 +02:00
Eduardo Cuthbert
074ddf8a8b Translated using Weblate (Spanish)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/es/
2020-05-25 15:41:39 +02:00
Bogdan Bodnar
182cbe698f Merge branch 'dev' into 'master'
Bump v5.0.0.

See merge request turris/reforis/foris-js!111
2020-05-07 17:17:47 +02:00
Bogdan Bodnar
982eb371ad Bump v5.0.0.
I've realized that it should be major update due to broken API.
2020-05-07 16:55:55 +02:00
Bogdan Bodnar
2786f856f7 Merge branch 'dev' into 'master'
Release v4.5.1.

See merge request turris/reforis/foris-js!110
2020-05-07 16:47:30 +02:00
Bogdan Bodnar
48b080dc26 Merge branch 'release-4.5.1' into 'dev'
Release v4.5.1.

See merge request turris/reforis/foris-js!109
2020-05-07 16:40:27 +02:00
Bogdan Bodnar
71beeb46f1 Bump v4.5.1.
* Add initial data to ForisForm children.
 * Update .pot file.
2020-05-07 16:34:33 +02:00
Bogdan Bodnar
060a0489e1 Merge branch 'translations' into 'dev'
Update translations (.pot).

See merge request turris/reforis/foris-js!108
2020-05-07 16:31:44 +02:00
Bogdan Bodnar
ae49b246cd Update translations (.pot). 2020-05-07 16:13:03 +02:00
Bogdan Bodnar
27c37eb74b Merge branch 'add-inital-form-data-to-children-of-foris-form' into 'dev'
Add initial form data to children of the ForisForm.

See merge request turris/reforis/foris-js!107
2020-05-07 16:03:51 +02:00
Bogdan Bodnar
cd708fa294 Add initial form data to children of the ForisForm. 2020-05-07 16:00:02 +02:00
Bogdan Bodnar
8ec0392852 Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!106
2020-03-26 16:29:27 +01:00
Bogdan Bodnar
27a5e62d9a Merge branch 'fix-pdfmake' into 'dev'
Fix pdfmake.

See merge request turris/reforis/foris-js!105
2020-03-25 21:34:38 +01:00
Bogdan Bodnar
aeaad4aa72 Bump v4.5.0. 2020-03-25 21:26:59 +01:00
Bogdan Bodnar
256a000d61 NPM update. 2020-03-25 21:17:12 +01:00
Bogdan Bodnar
c78ed9a5d0 NPM audit fix. 2020-03-25 21:17:12 +01:00
Bogdan Bodnar
bded10211a Use pdfmake from globals. 2020-03-25 21:17:12 +01:00
Bogdan Bodnar
25ac6cf1e9 Remove pdfmake. 2020-03-25 21:17:12 +01:00
Bogdan Bodnar
9a2547a6c2 Merge branch 'dev' into 'master'
Release v4.4.0.

See merge request turris/reforis/foris-js!104
2020-03-13 22:16:42 +01:00
Bogdan Bodnar
7968c7af4a Merge branch 'fix-hostname-validation-regex' into 'dev'
Fix hostname validation regex

See merge request turris/reforis/foris-js!103
2020-03-13 21:41:49 +01:00
Bogdan Bodnar
4b94c470c3 Bump v4.4.0. 2020-03-13 21:37:21 +01:00
Bogdan Bodnar
e1b5a25ddd Update domain vadlidation. 2020-03-13 21:37:21 +01:00
Bogdan Bodnar
95af86c776 NPM audit fix. 2020-03-13 21:37:21 +01:00
Bogdan Bodnar
02b5583712 Move vadliadtions and forisUrls to utils. 2020-03-13 21:37:21 +01:00
Bogdan Bodnar
2f4d757a1a Merge branch 'dev' into 'master'
Release v4.3.1.

See merge request turris/reforis/foris-js!102
2020-03-06 14:10:21 +01:00
Bogdan Bodnar
3c7a67783f Merge branch 'add-logout-url' into 'dev'
Add logout URL.

See merge request turris/reforis/foris-js!101
2020-03-06 14:06:34 +01:00
Bogdan Bodnar
4500e85a40 Bump v4.3.1. 2020-03-06 14:01:40 +01:00
Bogdan Bodnar
ce955095fd Add logout link. 2020-03-06 14:00:18 +01:00
Bogdan Bodnar
00b861531e Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!100
2020-02-26 17:02:47 +01:00
Bogdan Bodnar
fad5b97a2e Merge branch 'release-4.3.0' into 'dev'
Release 4.3.0

See merge request turris/reforis/foris-js!99
2020-02-26 16:57:26 +01:00
Bogdan Bodnar
aa639596d4 Bump v4.3.0. 2020-02-26 16:52:53 +01:00
Bogdan Bodnar
1ee41f4f14 Merge branch 'radioset-element-children' into 'dev'
Allow RadioSet accept elements as children.

See merge request turris/reforis/foris-js!98
2020-02-26 15:24:28 +01:00
Bogdan Bodnar
bf8c2d28bf Allow RadioSet accept elements as children. 2020-02-26 15:21:43 +01:00
Bogdan Bodnar
dbb840d51c Merge branch 'scrollable-modal' into 'dev'
Add option to make modal scrollable.

See merge request turris/reforis/foris-js!97
2020-02-26 11:47:50 +01:00
Bogdan Bodnar
ba772be869 Add option to make modal scrollable. 2020-02-26 11:44:52 +01:00
Bogdan Bodnar
70da1c3c00 Merge branch 'dev' into 'master'
Release 4.2.0.

See merge request turris/reforis/foris-js!96
2020-02-26 11:37:23 +01:00
Bogdan Bodnar
8e68bbc91f Merge branch 'release-v4.2.0' into 'dev'
Bump v4.2.0.

See merge request turris/reforis/foris-js!95
2020-02-21 16:33:31 +01:00
Bogdan Bodnar
0af8c4aa28 Bump v4.2.0. 2020-02-21 16:06:21 +01:00
Bogdan Bodnar
a9114caf9e Merge branch 'translations' into 'dev'
Translations

See merge request turris/reforis/foris-js!94
2020-02-21 11:07:13 +01:00
Bogdan Bodnar
3c81264024 Create and update translation messages. 2020-02-20 17:29:11 +01:00
Bogdan Bodnar
0330b39f2e Merge remote-tracking branch 'weblate/master' into translations 2020-02-20 17:26:34 +01:00
Bogdan Bodnar
a7dcced08b Add weblate config file. 2020-02-20 17:19:07 +01:00
Maciej Lenartowicz
c453a35763 Merge branch 'dev' into 'master'
Release 4.1.0

See merge request turris/reforis/foris-js!93
2020-02-20 16:07:50 +01:00
Maciej Lenartowicz
d97248c6ec Merge branch 'datetime-utils' into 'dev'
Added date and time utilities.

See merge request turris/reforis/foris-js!92
2020-02-20 14:30:28 +01:00
Maciej Lenartowicz
9fbc4e8383 Added date and time utilities. 2020-02-20 14:30:28 +01:00
Maciej Lenartowicz
57bebc92c7 Merge branch 'dev' into 'master'
Release 4.0.0

See merge request turris/reforis/foris-js!91
2020-02-20 11:53:43 +01:00
Maciej Lenartowicz
5939e9dd0e Merge branch 'version-4.0.0' into 'dev'
Changed version to 4.0.0.

See merge request turris/reforis/foris-js!90

[skip ci]
2020-02-20 10:40:24 +01:00
Maciej Lenartowicz
0665869c30 Changed version to 4.0.0. 2020-02-19 10:34:47 +01:00
Maciej Lenartowicz
199b27d63a Merge branch '12-api-error' into 'dev'
Rethrow unhandled error from API hooks.

Closes #12

See merge request turris/reforis/foris-js!89
2020-02-18 17:37:34 +01:00
Maciej Lenartowicz
2b28434712 Rethrow unhandled error from API hooks. 2020-02-18 14:32:59 +01:00
Maciej Lenartowicz
388860d51e Merge branch 'dev' into 'master'
Release 3.4.0

See merge request turris/reforis/foris-js!88
2020-02-17 10:55:59 +01:00
Maciej Lenartowicz
8b7c459855 Merge branch 'css-refactoring' into 'dev'
Added styles extracted from reForis.

See merge request turris/reforis/foris-js!85
2020-02-14 17:26:24 +01:00
Maciej Lenartowicz
83409b0118 Merge branch 'foris-form-docs' into 'dev'
Fixed ForisForm docstring.

See merge request turris/reforis/foris-js!87
2020-02-14 13:55:23 +01:00
Maciej Lenartowicz
c1cd90dff6 Fixed ForisForm docstring. 2020-02-14 12:40:23 +01:00
Maciej Lenartowicz
01fb897180 Merge branch 'form-reference' into 'dev'
Added reference to form element.

See merge request turris/reforis/foris-js!86
2020-02-13 15:55:01 +01:00
Maciej Lenartowicz
716c323b28 Added reference to form element. 2020-02-10 12:06:16 +01:00
nautilusx
55dbf8f8bb Translated using Weblate (German)
Currently translated at 66.7% (12 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/de/
2020-02-04 19:50:20 +01:00
Maciej Lenartowicz
85e42980ec Added styles extracted from reForis. 2020-01-22 13:21:46 +01:00
Allan Nordhøy
3dee532ea2 Translated using Weblate (Norwegian Bokmål)
Currently translated at 77.8% (14 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/nb_NO/
2020-01-21 14:52:31 +01:00
Maciej Lenartowicz
3aac48d2bf Merge branch 'foris-form-error' into 'dev'
Display actual error within the form.

See merge request turris/reforis/foris-js!84
2020-01-20 16:54:42 +01:00
Maciej Lenartowicz
ee33d33738 Display actual error within the form. 2020-01-20 16:54:42 +01:00
Maciej Lenartowicz
605f682356 Merge branch 'dev' into 'master'
Release 3.2.0

See merge request turris/reforis/foris-js!81
2020-01-17 15:14:23 +01:00
Bogdan Bodnar
a0a775996e Merge branch 'use-react-router-dom' into 'dev'
Use react-router-dom instead of react-router.

See merge request turris/reforis/foris-js!80
2020-01-17 14:35:20 +01:00
Bogdan Bodnar
532acf9d86 Add warning about using <ForisForm /> component in plugins. 2020-01-17 13:15:46 +01:00
Maciej Lenartowicz
cbc3c2f3e7 Merge branch 'revert-disable-prompt' into 'dev'
Revert "Prompt as an optional element of ForisForm."

See merge request turris/reforis/foris-js!83
2020-01-17 12:44:12 +01:00
Maciej Lenartowicz
556e12c964 Revert "Prompt as an optional element of ForisForm."
This reverts commit 75bfbb88ae.
2020-01-17 12:44:11 +01:00
Maciej Lenartowicz
813a865f62 Merge branch 'conection-timeout' into 'dev'
Increased network timeout.

See merge request turris/reforis/foris-js!82
2020-01-17 12:10:56 +01:00
Maciej Lenartowicz
c495aa97ac Increased network timeout. 2020-01-16 18:40:57 +01:00
Maciej Lenartowicz
2d375b1690 Merge branch 'disable-form-prompt' into 'dev'
Prompt as an optional element of ForisForm.

See merge request turris/reforis/foris-js!77
2020-01-16 16:12:44 +01:00
Bogdan Bodnar
e7e389e843 Add react-router-dom to peer dep. 2020-01-16 12:23:02 +01:00
Maciej Lenartowicz
8679749e0f Merge branch 'controller-id-in-hook' into 'dev'
Added controller ID filter to WebSocket hook.

See merge request turris/reforis/foris-js!79
2020-01-16 10:27:30 +01:00
Maciej Lenartowicz
6d8e0cec70 Added controller ID filter to WebSocket hook. 2020-01-16 10:27:30 +01:00
Bogdan Bodnar
5091eecedf Use react-router-dom instead of react-router. 2020-01-15 18:19:04 +01:00
Maciej Lenartowicz
75bfbb88ae Prompt as an optional element of ForisForm. 2020-01-14 15:00:17 +01:00
Maciej Lenartowicz
e5cbbc9019 Merge branch 'wifi-messages' into 'dev'
Updated translation messages after moving WiFi form.

See merge request turris/reforis/foris-js!76
2020-01-13 16:10:19 +01:00
Maciej Lenartowicz
7ab1d2aaa4 Updated translation messages after moving WiFi form. 2020-01-13 16:10:19 +01:00
Éfrit
e62accc4b3 Translated using Weblate (French)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/fr/
2020-01-11 22:21:24 +01:00
Maciej Lenartowicz
03e071d5ee Merge branch 'dev' into 'master'
Release 3.1.1

See merge request turris/reforis/foris-js!75
2020-01-10 10:44:40 +01:00
Maciej Lenartowicz
d83ba3bfd3 Merge branch 'moment-timezone' into 'dev'
Moved moment-timezone to devDependencies.

See merge request turris/reforis/foris-js!74
2020-01-10 09:53:50 +01:00
Maciej Lenartowicz
3e2c89cac7 Moved moment-timezone to devDependencies. 2020-01-09 17:46:15 +01:00
Maciej Lenartowicz
e3d159d6a3 Merge branch 'expose-fix' into 'dev'
Fix for exposed libraries.

See merge request turris/reforis/foris-js!73
2020-01-09 16:39:59 +01:00
Maciej Lenartowicz
afa9b5a402 Fix for exposed libraries. 2020-01-09 16:33:25 +01:00
Maciej Lenartowicz
b8555247f2 Merge branch 'dev' into 'master'
Release 3.1.0

See merge request turris/reforis/foris-js!72
2020-01-09 11:57:45 +01:00
Maciej Lenartowicz
de8462429b Merge branch 'wifi-settings' into 'dev'
Wi-Fi settings form

See merge request turris/reforis/foris-js!71
2020-01-09 11:25:29 +01:00
Maciej Lenartowicz
5fd0d3626a Wi-Fi settings form 2020-01-09 11:25:29 +01:00
Bogdan Bodnar
9dcc689491 Merge branch 'fix-main-js-path' into 'dev'
Fix main js file path in unpacked library.

See merge request turris/reforis/foris-js!70
2020-01-09 07:20:48 +01:00
Bogdan Bodnar
35f307200d Bump version to 3.0.1. 2020-01-08 11:59:33 +01:00
Bogdan Bodnar
afb5366dd7 Fix main js path in unpacked library. 2020-01-08 09:38:02 +01:00
Maciej Lenartowicz
1e6278abdf Merge branch 'dev' into 'master'
Release 3.0.0

See merge request turris/reforis/foris-js!68
2020-01-07 16:10:16 +01:00
Maciej Lenartowicz
6769e84e62 Merge branch 'version-3.0.0' into 'dev'
Version 3.0.0

See merge request turris/reforis/foris-js!69
2020-01-07 15:57:28 +01:00
Maciej Lenartowicz
71b0a9a5fa Version 3.0.0 [skip ci] 2020-01-07 15:37:07 +01:00
Maciej Lenartowicz
418e38de31 Merge branch 'form-widgets-size' into 'dev'
Form widgets size

See merge request turris/reforis/foris-js!66
2020-01-07 13:27:50 +01:00
Maciej Lenartowicz
56a4c47948 Form widgets size 2020-01-07 13:27:49 +01:00
Bogdan Bodnar
c67ad164ce Merge branch 'pack-without-src' into 'dev'
Make packing without src.

See merge request turris/reforis/foris-js!67
2020-01-07 13:07:44 +01:00
Bogdan Bodnar
6374fd5adf Make packing without src. 2020-01-07 13:03:55 +01:00
Bogdan Bodnar
cc13e9c164 Merge branch 'no-babel' into 'dev'
No babel

See merge request turris/reforis/foris-js!64
2020-01-07 11:12:17 +01:00
Maciej Lenartowicz
bb90800945 Merge branch 'dev' into 'master'
Release 2.1.1

See merge request turris/reforis/foris-js!65
2020-01-06 09:56:29 +01:00
Maciej Lenartowicz
6d4bff2b4f Merge branch 'datepicker' into 'dev'
Display datepicker above input

Closes reforis#153

See merge request turris/reforis/foris-js!63
2020-01-06 09:45:16 +01:00
Maciej Lenartowicz
92f560b69f Display datepicker above input 2020-01-06 09:45:16 +01:00
Éfrit
a318f12352 Translated using Weblate (French)
Currently translated at 38.9% (7 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/fr/
2019-12-28 15:55:00 +01:00
Bogdan Bodnar
32e3a57bd7 Fix source files path. 2019-12-27 12:10:19 +01:00
Bogdan Bodnar
dd27802056 Fix relative imports. 2019-12-27 12:10:19 +01:00
Bogdan Bodnar
bd4e1953e3 Don't use babel. 2019-12-27 12:10:19 +01:00
Bogdan Bodnar
68e4368ae3 npm audit fix. 2019-12-27 12:10:19 +01:00
Maciej Lenartowicz
51ba380cf0 Merge branch 'dev' into 'master'
Release 2.1.0

See merge request turris/reforis/foris-js!62
2019-12-19 12:10:18 +01:00
Maciej Lenartowicz
4eae1ed8d2 Merge branch 'ws-logging' into 'dev'
Changed levels of WS logs

Closes reforis#162

See merge request turris/reforis/foris-js!61
2019-12-19 11:58:59 +01:00
Maciej Lenartowicz
3d290114fa Changed levels of WS logs 2019-12-17 17:45:55 +01:00
Bogdan Bodnar
3f87e9e4b4 Merge branch 'export-radio' into 'dev'
Export <Radio />.

See merge request turris/reforis/foris-js!60
2019-12-17 14:07:14 +01:00
Bogdan Bodnar
6d5cb6a951 Export <Radio />. 2019-12-17 14:04:06 +01:00
Maciej Lenartowicz
8d3be8df67 Merge branch 'click-outside-hook' into 'dev'
Detect clicks outside element with a hook

See merge request turris/reforis/foris-js!59
2019-12-16 16:21:18 +01:00
Maciej Lenartowicz
90509f2a23 Detect clicks outside element with a hook 2019-12-16 13:44:58 +01:00
Maciej Lenartowicz
73f84a2d81 Merge branch '8-fix-git-link' into 'dev'
Fixed link to git repository

Closes #8

See merge request turris/reforis/foris-js!58
2019-12-09 12:45:01 +01:00
Maciej Lenartowicz
cea7325427 Fixed link to git repository 2019-12-09 11:51:50 +01:00
Bogdan Bodnar
a8d8c872f9 Merge branch 'dev' into 'master'
Publish 2.0.0.

See merge request turris/reforis/foris-js!57
2019-12-09 10:51:49 +01:00
Bogdan Bodnar
cda7898a96 Merge branch 'fix-readme' into 'dev'
Fix README.md publishing.

See merge request turris/reforis/foris-js!56
2019-12-09 10:24:42 +01:00
Bogdan Bodnar
26bea9c7c4 Fix README.md publishing. 2019-12-06 17:26:01 +01:00
Bogdan Bodnar
fd1518265f Merge branch 'spinner-element-class-name' into 'dev'
Add className to SpinnerElement.

See merge request turris/reforis/foris-js!55
2019-12-06 15:07:16 +01:00
Bogdan Bodnar
61d10e91e0 Merge branch 'ws-unsubscription' into 'dev'
Add WS unsubscribtion.

See merge request turris/reforis/foris-js!53
2019-12-06 15:06:59 +01:00
Bogdan Bodnar
aac6c6bf2a Add className to SpinnerElement. 2019-12-06 12:13:29 +01:00
Bogdan Bodnar
d55615abcc Grammar. 2019-12-06 12:09:54 +01:00
Bogdan Bodnar
9d322811c3 Improve docs and propTypes of WS usage in ForisForm. 2019-12-06 12:07:42 +01:00
Bogdan Bodnar
f30685d9c2 Add WS unsubscribtion.
Code review improvements.
2019-12-06 11:46:30 +01:00
Maciej Lenartowicz
5bb298270b Merge branch '15-api-url-suffix' into 'dev'
Set suffix for API URL

Closes #15

See merge request turris/reforis/foris-js!54
2019-12-06 10:27:48 +01:00
Maciej Lenartowicz
8d0c640994 Set suffix for API URL 2019-12-06 10:27:48 +01:00
Maciej Lenartowicz
25ddb5949c Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!52
2019-12-02 17:11:03 +01:00
Bogdan Bodnar
7b739f55a0 Merge branch 'add-reboot-button' into 'dev'
Add reboot button

See merge request turris/reforis/foris-js!50
2019-11-29 16:37:44 +01:00
Bogdan Bodnar
03a020f87c Bump version 1.4.0. 2019-11-29 16:29:32 +01:00
Bogdan Bodnar
c6fd9bbadb NPM audit fix. 2019-11-29 16:29:32 +01:00
Bogdan Bodnar
acaaab0654 Fix URL prefixes. 2019-11-29 16:29:31 +01:00
Bogdan Bodnar
04a667eb6f Extract reboot button from reForis.
* Add RebootButton tests.
 * RebootButton code review.
 * Update translations.
2019-11-29 16:29:31 +01:00
Maciej Lenartowicz
d71f638bd5 Merge branch 'download-button-class' into 'dev'
Allow adding classes to DownloadButton

See merge request turris/reforis/foris-js!51
2019-11-29 16:01:53 +01:00
Maciej Lenartowicz
1064277cd9 Allow adding classes to DownloadButton 2019-11-29 16:01:53 +01:00
Florian
cffa0a2b80 Translated using Weblate (French)
Currently translated at 38.9% (7 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/fr/
2019-11-25 17:04:59 +01:00
Bogdan Bodnar
8d1d5b57fd Merge branch 'dev' 2019-11-25 13:07:20 +01:00
Mattias Münster
7579fc3b8c Translated using Weblate (Swedish)
Currently translated at 11.1% (2 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/sv/
2019-11-23 18:04:52 +01:00
Bogdan Bodnar
6601cd55e0 Translated using Weblate (Russian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/ru/
2019-11-23 18:04:51 +01:00
Bogdan Bodnar
fd01bc6f56 Merge branch 'bump-1.3.3' into 'dev'
Bump version 1.3.3.

See merge request turris/reforis/foris-js!48
2019-11-22 15:35:41 +00:00
Bogdan Bodnar
e27a23600f Bump version 1.3.3. 2019-11-22 16:30:25 +01:00
Bogdan Bodnar
daf787e2df Merge branch 'translations' into 'master'
Translations

See merge request turris/reforis/foris-js!47
2019-11-22 15:19:01 +00:00
Pavel Borecki
512c65c213 Translated using Weblate (Czech)
Currently translated at 100.0% (18 of 18 strings)

Translation: Turris/Foris JS
Translate-URL: https://hosted.weblate.org/projects/turris/foris-js/cs/
2019-11-21 18:04:38 +01:00
Weblate
3925fb6439 Added translation using Weblate (Spanish) 2019-11-21 05:10:24 +01:00
Bogdan Bodnar
21bbfb6d2e Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!43
2019-11-20 10:22:05 +00:00
Bogdan Bodnar
1dcba1dfa8 Merge branch 'api-polling-catch-error' into 'dev'
Catch error state in API polling.

See merge request turris/reforis/foris-js!44
2019-11-19 16:56:38 +00:00
Bogdan Bodnar
fd8acd1ceb Catch error and sending state in API polling. 2019-11-19 16:56:38 +00:00
Bogdan Bodnar
48d5cf0119 Merge branch 'clickable-checkbox-helptext' into 'dev'
Make checkBox help text clickabe.

See merge request turris/reforis/foris-js!45
2019-11-19 12:43:15 +00:00
Bogdan Bodnar
1a82f8e225 Update checkbox snapshots. 2019-11-19 13:37:43 +01:00
Bogdan Bodnar
638821d025 Make checkBox help text clickabe. 2019-11-19 13:36:49 +01:00
Maciej Lenartowicz
fbaa73e378 Merge branch 'global-alert-context' into 'dev'
Use global AlertContext

See merge request turris/reforis/foris-js!42
2019-11-18 12:51:43 +00:00
Maciej Lenartowicz
e54db2c577 Use global AlertContext 2019-11-18 12:51:43 +00:00
Bogdan Bodnar
a6866a0673 Merge branch 'api-polling' into 'dev'
Add hook for API polling.

See merge request turris/reforis/foris-js!41
2019-11-15 15:59:44 +00:00
Bogdan Bodnar
429814ebb5 Add until param to API polling hook. 2019-11-15 14:21:19 +01:00
Bogdan Bodnar
73e213c467 Add default delay. 2019-11-15 14:20:14 +01:00
Bogdan Bodnar
2e68e56e44 Add hook for API polling. 2019-11-15 14:20:07 +01:00
Maciej Lenartowicz
b3bb4de646 Merge branch 'with-either-props' into 'dev'
Fix for withEither

See merge request turris/reforis/foris-js!40
2019-11-15 08:55:24 +00:00
Maciej Lenartowicz
b30f9f59b4 Fix for withEither 2019-11-15 08:55:24 +00:00
Maciej Lenartowicz
158bd1bb24 Merge branch 'dev' into 'master'
Release 1.3.1

See merge request turris/reforis/foris-js!38
2019-11-14 12:58:31 +00:00
Maciej Lenartowicz
d935f78d6e Merge branch '13-packages-required-for-translations' into 'dev'
Added virtual environment and packages required for translations.

Closes #13

See merge request turris/reforis/foris-js!39
2019-11-14 12:51:58 +00:00
Maciej Lenartowicz
73f4ab48c3 Added virtual environment and packages required for translations. 2019-11-14 11:28:28 +01:00
Bogdan Bodnar
7075592f24 Merge branch 'timeout' into 'dev'
Timeout handling

See merge request turris/reforis/foris-js!36
2019-11-13 14:45:24 +00:00
Bogdan Bodnar
23029470b9 Improve error handling + small refactoring. 2019-11-13 15:28:26 +01:00
Bogdan Bodnar
7e6e6f8c87 Use generic error message in the ForisForm. 2019-11-13 14:49:52 +01:00
Bogdan Bodnar
b831d664a9 Add timout handling. 2019-11-13 14:49:52 +01:00
Maciej Lenartowicz
0f5b35a3ba Merge branch 'shared-lint-configs' into 'dev'
Shared lint configs

See merge request turris/reforis/foris-js!37
2019-11-13 10:11:11 +00:00
Maciej Lenartowicz
6e02f1d194 Shared lint configs 2019-11-13 10:11:11 +00:00
Maciej Lenartowicz
13ff8221ca Merge branch 'improve-alert' into 'dev'
Move alert to portal

See merge request turris/reforis/foris-js!35
2019-11-12 12:50:29 +00:00
Maciej Lenartowicz
a51ba0630d Move alert to portal 2019-11-12 12:50:29 +00:00
Maciej Lenartowicz
ee5cf07614 Merge branch 'loading-and-errors' into 'dev'
Loading and errors HOCs

See merge request turris/reforis/foris-js!34
2019-11-07 17:21:14 +00:00
Maciej Lenartowicz
8b39bf4193 Loading and errors HOCs 2019-11-07 17:21:14 +00:00
Maciej Lenartowicz
644726a0fc Merge branch '6-api-hooks-fix' into 'dev'
Added missing hook to index.js

Closes #6

See merge request turris/reforis/foris-js!33
2019-11-05 12:03:43 +00:00
Maciej Lenartowicz
654ae6914a Added missing hook to index.js 2019-11-05 13:00:44 +01:00
Maciej Lenartowicz
81dedf59cd Merge branch '6-api-hooks' into 'dev'
Resolve "Discuss and implement proper API methods."

Closes #6

See merge request turris/reforis/foris-js!32
2019-11-05 11:10:50 +00:00
Maciej Lenartowicz
7f8aaea7b8 Resolve "Discuss and implement proper API methods." 2019-11-05 11:10:50 +00:00
Maciej Lenartowicz
dfa80e64ec Merge branch 'publish-fix' into 'dev'
Fixed publish script

See merge request turris/reforis/foris-js!31
2019-11-01 11:08:05 +00:00
Maciej Lenartowicz
031b53f03c Fixed publish script 2019-11-01 10:49:45 +01:00
Maciej Lenartowicz
ca23b2d335 Merge branch 'alert-context-fix' into 'dev'
Flat structure of published package

See merge request turris/reforis/foris-js!30
2019-11-01 09:28:28 +00:00
Maciej Lenartowicz
0984c45161 Flat structure of published package 2019-11-01 09:28:28 +00:00
Maciej Lenartowicz
6835bc7a28 Merge branch 'global-alert' into 'dev'
Global alert

See merge request turris/reforis/foris-js!29
2019-10-30 16:06:53 +00:00
Maciej Lenartowicz
0915d477fe Global alert 2019-10-30 16:06:53 +00:00
Maciej Lenartowicz
c114579b7f Merge branch 'dev' into 'master'
Release 1.2.0

See merge request turris/reforis/foris-js!28
2019-10-24 13:52:35 +00:00
Maciej Lenartowicz
22b2bc9c09 Merge branch 'release-1.2.0' into 'dev'
Release 1.2.0

See merge request turris/reforis/foris-js!27

[skip ci]
2019-10-24 11:54:36 +00:00
Maciej Lenartowicz
cab2cfa068 Release 1.2.0 2019-10-24 13:47:25 +02:00
Maciej Lenartowicz
a137a0d4cf Merge branch 'dev' into 'master'
Release 1.1.1

See merge request turris/reforis/foris-js!26
2019-10-24 11:24:57 +00:00
Maciej Lenartowicz
54cf7e3c06 Merge branch 'master' into dev 2019-10-24 10:47:13 +02:00
Maciej Lenartowicz
18eb28f368 Merge branch 'radio-checkbox-whitespace' into 'dev'
Radio checkbox whitespace

See merge request turris/reforis/foris-js!25

[skip ci]
2019-10-24 08:12:39 +00:00
Maciej Lenartowicz
88f3836485 Radio checkbox whitespace 2019-10-24 08:12:39 +00:00
Maciej Lenartowicz
a88fbf63e9 Merge branch 'shell-quotes' into 'dev'
Added quotes to shell variables

See merge request turris/reforis/foris-js!23
2019-10-15 08:50:53 +00:00
Maciej Lenartowicz
ff9e8fdeb1 Added quotes to shell variables 2019-10-15 10:10:33 +02:00
Maciej Lenartowicz
8cd4ac8b44 Merge branch 'ws-connection-closed' into 'dev'
Download button

See merge request turris/reforis/foris-js!22
2019-10-11 14:11:14 +00:00
Maciej Lenartowicz
760e6d9243 Download button 2019-10-11 14:11:14 +00:00
Maciej Lenartowicz
2429f4662c Merge branch '5-versioning' into 'dev'
Changed beta versioning procedure

Closes #5

See merge request turris/reforis/foris-js!21
2019-10-11 08:25:06 +00:00
Maciej Lenartowicz
b320e6a860 Changed beta versioning procedure 2019-10-11 10:15:53 +02:00
Maciej Lenartowicz
1e3e9433ec Merge branch 'client-configuration' into 'dev'
Client configuration

See merge request turris/reforis/foris-js!20
2019-10-10 15:25:00 +00:00
Maciej Lenartowicz
e3a795e40d Client configuration 2019-10-10 15:25:00 +00:00
155 changed files with 47622 additions and 14783 deletions

View File

@@ -1,66 +1,8 @@
const path = require("path");
module.exports = { module.exports = {
"env": { extends: ["eslint-config-reforis", "prettier"],
"browser": true, plugins: ["prettier"],
"node": true, rules: {
"es6": true, "prettier/prettier": ["error"],
"jest": true
},
"extends": [
"airbnb",
"airbnb/hooks"
],
"globals": {
"_": "readonly",
"babel": "readonly",
"ForisTranslations": "readonly",
"ngettext": "readonly",
"ForisPlugins": "readonly"
},
"parser": "babel-eslint",
"rules": {
"quotes": ["error", "double"],
"indent": ["error", 4],
"react/jsx-indent": ["error", 4],
"react/jsx-indent-props": ["error", 4],
"react/prop-types": "warn",
"react/no-array-index-key": "warn",
"react/button-has-type": "warn",
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"import/no-unresolved": [
"error",
// Ignore imports used only in tests
{ ignore: ["customTestRender"] }
],
"import/no-cycle": "warn",
"no-console": "error",
"no-use-before-define": ["error", {
functions: false,
classes: true,
variables: true
}],
"no-restricted-syntax": "warn",
// Should be enabled in the future
"camelcase": "off",
"no-param-reassign": "off",
"react/jsx-props-no-spreading": "off",
"react/require-default-props": "off",
"react/default-props-match-prop-types": "off",
"react/forbid-prop-types": "off",
// Permanently disabled
"react/jsx-filename-extension": "off",
"no-plusplus": "off",
"consistent-return": "off",
"radix": "off",
"no-continue": "off",
"react/no-danger": "off",
}, },
"settings": {
"import/resolver": {
"node": {
"paths": ["src"]
}
}
}
}; };

4
.gitignore vendored
View File

@@ -4,6 +4,9 @@
logs logs
*.log *.log
# Python
venv/
# NodeJS # NodeJS
## Logs ## Logs
npm-debug.log* npm-debug.log*
@@ -48,3 +51,4 @@ coverage.xml
dist/ dist/
foris-*.tgz foris-*.tgz
styleguide/ styleguide/
testUtils

View File

@@ -1,43 +1,44 @@
image: node:8-alpine image: node:16-alpine
stages: stages:
- test - test
- build - build
- publish - publish
before_script: before_script:
- npm install - apk add make
- npm install
test: test:
stage: test stage: test
script: script:
- npm test - make test
lint: lint:
stage: test stage: test
script: script:
- npm run lint - make lint
build: build:
stage: build stage: build
script: script:
- npm pack - make pack
artifacts: artifacts:
paths: paths:
- foris-*.tgz - dist/foris-*.tgz
publish_beta: publish_beta:
stage: publish stage: publish
only: only:
refs: refs:
- dev - dev
script: script:
- sh scripts/publish.sh beta - make publish-beta
publish_latest: publish_latest:
stage: publish stage: publish
only: only:
refs: refs:
- master - master
script: script:
- sh scripts/publish.sh latest - make publish-latest

11
.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"singleQuote": false,
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 4,
"useTabs": false,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"semi": true
}

3
.weblate Normal file
View File

@@ -0,0 +1,3 @@
[weblate]
url = https://hosted.weblate.org/api/
translation = turris/foris-js

114
Makefile
View File

@@ -1,16 +1,30 @@
.PHONY: all install-js watch-js build-js lint-js test-js create-messages update-messages docs clean # Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
PROJECT="Foris JS"
# Retrieve Foris JS version from package.json
VERSION= $(shell sed -En "s/.*version['\"]: ['\"](.+)['\"].*/\1/p" package.json)
COPYRIGHT_HOLDER="CZ.NIC, z.s.p.o. (https://www.nic.cz/)"
MSGID_BUGS_ADDRESS="tech.support@turris.cz"
DEV_PYTHON=python3
VENV_NAME?=venv
VENV_BIN=$(shell pwd)/$(VENV_NAME)/bin
.PHONY: all
all: all:
@echo "make install-js" @echo "make install-js"
@echo " Install dependencies" @echo " Install npm dependencies."
@echo "make watch-js" @echo "make lint"
@echo " Compile JS in watch mode." @echo " Run linter on the project."
@echo "make build-js" @echo "make test"
@echo " Compile JS." @echo " Run tests on the project."
@echo "make lint-js" @echo "make test-js-watch"
@echo " Run linter" @echo " Run tests on the project in watch mode."
@echo "make test-js" @echo "make test-js-update-snapshots"
@echo " Run tests" @echo " Update snapshots."
@echo "make create-messages" @echo "make create-messages"
@echo " Create locale messages (.pot)." @echo " Create locale messages (.pot)."
@echo "make update-messages" @echo "make update-messages"
@@ -22,29 +36,93 @@ all:
@echo "make clean" @echo "make clean"
@echo " Remove python artifacts and virtualenv." @echo " Remove python artifacts and virtualenv."
# Preparation
.PHONY: venv
venv: $(VENV_NAME)/bin/activate
$(VENV_NAME)/bin/activate:
test -d $(VENV_NAME) || $(DEV_PYTHON) -m virtualenv -p $(DEV_PYTHON) $(VENV_NAME)
$(VENV_BIN)/$(DEV_PYTHON) -m pip install -r requirements.txt
touch $(VENV_NAME)/bin/activate
# Installation
.PHONY: install-js
install-js: package.json install-js: package.json
npm install --save-dev npm install --save-dev
watch-js:
npm run build:watch
build-js:
npm run build
# Publishing
.PHONY: collect-files
collect-files:
sh scripts/collect_files.sh
.PHONY: pack
pack: collect-files
cd dist && npm pack
.PHONY: publish-beta
publish-beta: collect-files
sh scripts/publish.sh beta
.PHONY: publish-latest
publish-latest: collect-files
sh scripts/publish.sh latest
# Linting
.PHONY: lint
lint: lint:
npm run lint npm run lint
.PHONY: lint-js-fix
lint-js-fix:
npm run lint:fix
# Testing
.PHONY: test
test: test:
npm test npm test
create-messages: .PHONY: test-js-watch
pybabel extract -F babel.cfg -o ./translations/forisjs.pot . test-js-watch:
update-messages: cd $(JS_DIR); npm test -- --watch
pybabel update -i translations/forisjs.pot -d translations
.PHONY: test-js-update-snapshots
test-js-update-snapshots:
npm test -- -u
# Translations
.PHONY: create-messages
create-messages: venv
$(VENV_BIN)/pybabel extract -F babel.cfg -o ./translations/forisjs.pot . --project=$(PROJECT) --version=$(VERSION) --copyright-holder=$(COPYRIGHT_HOLDER) --msgid-bugs-address=$(MSGID_BUGS_ADDRESS)
.PHONY: update-messages
update-messages: venv
$(VENV_BIN)/pybabel update -i ./translations/forisjs.pot -d ./translations -D forisjs --update-header-comment
# Documentation
.PHONY: docs
docs: docs:
npm run-script docs npm run-script docs
.PHONY: docs-watch
docs-watch: docs-watch:
npm run-script docs:watch npm run-script docs:watch
# Other
.PHONY: clean
clean: clean:
rm -rf node_modules dist rm -rf node_modules dist

View File

@@ -1,5 +1,7 @@
# foris-js # foris-js
Set of utils and common React elements for reForis.
## Publishing package ## Publishing package
### Beta versions ### Beta versions
@@ -12,6 +14,39 @@ tagged `beta`. Versions names are based on commit SHA, e.g.
1. Crete a merge request to `dev` branch with version bumped 1. Crete a merge request to `dev` branch with version bumped
2. When merging add `[skip ci]` to commit message to prevent publishing 2. When merging add `[skip ci]` to commit message to prevent publishing
unnecessary version unnecessary version
3. Create a merge request from `dev` to `master` branch 3. Create a merge request from `dev` to `master` branch
4. New version should be published automatically 4. New version should be published automatically
## Manually managed dependencies
Because of `<ForisForm />` component it's required to use exposed
`ReactRouterDOM` object from `react-router-dom` library. `ReactRouterDOM` is
exposed by
[reForis](https://gitlab.labs.nic.cz/turris/reforis/reforis/blob/master/js/webpack.config.js).
It can be done by following steps:
1. Setting `react-router-dom` as `peerDependencies` and `devDependencies` in
`package.json`.
2. Adding the following rules to `externals` in `webpack.conf.js` of the plugin:
```js
externals: {
...
"react-router-dom": "ReactRouterDOM",
}
```
### Docs
Build or watch docs to get more info about library:
```bash
make docs
```
or
```bash
make docs-watch
```

View File

@@ -1,17 +1,4 @@
module.exports = { module.exports = {
presets: [ presets: ["@babel/preset-env", "@babel/preset-react"],
"@babel/preset-env", plugins: ["@babel/plugin-transform-runtime"],
"@babel/preset-react",
],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-export-default-from",
["module-resolver", {
root: ["./src"],
alias: {
test: "./test",
underscore: "lodash",
},
}],
],
}; };

36
docs/components/Logo.js Normal file
View File

@@ -0,0 +1,36 @@
import React from "react";
import PropTypes from "prop-types";
import Styled from "rsg-components/Styled";
import logo from "./logo.svg";
const styles = ({ fontFamily }) => ({
logo: {
display: "flex",
alignItems: "center",
margin: 0,
fontFamily: fontFamily.base,
fontSize: 18,
fontWeight: "normal",
},
image: {
height: "1.3em",
marginLeft: "-0.2em",
marginRight: "0.2em",
},
});
export function LogoRenderer({ classes, children }) {
return (
<h1 className={classes.logo}>
<img className={classes.image} src={logo} alt="React logo" />
{children}
</h1>
);
}
LogoRenderer.propTypes = {
classes: PropTypes.object.isRequired,
children: PropTypes.node,
};
export default Styled(styles)(LogoRenderer);

3
docs/components/logo.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
<path d="M288.258 240.0394L717.5586-.44c.803 62.277-1.8207 124.502-1.4996 186.7266 1.8208 7.6343-7.2288 10.1966-12.102 13.4908L286.4375 432.1518l1.8206-192.1124zm2.284 277.645L711 278.3176l-.8416 192.7742L457.357 614.514l-1.842 289.03-167.7097 95.8926 2.7365-481.753z"/>
</svg>

After

Width:  |  Height:  |  Size: 349 B

25
docs/development.md Normal file
View File

@@ -0,0 +1,25 @@
Sooner or later, you will face with situation when you want/need to make some
changes in the library. Then the most important tool for you it's the
[`npm link`](https://docs.npmjs.com/cli/link).
Please, notice that it will not work if you link the library just from the root
of the repo. It happens due to the location of sources `./src`. You need to pack
the library first, `make pack` and then link it from the `./dist` directory.
Yeah, it's not such a comfortable solution for development. But it can be fixed
by writing a small script similar to making a pack but by linking every file and
directory from `./src` to the same directory and linking then from it. Notice
that you need to link a `package.json` and a `package-lock.json` as well.
So step by step:
```bash
make pack;
cd dist;
npm link;
cd $project_dir/js # Navigate to JS directory of the project where you want to link the library
npm link foris
```
And that's it ;)

4
docs/forisjs-npm.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" fill="white">
<path d="M49.5 171.722222222222h400v133.333333333333h-200v22.222222222223h-88.888888888889v-22.222222222223H49.5V171.722222222222zm22.222222222222 111.111111111111h44.444444444445v-66.666666666667h22.222222222222v66.666666666667h22.222222222222v-88.888888888889H71.722222222222v88.888888888889zm111.111111111111-88.888888888889v111.111111111111h44.444444444445v-22.222222222222h44.444444444444v-88.888888888889h-88.888888888889zm44.444444444445 22.222222222222H249.5v44.444444444445h-22.222222222222v-44.444444444445zm66.666666666666-22.222222222222v88.888888888889h44.444444444445v-66.666666666667h22.222222222222v66.666666666667h22.222222222222v-66.666666666667h22.222222222222v66.666666666667h22.222222222223v-88.888888888889H293.944444444444z" fill="#cb3837" />
<path d="M71.722222222222 282.833333333333h44.444444444444v-66.666666666667h22.222222222223v66.666666666667h22.222222222222v-88.888888888889H71.722222222222zm111.111111111111-88.888888888889v111.111111111111h44.444444444444v-22.222222222222h44.444444444445v-88.888888888889h-88.888888888889zM249.5 260.611111111111h-22.222222222223v-44.444444444445H249.5v44.444444444445zm44.444444444444-66.666666666667v88.888888888889h44.444444444444v-66.666666666667h22.222222222223v66.666666666667h22.222222222222v-66.666666666667h22.222222222222v66.666666666667h22.222222222222v-88.888888888889z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
Foris JS library is set of componets and utils for Foris JS application and plugins.

37
docs/introduction.md Normal file
View File

@@ -0,0 +1,37 @@
Welcome! This is the official documentation for Foris JS.
## What Foris JS is
Foris JS library is a set of components and utils for reForis application and
plugins.
Please notice that all of these components or utils are used in reForis and
plugins. If you want to study them by example, I recommend you to full-text
search those repositories.
# Installation
## Prerequisites
Please make sure that [Node.js](https://nodejs.org/en/) is installed on your
system.
The current Long Term Support (LTS) release is an ideal starting point, see
[here](https://github.com/nodejs/Release#release-schedule).
## Installation
To install the latest release:
```plain
npm install foris
```
To install a specific version:
```plain
npm install foris@version
```
<a target="_blank" href="https://www.npmjs.com/package/foris">Check
on<img width="100px" src="./docs/forisjs-npm.svg"></a>

View File

@@ -12,10 +12,13 @@ module.exports = {
"<rootDir>/src/testUtils", "<rootDir>/src/testUtils",
"<rootDir>/src/", "<rootDir>/src/",
], ],
moduleNameMapper: {
"\\.(css|less)$": "<rootDir>/src/__mocks__/styleMock.js",
},
clearMocks: true, clearMocks: true,
collectCoverageFrom: ["src/**/*.{js,jsx}"], collectCoverageFrom: ["src/**/*.{js,jsx}"],
coverageDirectory: "coverage", coverageDirectory: "coverage",
testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/"], testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/", "/dist/"],
verbose: false, verbose: false,
setupFilesAfterEnv: [ setupFilesAfterEnv: [
"@testing-library/react/cleanup-after-each", "@testing-library/react/cleanup-after-each",
@@ -24,8 +27,5 @@ module.exports = {
globals: { globals: {
TZ: "utc", TZ: "utc",
}, },
transform: { transformIgnorePatterns: ["node_modules/(?!(react-datetime)/)"],
"^.+\\.js$": "babel-jest",
"^.+\\.css$": "jest-transform-css",
},
}; };

46407
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +1,70 @@
{ {
"name": "foris", "name": "foris",
"version": "1.1.0", "version": "5.4.1",
"description": "Set of components and utils for Foris and its plugins.", "description": "Foris JS library is a set of components and utils for reForis application and plugins.",
"author": "CZ.NIC, z.s.p.o.", "author": "CZ.NIC, z.s.p.o.",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitlab.labs.nic.cz/turris/reforis/forisjs.git" "url": "https://gitlab.nic.cz/turris/reforis/foris-js.git"
}, },
"keywords": [ "keywords": [
"foris", "foris",
"reforis" "reforis"
], ],
"license": "GPL-3.0", "license": "GPL-3.0",
"main": "./dist/index.js", "main": "./src/index.js",
"dependencies": { "dependencies": {
"axios": "^0.19.0", "axios": "^0.21.1",
"immutability-helper": "^3.0.0", "immutability-helper": "3.0.1",
"jest-transform-css": "^2.0.0", "moment": "^2.24.0",
"moment": "^2.24.0", "qrcode.react": "^1.0.1",
"moment-timezone": "^0.5.25", "react-datetime": "^3.1.1",
"prop-types": "^15.7.2", "react-uid": "^2.2.0"
"react-datetime": "^2.16.3", },
"react-router": "^5.0.1", "peerDependencies": {
"react-uid": "^2.2.0" "bootstrap": "4.4.1",
}, "prop-types": "15.8.1",
"peerDependencies": { "react": "16.9.0",
"react": "^16.9.0", "react-dom": "16.9.0",
"react-dom": "^16.9.0" "react-router-dom": "^5.1.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.4.4", "@babel/cli": "^7.12.10",
"@babel/core": "^7.4.5", "@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/plugin-transform-runtime": "^7.9.0",
"@babel/plugin-syntax-export-default-from": "^7.2.0", "@babel/preset-env": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.4.4", "@babel/preset-react": "^7.9.4",
"@babel/preset-env": "^7.4.5", "@fortawesome/fontawesome-free": "^5.13.0",
"@babel/preset-react": "^7.0.0", "@testing-library/react": "^8.0.9",
"@fortawesome/fontawesome-free": "^5.11.2", "babel-loader": "^8.1.0",
"@testing-library/react": "^8.0.9", "babel-polyfill": "^6.26.0",
"babel-eslint": "^9.0.0", "bootstrap": "^4.5.0",
"babel-jest": "^24.8.0", "css-loader": "^5.2.4",
"babel-loader": "^8.0.6", "eslint": "^6.8.0",
"babel-plugin-module-resolver": "^3.2.0", "eslint-config-prettier": "^6.11.0",
"babel-plugin-react-transform": "^3.0.0", "eslint-config-reforis": "^1.0.0",
"babel-polyfill": "^6.26.0", "eslint-plugin-prettier": "^3.1.4",
"bootstrap": "^4.3.1", "file-loader": "^6.0.0",
"copy-webpack-plugin": "^5.0.4", "jest": "^25.2.0",
"css-loader": "^3.2.0", "jest-mock-axios": "^3.2.0",
"eslint": "^6.1.0", "moment-timezone": "^0.5.34",
"eslint-config-airbnb": "^18.0.1", "prettier": "2.0.5",
"eslint-plugin-import": "^2.18.2", "prop-types": "15.8.1",
"eslint-plugin-jsx-a11y": "^6.2.3", "react": "16.9.0",
"eslint-plugin-react": "^7.14.3", "react-dom": "16.9.0",
"eslint-plugin-react-hooks": "^1.7.0", "react-router-dom": "^5.1.2",
"file-loader": "^4.2.0", "react-styleguidist": "^11.2.0",
"jest": "^24.8.0", "snapshot-diff": "^0.7.0",
"jest-mock-axios": "^3.0.0", "style-loader": "^1.2.1",
"moment": "^2.24.0", "webpack": "^5.68.0"
"moment-timezone": "^0.5.25", },
"react": "^16.9.0", "scripts": {
"react-dom": "^16.9.0", "lint": "eslint src",
"react-styleguidist": "^9.1.16", "lint:fix": "eslint --fix src",
"snapshot-diff": "^0.5.1", "test": "jest",
"style-loader": "^1.0.0", "test:watch": "jest --watch",
"webpack": "^4.41.0" "test:coverage": "jest --coverage --colors",
}, "docs": "npx styleguidist build ",
"scripts": { "docs:watch": "styleguidist server"
"build": "rm -rf dist; babel src --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files", }
"build:watch": "babel src --verbose --watch --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
"prepare": "rm -rf ./dist && npm run build",
"lint": "eslint src",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage --colors",
"test:update-snapshots": "jest -u",
"docs": "npx styleguidist build ",
"docs:watch": "styleguidist server"
},
"files": [
"dist/**",
"translations"
]
} }

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
Babel

13
scripts/collect_files.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
# Collect files
mkdir -p dist
cp -rf ./src/* dist
cp package.json package-lock.json README.md dist
sed -i 's/\/src//g' dist/package.json # remove ./src from main js file path
cp -rf translations dist
# Remove unwanted files
find dist -type d -name __tests__ -exec rm -r {} +
rm -rf dist/__mocks__

View File

@@ -5,8 +5,8 @@ then
echo "\$NPM_TOKEN is not set" echo "\$NPM_TOKEN is not set"
exit 1 exit 1
else else
# Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-" cd dist
echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN" | tr _ -)" > .npmrc echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN")" > .npmrc
echo "unsafe-perm = true" >> ~/.npmrc echo "unsafe-perm = true" >> ~/.npmrc
if test "$1" = "beta" if test "$1" = "beta"
then then

View File

@@ -0,0 +1,8 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
module.exports = {};

View File

@@ -5,12 +5,11 @@
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
import React, { useState } from "react"; import React, { useState, useContext, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Alert } from "bootstrap/Alert"; import { Alert, ALERT_TYPES } from "../bootstrap/Alert";
import { Portal } from "../utils/Portal";
const AlertContext = React.createContext();
AlertContextProvider.propTypes = { AlertContextProvider.propTypes = {
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
@@ -20,16 +19,37 @@ AlertContextProvider.propTypes = {
}; };
function AlertContextProvider({ children }) { function AlertContextProvider({ children }) {
const { AlertContext } = window;
const [alert, setAlert] = useState(null); const [alert, setAlert] = useState(null);
const setAlertWrapper = useCallback(
(message, type = ALERT_TYPES.DANGER) => {
setAlert({ message, type });
},
[setAlert]
);
const dismissAlert = useCallback(() => setAlert(null), [setAlert]);
return ( return (
<> <>
{alert && <Alert type="danger" message={alert} onDismiss={() => setAlert(null)} />} {alert && (
<AlertContext.Provider value={setAlert}> <Portal containerId="alert-container">
{ children } <Alert type={alert.type} onDismiss={dismissAlert}>
{alert.message}
</Alert>
</Portal>
)}
<AlertContext.Provider value={[setAlertWrapper, dismissAlert]}>
{children}
</AlertContext.Provider> </AlertContext.Provider>
</> </>
); );
} }
export { AlertContext, AlertContextProvider }; function useAlert() {
const { AlertContext } = window;
return useContext(AlertContext);
}
export { AlertContextProvider, useAlert };

View File

@@ -0,0 +1,5 @@
It provides alert context to children. `AlertContext` allows using `useAlert` in
components.
Notice that `<div id="alert-container"/>` should be presented in HTML doc to get
it work (In reForis it's already done with base Jinja2 templates).

View File

@@ -5,15 +5,22 @@
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
import React, { useContext } from "react"; import React from "react";
import { render, getByText, queryByText, fireEvent } from "customTestRender"; import { render, getByText, queryByText, fireEvent } from "customTestRender";
import { AlertContext, AlertContextProvider } from "../AlertContext"; import { useAlert, AlertContextProvider } from "../AlertContext";
function AlertTest() { function AlertTest() {
const setAlert = useContext(AlertContext); const [setAlert, dismissAlert] = useAlert();
return <button onClick={() => setAlert("Alert content")}>Set alert</button>; // alert-container serves as an output for Portal which renders Alert
}; return (
<>
<div id="alert-container" />
<button onClick={() => setAlert("Alert content")}>Set alert</button>
<button onClick={dismissAlert}>Dismiss alert</button>
</>
);
}
describe("AlertContext", () => { describe("AlertContext", () => {
let componentContainer; let componentContainer;
@@ -36,7 +43,7 @@ describe("AlertContext", () => {
expect(componentContainer).toMatchSnapshot(); expect(componentContainer).toMatchSnapshot();
}); });
it("should dismiss alert", () => { it("should dismiss alert with alert button", () => {
fireEvent.click(getByText(componentContainer, "Set alert")); fireEvent.click(getByText(componentContainer, "Set alert"));
// Alert is present // Alert is present
expect(getByText(componentContainer, "Alert content")).toBeDefined(); expect(getByText(componentContainer, "Alert content")).toBeDefined();
@@ -45,4 +52,14 @@ describe("AlertContext", () => {
// Alert is gone // Alert is gone
expect(queryByText(componentContainer, "Alert content")).toBeNull(); expect(queryByText(componentContainer, "Alert content")).toBeNull();
}); });
it("should dismiss alert with external button", () => {
fireEvent.click(getByText(componentContainer, "Set alert"));
// Alert is present
expect(getByText(componentContainer, "Alert content")).toBeDefined();
fireEvent.click(getByText(componentContainer, "Dismiss alert"));
// Alert is gone
expect(queryByText(componentContainer, "Alert content")).toBeNull();
});
}); });

View File

@@ -3,26 +3,39 @@
exports[`AlertContext should render alert 1`] = ` exports[`AlertContext should render alert 1`] = `
<div> <div>
<div <div
class="alert alert-dismissible alert-danger" id="alert-container"
> >
<button <div
class="close" class="alert alert-dismissible alert-danger"
type="button"
> >
× <button
</button> class="close"
Alert content type="button"
>
×
</button>
Alert content
</div>
</div> </div>
<button> <button>
Set alert Set alert
</button> </button>
<button>
Dismiss alert
</button>
</div> </div>
`; `;
exports[`AlertContext should render component without alert 1`] = ` exports[`AlertContext should render component without alert 1`] = `
<div> <div>
<div
id="alert-container"
/>
<button> <button>
Set alert Set alert
</button> </button>
<button>
Dismiss alert
</button>
</div> </div>
`; `;

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import { useReducer, useCallback } from "react";
import axios from "axios";
import {
API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
} from "./utils";
export function useAPIDelete(url) {
const [state, dispatch] = useReducer(APIReducer, {
isSending: false,
isError: false,
isSuccess: false,
data: null,
});
const requestDelete = useCallback(async () => {
dispatch({ type: API_ACTIONS.INIT });
try {
await axios.delete(url, {
timeout: TIMEOUT,
headers: HEADERS,
});
dispatch({ type: API_ACTIONS.SUCCESS });
} catch (error) {
dispatch({
type: API_ACTIONS.FAILURE,
payload: getErrorMessage(error),
status: error.response.status,
});
}
}, [url]);
return [state, requestDelete];
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import { useReducer, useCallback } from "react";
import axios from "axios";
import { ForisURLs } from "forisUrls";
import { API_ACTIONS, TIMEOUT } from "./utils";
const APIGetReducer = (state, action) => {
switch (action.type) {
case API_ACTIONS.INIT:
return {
...state,
isLoading: true,
isError: false,
};
case API_ACTIONS.SUCCESS:
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
case API_ACTIONS.FAILURE:
if (action.status === 403) window.location.assign(ForisURLs.login);
return {
...state,
isLoading: false,
isError: true,
data: action.payload,
};
default:
throw new Error();
}
};
export function useAPIGet(url) {
const [state, dispatch] = useReducer(APIGetReducer, {
isLoading: false,
isError: false,
data: null,
});
const get = useCallback(async () => {
dispatch({ type: API_ACTIONS.INIT });
try {
const result = await axios.get(url, {
timeout: TIMEOUT,
});
dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
} catch (error) {
dispatch({
type: API_ACTIONS.FAILURE,
payload: error.response.data,
status: error.response.status,
});
}
}, [url]);
return [state, get];
}

135
src/api/hooks.js Normal file
View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import { useCallback, useEffect, useReducer, useState } from "react";
import {
API_ACTIONS,
API_METHODS,
API_STATE,
getErrorPayload,
HEADERS,
TIMEOUT,
} from "./utils";
const DATA_METHODS = ["POST", "PATCH", "PUT"];
function createAPIHook(method) {
return (urlRoot, contentType) => {
const [state, dispatch] = useReducer(APIReducer, {
state: API_STATE.INIT,
data: null,
});
const sendRequest = useCallback(
async ({ data, suffix } = {}) => {
const headers = { ...HEADERS };
if (contentType) {
headers["Content-Type"] = contentType;
}
dispatch({ type: API_ACTIONS.INIT });
try {
// Prepare request
const request = API_METHODS[method];
const config = {
timeout: TIMEOUT,
headers,
};
const url = suffix ? `${urlRoot}/${suffix}` : urlRoot;
// Make request
let result;
if (DATA_METHODS.includes(method)) {
result = await request(url, data, config);
} else {
result = await request(url, config);
}
// Process request result
dispatch({
type: API_ACTIONS.SUCCESS,
payload: result.data,
});
} catch (error) {
const errorPayload = getErrorPayload(error);
dispatch({
type: API_ACTIONS.FAILURE,
status: error.response && error.response.status,
payload: errorPayload,
});
}
},
[urlRoot, contentType]
);
return [state, sendRequest];
};
}
function APIReducer(state, action) {
switch (action.type) {
case API_ACTIONS.INIT:
return {
...state,
state: API_STATE.SENDING,
};
case API_ACTIONS.SUCCESS:
return {
state: API_STATE.SUCCESS,
data: action.payload,
};
case API_ACTIONS.FAILURE:
if (action.status === 401) {
window.location.reload();
}
// Not an API error - should be rethrown.
if (
action.payload &&
action.payload.stack &&
action.payload.message
) {
throw action.payload;
}
return {
state: API_STATE.ERROR,
data: action.payload,
};
default:
throw new Error();
}
}
const useAPIGet = createAPIHook("GET");
const useAPIPost = createAPIHook("POST");
const useAPIPatch = createAPIHook("PATCH");
const useAPIPut = createAPIHook("PUT");
const useAPIDelete = createAPIHook("DELETE");
export { useAPIGet, useAPIPost, useAPIPatch, useAPIPut, useAPIDelete };
export function useAPIPolling(endpoint, delay = 1000, until) {
// delay ms
const [state, setState] = useState({ state: API_STATE.INIT });
const [getResponse, get] = useAPIGet(endpoint);
useEffect(() => {
if (getResponse.state !== API_STATE.INIT) {
setState(getResponse);
}
}, [getResponse]);
useEffect(() => {
if (until) {
const interval = setInterval(get, delay);
return () => clearInterval(interval);
}
}, [until, delay, get]);
return [state];
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import { useReducer } from "react";
import axios from "axios";
import {
API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
} from "./utils";
export function useAPIPatch(url) {
const [state, dispatch] = useReducer(APIReducer, {
isSending: false,
isError: false,
isSuccess: false,
data: null,
});
const patch = async (data) => {
dispatch({ type: API_ACTIONS.INIT });
try {
const result = await axios.patch(url, data, {
timeout: TIMEOUT,
headers: HEADERS,
});
dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
} catch (error) {
dispatch({
type: API_ACTIONS.FAILURE,
payload: getErrorMessage(error),
status: error.response.status,
});
}
};
return [state, patch];
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import { useReducer } from "react";
import axios from "axios";
import {
API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
} from "./utils";
export function useAPIPost(url, contentType) {
const [state, dispatch] = useReducer(APIReducer, {
isSending: false,
isError: false,
isSuccess: false,
data: null,
});
const headers = { ...HEADERS };
if (contentType) {
headers["Content-Type"] = contentType;
}
const post = async (data) => {
dispatch({ type: API_ACTIONS.INIT });
try {
const result = await axios.post(url, data, {
timeout: TIMEOUT,
headers,
});
dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
} catch (error) {
dispatch({
type: API_ACTIONS.FAILURE,
payload: getErrorMessage(error),
status: error.response.status,
});
}
};
return [state, post];
}

View File

@@ -1,11 +1,41 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
import { ForisURLs } from "forisUrls"; import axios from "axios";
export const HEADERS = {
Accept: "application/json",
"Content-Type": "application/json",
"X-CSRFToken": getCookie("_csrf_token"),
"X-Requested-With": "json",
};
export const TIMEOUT = 30500;
export const API_ACTIONS = {
INIT: 1,
SUCCESS: 2,
FAILURE: 3,
};
export const API_STATE = {
INIT: "init",
SENDING: "sending",
SUCCESS: "success",
ERROR: "error",
};
export const API_METHODS = {
GET: axios.get,
POST: axios.post,
PATCH: axios.patch,
PUT: axios.put,
DELETE: axios.delete,
};
function getCookie(name) { function getCookie(name) {
let cookieValue = null; let cookieValue = null;
@@ -14,8 +44,10 @@ function getCookie(name) {
for (let i = 0; i < cookies.length; i++) { for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim(); const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want? // Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (`${name}=`)) { if (cookie.substring(0, name.length + 1) === `${name}=`) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); cookieValue = decodeURIComponent(
cookie.substring(name.length + 1)
);
break; break;
} }
} }
@@ -23,55 +55,26 @@ function getCookie(name) {
return cookieValue; return cookieValue;
} }
export const HEADERS = { export function getErrorPayload(error) {
Accept: "application/json", if (error.response) {
"Content-Type": "application/json", if (error.response.status === 401) {
"X-CSRFToken": getCookie("_csrf_token"), return _("The session is expired. Please log in again.");
}; }
return getJSONErrorMessage(error);
export const TIMEOUT = 5000;
export const API_ACTIONS = {
INIT: 1,
SUCCESS: 2,
FAILURE: 3,
};
export function APIReducer(state, action) {
switch (action.type) {
case API_ACTIONS.INIT:
return {
...state,
isSending: true,
isError: false,
isSuccess: false,
};
case API_ACTIONS.SUCCESS:
return {
...state,
isSending: false,
isError: false,
isSuccess: true,
data: action.payload,
};
case API_ACTIONS.FAILURE:
if (action.status === 403) window.location.assign(ForisURLs.login);
return {
...state,
isSending: false,
isError: true,
isSuccess: false,
data: action.payload,
};
default:
throw new Error();
} }
if (error.code === "ECONNABORTED") {
return _("Timeout error occurred.");
}
if (error.request) {
return _("No response received.");
}
// Return original error because it's not directly related to API request/response.
return error;
} }
export function getErrorMessage(error) { export function getJSONErrorMessage(error) {
let payload = "An unknown error occurred";
if (error.response.headers["content-type"] === "application/json") { if (error.response.headers["content-type"] === "application/json") {
payload = error.response.data; return error.response.data;
} }
return payload; return _("An unknown API error occurred.");
} }

View File

@@ -8,11 +8,20 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
export const ALERT_TYPES = Object.freeze({
PRIMARY: "primary",
SECONDARY: "secondary",
SUCCESS: "success",
DANGER: "danger",
WARNING: "warning",
INFO: "info",
LIGHT: "light",
DARK: "dark",
});
Alert.propTypes = { Alert.propTypes = {
/** Type of the alert it adds as `alert-${type}` class. */ /** Type of the alert it adds as `alert-${type}` class. */
type: PropTypes.string.isRequired, type: PropTypes.oneOf(Object.values(ALERT_TYPES)),
/** Alert message. */
message: PropTypes.string,
/** Alert content. */ /** Alert content. */
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node), PropTypes.arrayOf(PropTypes.node),
@@ -22,13 +31,24 @@ Alert.propTypes = {
onDismiss: PropTypes.func, onDismiss: PropTypes.func,
}; };
export function Alert({ Alert.defaultProps = {
type, message, onDismiss, children, type: ALERT_TYPES.DANGER,
}) { };
export function Alert({ type, onDismiss, children }) {
return ( return (
<div className={`alert alert-dismissible alert-${type}`}> <div
{onDismiss ? <button type="button" className="close" onClick={onDismiss}>&times;</button> : false} className={`alert ${
{message} onDismiss ? "alert-dismissible" : ""
} alert-${type}`}
>
{onDismiss ? (
<button type="button" className="close" onClick={onDismiss}>
&times;
</button>
) : (
false
)}
{children} {children}
</div> </div>
); );

View File

@@ -1,20 +1,21 @@
Bootstrap alert component. Bootstrap alert component.
```jsx
import {useState} from 'react';
function AlertExample(){ ```jsx
import { useState } from "react";
function AlertExample() {
const [alert, setAlert] = useState(true); const [alert, setAlert] = useState(true);
if (alert) if (alert)
return <Alert return (
type='warning' <Alert type="warning" onDismiss={() => setAlert(false)}>
message='Some warning out there!' Some warning out there!
onDismiss={()=>setAlert(false)} </Alert>
/>; );
return <button return (
className='btn btn-secondary' <button className="btn btn-secondary" onClick={() => setAlert(true)}>
onClick={()=>setAlert(true)} Show alert again
>Show alert again</button>; </button>
}; );
<AlertExample/> }
<AlertExample />;
``` ```

View File

@@ -8,11 +8,6 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
const OFFSET = 8;
const SIZE = 3;
const SIZE_CLASS = ` offset-lg-${OFFSET} col-lg-${SIZE}`;
const SIZE_CLASS_SM = " col-sm-12";
Button.propTypes = { Button.propTypes = {
/** Additional class name. */ /** Additional class name. */
className: PropTypes.string, className: PropTypes.string,
@@ -30,20 +25,29 @@ Button.propTypes = {
}; };
export function Button({ export function Button({
className, loading, forisFormSize, children, ...props className,
loading,
forisFormSize,
children,
...props
}) { }) {
className = className ? `btn ${className}` : "btn btn-primary "; let buttonClass = className ? `btn ${className}` : "btn btn-primary ";
if (forisFormSize) className += SIZE_CLASS + SIZE_CLASS_SM; if (forisFormSize) {
buttonClass = `${buttonClass} col-sm-12 col-md-3 col-lg-2`;
}
const span = loading const span = loading ? (
? <span className="spinner-border spinner-border-sm" role="status" aria-hidden="true" /> : null; <span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
/>
) : null;
return ( return (
<button type="button" className={className} {...props}> <button type="button" className={buttonClass} {...props}>
{span} {span}
{" "}
{span ? " " : null} {span ? " " : null}
{" "}
{children} {children}
</button> </button>
); );

View File

@@ -11,5 +11,7 @@ Can be used without parameters:
Using loading spinner: Using loading spinner:
```jsx ```jsx
<Button loading disabled>Loading...</Button> <Button loading disabled>
Loading...
</Button>
``` ```

View File

@@ -9,42 +9,40 @@ import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useUID } from "react-uid"; import { useUID } from "react-uid";
import { formFieldsSize } from "./constants";
CheckBox.propTypes = { CheckBox.propTypes = {
/** Label message */ /** Label message */
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
/** Help text message */ /** Help text message */
helpText: PropTypes.string, helpText: PropTypes.string,
/** Apply default size (full-width) */
useDefaultSize: PropTypes.bool,
/** Control if checkbox is clickable */ /** Control if checkbox is clickable */
disabled: PropTypes.bool, disabled: PropTypes.bool,
}; };
CheckBox.defaultProps = { CheckBox.defaultProps = {
useDefaultSize: true,
disabled: false, disabled: false,
}; };
export function CheckBox({ export function CheckBox({ label, helpText, disabled, ...props }) {
label, helpText, useDefaultSize, disabled, ...props
}) {
const uid = useUID(); const uid = useUID();
return ( return (
<div className={useDefaultSize ? formFieldsSize : ""} style={{ marginBottom: "1rem" }}> <div className="form-group">
<div className="custom-control custom-checkbox" style={{ marginBottom: "0" }}> <div className="custom-control custom-checkbox ">
<input <input
className="custom-control-input" className="custom-control-input"
type="checkbox" type="checkbox"
id={uid} id={uid}
disabled={disabled} disabled={disabled}
{...props} {...props}
/> />
<label className="custom-control-label" htmlFor={uid} style={helpText ? { marginBottom: "0" } : null}>{label}</label> <label className="custom-control-label" htmlFor={uid}>
{label}
{helpText && (
<small className="form-text text-muted">
{helpText}
</small>
)}
</label>
</div> </div>
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</div> </div>
); );
} }

View File

@@ -1,16 +1,17 @@
Checkbox with label Bootstrap component with predefined sizes and structure for using in foris forms. Checkbox with label Bootstrap component with predefined sizes and structure for
using in foris forms.
All additional `props` are passed to the `<input type="checkbox">` HTML component.
All additional `props` are passed to the `<input type="checkbox">` HTML
component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const [value, setValue] = useState(false); const [value, setValue] = useState(false);
<CheckBox <CheckBox
value={value} value={value}
label="Some label" label="Some label"
helpText="Read the small text!" helpText="Read the small text!"
onChange={event =>setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
/> />;
``` ```

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React, { useState, useRef } from "react";
import PropTypes from "prop-types";
import { Input } from "./Input";
CopyInput.propTypes = {
/** Field label. */
label: PropTypes.string.isRequired,
/** Field value. */
value: PropTypes.string,
/** Help text message. */
helpText: PropTypes.string,
/** Disable input field */
disabled: PropTypes.bool,
/** Readonly input field */
readOnly: PropTypes.bool,
};
export function CopyInput({ value, ...props }) {
const inputTextRef = useRef();
const [isCopied, setIsCopied] = useState(false);
const handleCopyClick = async () => {
// Clipboard API works only in a secure (HTTPS) context.
if (navigator.clipboard) {
await navigator.clipboard.writeText(value);
} else {
// Fallback to the "classic" copy to clipboard implementation.
inputTextRef.current.focus();
inputTextRef.current.select();
document.execCommand("copy");
inputTextRef.current.blur();
}
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 1500);
};
return (
<Input type="text" value={value} ref={inputTextRef} {...props}>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={handleCopyClick}
>
<span>{isCopied ? _("Copied!") : _("Copy")}</span>
</button>
</div>
</Input>
);
}

View File

@@ -0,0 +1,17 @@
CopyInput Bootstrap component contains input with a label, predefined sizes, and
structure for use in ForisForm and the "Copy" button (copy to clipboard). It can
be used with `readOnly` and `disabled` parameters, please see an example.
All additional `props` are passed to the `<input type="text">` HTML component.
```js
import React, { useState } from "react";
const [value, setValue] = useState("Text to appear in clipboard.");
<CopyInput
label="Copy me"
value={value}
helpText="Read the small text!"
readOnly
/>;
```

View File

@@ -0,0 +1,4 @@
/* Override defaults from "react-datetime" - display picker above input */
.rdtPicker {
bottom: 0;
}

View File

@@ -7,9 +7,10 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Datetime from "react-datetime/DateTime"; import Datetime from "react-datetime";
import moment from "moment/moment"; import moment from "moment/moment";
import "react-datetime/css/react-datetime.css"; import "react-datetime/css/react-datetime.css";
import "./DataTimeInput.css";
import { Input } from "./Input"; import { Input } from "./Input";
@@ -37,14 +38,17 @@ const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
const DEFAULT_TIME_FORMAT = "HH:mm:ss"; const DEFAULT_TIME_FORMAT = "HH:mm:ss";
export function DataTimeInput({ export function DataTimeInput({
value, onChange, isValidDate, dateFormat, timeFormat, children, ...props value,
onChange,
isValidDate,
dateFormat,
timeFormat,
children,
...props
}) { }) {
function renderInput(datetimeProps) { function renderInput(datetimeProps) {
return ( return (
<Input <Input {...props} {...datetimeProps}>
{...props}
{...datetimeProps}
>
{children} {children}
</Input> </Input>
); );
@@ -53,8 +57,12 @@ export function DataTimeInput({
return ( return (
<Datetime <Datetime
locale={ForisTranslations.locale} locale={ForisTranslations.locale}
dateFormat={dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT} dateFormat={
timeFormat={timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT} dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT
}
timeFormat={
timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT
}
value={value} value={value}
onChange={onChange} onChange={onChange}
isValidDate={isValidDate} isValidDate={isValidDate}

View File

@@ -1,25 +1,26 @@
Adopted from `react-datetime/DateTime` datatime picker component. Adopted from `react-datetime/DateTime` datatime picker component. It uses
It uses `momentjs` see example. `momentjs` see example.
It requires `ForisTranslations.locale` to be defined in order to use right locale. It requires `ForisTranslations.locale` to be defined in order to use right
locale.
```js ```js
ForisTranslations={locale:'en'}; ForisTranslations = { locale: "en" };
import {useState, useEffect} from 'react'; import { useState, useEffect } from "react";
import moment from 'moment/moment'; import moment from "moment/moment";
const [dataTime, setDataTime] = useState(moment()); const [dataTime, setDataTime] = useState(moment());
const [error, setError] = useState(); const [error, setError] = useState();
useEffect(()=>{ useEffect(() => {
dataTime.isValid() ? setError(null) : setError('Invalid value!'); dataTime.isValid() ? setError(null) : setError("Invalid value!");
},[dataTime]); }, [dataTime]);
<DataTimeInput <DataTimeInput
label='Time to sleep' label="Time to sleep"
value={dataTime} value={dataTime}
error={error} error={error}
helpText='Example helptext...' helpText="Example helptext..."
onChange={value => setDataTime(value)} onChange={(value) => setDataTime(value)}
/> />;
``` ```

View File

@@ -10,12 +10,26 @@ import PropTypes from "prop-types";
DownloadButton.propTypes = { DownloadButton.propTypes = {
href: PropTypes.string.isRequired, href: PropTypes.string.isRequired,
className: PropTypes.string,
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node), PropTypes.arrayOf(PropTypes.node),
PropTypes.node, PropTypes.node,
]), ]),
}; };
export function DownloadButton({ href, children }) { DownloadButton.defaultProps = {
return <a href={href} className="btn btn-primary" download>{children}</a>; className: "btn-primary",
};
export function DownloadButton({ href, className, children, ...props }) {
return (
<a
href={href}
className={`btn ${className}`.trim()}
{...props}
download
>
{children}
</a>
);
} }

View File

@@ -1,6 +1,9 @@
Hyperlink with apperance of a button. Hyperlink with apperance of a button.
It has `download` attribute, which prevents closing WebSocket connection on Firefox. See [related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more details. It has `download` attribute, which prevents closing WebSocket connection on
Firefox. See
[related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more
details.
```js ```js
<DownloadButton href="example.zip">Download</DownloadButton> <DownloadButton href="example.zip">Download</DownloadButton>

View File

@@ -6,13 +6,12 @@
*/ */
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Input } from "./Input"; import { Input } from "./Input";
export const EmailInput = ({ ...props }) => <Input type="email" {...props} />; export const EmailInput = ({ ...props }) => <Input type="email" {...props} />;
EmailInput.propTypes = { EmailInput.propTypes = {
/** Field label. */ /** Field label. */
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,

View File

@@ -1,18 +1,19 @@
Bootstrap component of email input with label with predefined sizes and structure for using in foris forms. Bootstrap component of email input with label with predefined sizes and
It use built-in browser email address checking. It's only meaningful using inside `<form>`. structure for using in foris forms. It use built-in browser email address
checking. It's only meaningful using inside `<form>`.
All additional `props` are passed to the `<input type="email">` HTML component. All additional `props` are passed to the `<input type="email">` HTML component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const [email, setEmail] = useState('Wrong email'); const [email, setEmail] = useState("Wrong email");
<form onSubmit={e=>e.preventDefault()}> <form onSubmit={(e) => e.preventDefault()}>
<EmailInput <EmailInput
value={email} value={email}
label="Some label" label="Some label"
helpText="Read the small text!" helpText="Read the small text!"
onChange={event =>setEmail(event.target.value)} onChange={(event) => setEmail(event.target.value)}
/> />
<button type="submit">Try to submit</button> <button type="submit">Try to submit</button>
</form> </form>;
``` ```

View File

@@ -19,6 +19,8 @@ FileInput.propTypes = {
helpText: PropTypes.string, helpText: PropTypes.string,
/** Email value. */ /** Email value. */
value: PropTypes.string, value: PropTypes.string,
/** Allow selecting multiple files. */
multiple: PropTypes.bool,
}; };
export function FileInput({ ...props }) { export function FileInput({ ...props }) {

View File

@@ -1,15 +1,48 @@
Bootstrap component for file input. Includes label and has predefined sizes and structure for using in foris forms. Bootstrap component for file input. Includes label and has predefined sizes and
structure for using in foris forms.
All additional `props` are passed to the `<input type="file">` HTML component. All additional `props` are passed to the `<input type="file">` HTML component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
<FileInput // Note that files is not an array but FileList.
files={files} const label = files.length === 1 ? files[0].name : "Choose file";
label="Some file"
helpText="Will be uploaded" <form className="col">
onChange={event =>setFiles(event.target.files)} <FileInput
/> files={files}
label={label}
helpText="Will be uploaded"
onChange={(event) => setFiles(event.target.files)}
/>
</form>;
```
### FileInput with multiple files
```js
import { useState } from "react";
const [files, setFiles] = useState([]);
// Note that files is not an array but FileList.
const label =
files.length > 0
? Array.from(files)
.map((file) => file.name)
.join(", ")
: "Choose files";
<form className="col">
<FileInput
files={files}
label={label}
helpText="Will be uploaded"
onChange={(event) => setFiles(event.target.files)}
multiple
/>
</form>;
``` ```

View File

@@ -1,15 +1,59 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
import React from "react"; import React, { forwardRef } from "react";
import { useUID } from "react-uid"; import { useUID } from "react-uid";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { formFieldsSize } from "./constants"; /** Base bootstrap input component. */
export const Input = forwardRef(
(
{
type,
label,
helpText,
error,
className,
children,
labelClassName,
groupClassName,
...props
},
ref
) => {
const uid = useUID();
const inputClassName = `form-control ${className || ""} ${
error ? "is-invalid" : ""
}`.trim();
return (
<div className="form-group">
<label className={labelClassName} htmlFor={uid}>
{label}
</label>
<div className={`input-group ${groupClassName || ""}`.trim()}>
<input
className={inputClassName}
type={type}
id={uid}
ref={ref}
{...props}
/>
{children}
</div>
{error ? <div className="invalid-feedback">{error}</div> : null}
{helpText ? (
<small className="form-text text-muted">{helpText}</small>
) : null}
</div>
);
}
);
Input.propTypes = { Input.propTypes = {
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
@@ -24,28 +68,3 @@ Input.propTypes = {
labelClassName: PropTypes.string, labelClassName: PropTypes.string,
groupClassName: PropTypes.string, groupClassName: PropTypes.string,
}; };
/** Base bootstrap input component. */
export function Input({
type, label, helpText, error, className, children, labelClassName, groupClassName, ...props
}) {
const uid = useUID();
const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim();
return (
<div className={`form-group ${formFieldsSize}`}>
<label className={labelClassName} htmlFor={uid}>{label}</label>
<div className={`input-group ${groupClassName || ""}`.trim()}>
<input
className={inputClassName}
type={type}
id={uid}
{...props}
/>
{children}
</div>
{error ? <div className="invalid-feedback">{error}</div> : null}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</div>
);
}

15
src/bootstrap/Modal.css Normal file
View File

@@ -0,0 +1,15 @@
@keyframes modalFade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal.show {
display: block;
animation-name: modalFade;
animation-duration: 0.3s;
background: rgba(0, 0, 0, 0.2);
}

View File

@@ -1,20 +1,24 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
import React, { useEffect, useRef } from "react"; import React, { useRef, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Portal } from "utils/Portal"; import { Portal } from "../utils/Portal";
import { useClickOutside } from "../utils/hooks";
import "./Modal.css";
Modal.propTypes = { Modal.propTypes = {
/** Is modal shown value */ /** Is modal shown value */
shown: PropTypes.bool.isRequired, shown: PropTypes.bool.isRequired,
/** Callback to manage modal visibility */ /** Callback to manage modal visibility */
setShown: PropTypes.func.isRequired, setShown: PropTypes.func.isRequired,
scrollable: PropTypes.bool,
size: PropTypes.string,
/** Modal content use following: `ModalHeader`, `ModalBody`, `ModalFooter` */ /** Modal content use following: `ModalHeader`, `ModalBody`, `ModalFooter` */
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
@@ -23,28 +27,54 @@ Modal.propTypes = {
]).isRequired, ]).isRequired,
}; };
export function Modal({ shown, setShown, children }) { export function Modal({ shown, setShown, scrollable, size, children }) {
const dialogRef = useRef(); const dialogRef = useRef();
let modalSize = "modal-";
useClickOutside(dialogRef, () => setShown(false));
useEffect(() => { useEffect(() => {
function handleClickOutsideDialog(e) { const handleEsc = (event) => {
if (!dialogRef.current.contains(e.target)) setShown(false); if (event.keyCode === 27) {
} setShown(false);
}
};
window.addEventListener("keydown", handleEsc);
document.addEventListener("mousedown", handleClickOutsideDialog);
return () => { return () => {
document.removeEventListener("mousedown", handleClickOutsideDialog); window.removeEventListener("keydown", handleEsc);
}; };
}, [setShown]); }, [setShown]);
switch (size) {
case "sm":
modalSize += "sm";
break;
case "lg":
modalSize += "lg";
break;
case "xl":
modalSize += "xl";
break;
default:
modalSize = "";
break;
}
return ( return (
<Portal containerId="modal-container"> <Portal containerId="modal-container">
<div className={`modal fade ${shown ? "show" : ""}`} role="dialog"> <div
<div ref={dialogRef} className="modal-dialog" role="document"> className={`modal fade ${shown ? "show" : ""}`.trim()}
<div className="modal-content"> role="dialog"
{children} >
</div> <div
ref={dialogRef}
className={`${modalSize.trim()} modal-dialog modal-dialog-centered ${
scrollable ? "modal-dialog-scrollable" : ""
}`.trim()}
role="document"
>
<div className="modal-content">{children}</div>
</div> </div>
</div> </div>
</Portal> </Portal>
@@ -60,7 +90,11 @@ export function ModalHeader({ setShown, title }) {
return ( return (
<div className="modal-header"> <div className="modal-header">
<h5 className="modal-title">{title}</h5> <h5 className="modal-title">{title}</h5>
<button type="button" className="close" onClick={() => setShown(false)}> <button
type="button"
className="close"
onClick={() => setShown(false)}
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
@@ -86,9 +120,5 @@ ModalFooter.propTypes = {
}; };
export function ModalFooter({ children }) { export function ModalFooter({ children }) {
return ( return <div className="modal-footer">{children}</div>;
<div className="modal-footer">
{children}
</div>
);
} }

View File

@@ -1,33 +1,47 @@
Bootstrap modal component. Bootstrap modal component.
it's required to have an element `<div id={"modal-container"}/>` somewhere on the page since modals are rendered in portals. It's required to have an element `<div id={"modal-container"}/>` somewhere on
the page since modals are rendered in portals.
Modals also have three optional sizes, which can be defined through the `size`
prop:
- small - `sm`
- large - `lg`
- extra-large - `xl`
For more details please visit Bootstrap
<a href="https://getbootstrap.com/docs/4.5/components/modal/#optional-sizes" target="_blank">
documentation</a>.
```js ```js
<div id="modal-container"/> <div id="modal-container" />
``` ```
I have no idea why example doesn't work here but you can investigate HTML code and Foris project.
```js ```js
import {ModalHeader, ModalBody, ModalFooter} from './Modal'; import { ModalHeader, ModalBody, ModalFooter } from "./Modal";
import {useState} from 'react'; import { useState } from "react";
const [shown, setShown] = useState(false); const [shown, setShown] = useState(false);
<> <>
<Modal setShown={setShown} shown={shown}> <Modal setShown={setShown} shown={shown} size="sm">
<ModalHeader setShown={setShown} title='Warning!'/> <ModalHeader setShown={setShown} title="Warning!" />
<ModalBody><p>Bla bla bla...</p></ModalBody> <ModalBody>
<p>Bla bla bla...</p>
</ModalBody>
<ModalFooter> <ModalFooter>
<button <button
className='btn btn-secondary' className="btn btn-secondary"
onClick={() => setShown(false)} onClick={() => setShown(false)}
>Skip it</button> >
Skip it
</button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
<button className='btn btn-secondary' onClick={()=>setShown(true)}> <button className="btn btn-secondary" onClick={() => setShown(true)}>
Show modal Show modal
</button> </button>
</> </>;
``` ```

View File

@@ -4,7 +4,7 @@ input[type="number"] {
appearance: textfield; appearance: textfield;
} }
input[type=number]::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
} }

View File

@@ -8,7 +8,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useConditionalTimeout } from "utils/hooks"; import { useConditionalTimeout } from "../utils/hooks";
import { Input } from "./Input"; import { Input } from "./Input";
import "./NumberInput.css"; import "./NumberInput.css";
@@ -20,10 +20,7 @@ NumberInput.propTypes = {
/** Help text message. */ /** Help text message. */
helpText: PropTypes.string, helpText: PropTypes.string,
/** Number value. */ /** Number value. */
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.string,
PropTypes.number,
]),
/** Function called when value changes. */ /** Function called when value changes. */
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
/** Additional description dispaled to the right of input value. */ /** Additional description dispaled to the right of input value. */
@@ -34,15 +31,21 @@ NumberInput.defaultProps = {
value: 0, value: 0,
}; };
export function NumberInput({ export function NumberInput({ onChange, inlineText, value, ...props }) {
onChange, inlineText, value, ...props
}) {
function updateValue(initialValue, difference) { function updateValue(initialValue, difference) {
onChange({ target: { value: initialValue + difference } }); onChange({ target: { value: initialValue + difference } });
} }
const enableIncrease = useConditionalTimeout({ callback: updateValue }, value, 1); const enableIncrease = useConditionalTimeout(
const enableDecrease = useConditionalTimeout({ callback: updateValue }, value, -1); { callback: updateValue },
value,
1
);
const enableDecrease = useConditionalTimeout(
{ callback: updateValue },
value,
-1
);
return ( return (
<Input type="number" onChange={onChange} value={value} {...props}> <Input type="number" onChange={onChange} value={value} {...props}>

View File

@@ -1,17 +1,18 @@
Bootstrap component of number input with label with predefined sizes and structure for using in foris forms. Bootstrap component of number input with label with predefined sizes and
structure for using in foris forms.
All additional `props` are passed to the `<input type="number">` HTML component. All additional `props` are passed to the `<input type="number">` HTML component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const [value, setValue] = useState(42); const [value, setValue] = useState(42);
<NumberInput <NumberInput
value={value} value={value}
label="Some number" label="Some number"
helpText="Read the small text!" helpText="Read the small text!"
min='33' min="33"
max='54' max="54"
onChange={event =>setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
/> />;
``` ```

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@@ -21,32 +21,37 @@ PasswordInput.propTypes = {
helpText: PropTypes.string, helpText: PropTypes.string,
/** Use show/hide password button. */ /** Use show/hide password button. */
withEye: PropTypes.bool, withEye: PropTypes.bool,
/** Use new-password in autocomplete attribute. */
newPass: PropTypes.bool,
}; };
export function PasswordInput({ withEye, ...props }) { export function PasswordInput({ withEye, newPass, ...props }) {
const [isHidden, setHidden] = useState(true); const [isHidden, setHidden] = useState(true);
return ( return (
<Input <Input
type={withEye && !isHidden ? "text" : "password"} type={withEye && !isHidden ? "text" : "password"}
autoComplete={isHidden ? "new-password" : null} autoComplete={newPass ? "new-password" : "current-password"}
{...props} {...props}
> >
{withEye {withEye ? (
? ( <div className="input-group-append">
<div className="input-group-append"> <button
<button type="button"
type="button" className="input-group-text"
className="input-group-text" onClick={(e) => {
onClick={(e) => { e.preventDefault();
e.preventDefault(); setHidden((shouldBeHidden) => !shouldBeHidden);
setHidden((shouldBeHidden) => !shouldBeHidden); }}
}} >
> <i
<i className={`fa ${isHidden ? "fa-eye" : "fa-eye-slash"}`} /> className={`fa ${
</button> isHidden ? "fa-eye" : "fa-eye-slash"
</div> }`}
) />
: null} </button>
</div>
) : null}
</Input> </Input>
); );
} }

View File

@@ -1,17 +1,18 @@
Password Bootstrap component input with label and predefined sizes and structure for using in foris forms. Password Bootstrap component input with label and predefined sizes and structure
Can be used with "eye" button, see example. for using in foris forms. Can be used with "eye" button, see example.
All additional `props` are passed to the `<input type="password">` HTML component. All additional `props` are passed to the `<input type="password">` HTML
component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const [value, setValue] = useState('secret'); const [value, setValue] = useState("secret");
<PasswordInput <PasswordInput
withEye withEye
value={value} value={value}
label="Some password" label="Some password"
helpText="Read the small text!" helpText="Read the small text!"
onChange={event =>setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
/> />;
``` ```

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@@ -9,29 +9,41 @@ import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useUID } from "react-uid"; import { useUID } from "react-uid";
import { formFieldsSize } from "./constants";
RadioSet.propTypes = { RadioSet.propTypes = {
/** Name attribute of the input HTML tag. */ /** Name attribute of the input HTML tag. */
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
/** RadioSet label . */ /** RadioSet label . */
label: PropTypes.string, label: PropTypes.string,
/** Choices . */ /** Choices . */
choices: PropTypes.arrayOf(PropTypes.shape({ choices: PropTypes.arrayOf(
/** Choice lable . */ PropTypes.shape({
label: PropTypes.string.isRequired, /** Choice lable . */
/** Choice value . */ label: PropTypes.oneOfType([
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, PropTypes.string,
})).isRequired, PropTypes.element,
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]).isRequired,
/** Choice value . */
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
})
).isRequired,
/** Initial value . */ /** Initial value . */
value: PropTypes.string, value: PropTypes.string,
/** Help text message . */ /** Help text message . */
helpText: PropTypes.string, helpText: PropTypes.string,
inline: PropTypes.bool,
}; };
export function RadioSet({ export function RadioSet({
name, label, choices, value, helpText, ...props name,
label,
choices,
value,
helpText,
inline,
...props
}) { }) {
const uid = useUID(); const uid = useUID();
const radios = choices.map((choice, key) => { const radios = choices.map((choice, key) => {
@@ -45,49 +57,62 @@ export function RadioSet({
value={choice.value} value={choice.value}
helpText={choice.helpText} helpText={choice.helpText}
checked={choice.value === value} checked={choice.value === value}
inline={inline}
{...props} {...props}
/> />
); );
}); });
return ( return (
<div className={`form-group ${formFieldsSize}`} style={{ marginBottom: "1rem" }}> <div className="form-group">
{label {label && (
? ( <label htmlFor={uid} className="d-block">
<label className="col-12" htmlFor={uid} style={{ paddingLeft: "0" }}> {label}
{label} </label>
</label> )}
)
: null}
{radios} {radios}
{helpText ? <small className="form-text text-muted">{helpText}</small> : null} {helpText && (
<small className="form-text text-muted">{helpText}</small>
)}
</div> </div>
); );
} }
Radio.propTypes = { Radio.propTypes = {
label: PropTypes.string.isRequired, label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]).isRequired,
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
inline: PropTypes.bool,
helpText: PropTypes.string, helpText: PropTypes.string,
}; };
function Radio({ export function Radio({ label, id, helpText, inline, ...props }) {
label, id, helpText, ...props
}) {
return ( return (
<> <>
<div className="custom-control custom-radio custom-control-inline"> <div
className={`custom-control custom-radio ${
inline ? "custom-control-inline" : ""
}`.trim()}
>
<input <input
id={id} id={id}
className="custom-control-input" className="custom-control-input"
type="radio" type="radio"
{...props} {...props}
/> />
<label className="custom-control-label" htmlFor={id}>{label}</label> <label className="custom-control-label" htmlFor={id}>
{label}
</label>
{helpText && (
<small className="form-text text-muted mt-0 mb-3">
{helpText}
</small>
)}
</div> </div>
{helpText ? <small className="form-text text-muted">{helpText}</small> : null}
</> </>
); );
} }

View File

@@ -1,13 +1,16 @@
Set of radio Bootstrap component input with label and predefined sizes and structure for using in foris forms. Set of radio Bootstrap component input with label and predefined sizes and
structure for using in foris forms.
All additional `props` are passed to the `<input type="number">` HTML component. All additional `props` are passed to the `<input type="number">` HTML component.
Unless `helpText` is set for one of the options they are displayed inline.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const CHOICES=[ const CHOICES = [
{value:'one',label:'1'}, { value: "one", label: "1" },
{value:'two',label:'2'}, { value: "two", label: "2" },
{value:'three',label:'3'}, { value: "three", label: "3" },
]; ];
const [value, setValue] = useState(CHOICES[0].value); const [value, setValue] = useState(CHOICES[0].value);
@@ -15,10 +18,10 @@ const [value, setValue] = useState(CHOICES[0].value);
{/*Yeah, it gets event, not value!*/} {/*Yeah, it gets event, not value!*/}
<RadioSet <RadioSet
value={value} value={value}
name='some-radio' name="some-radio"
choices={CHOICES} choices={CHOICES}
onChange={event =>setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
/> />
<p>Selected value: {value}</p> <p>Selected value: {value}</p>
</> </>;
``` ```

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@@ -9,41 +9,35 @@ import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useUID } from "react-uid"; import { useUID } from "react-uid";
Select.propTypes = { Select.propTypes = {
/** Select field Label. */ /** Select field Label. */
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
/** Choices if form of {value : "Label",...}. */ /** Choices if form of {value : "Label",...}. */
choices: PropTypes.object.isRequired, choices: PropTypes.object.isRequired,
/** Current value. */ /** Current value. */
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
PropTypes.string,
PropTypes.number,
]).isRequired,
/** Help text message. */ /** Help text message. */
helpText: PropTypes.string, helpText: PropTypes.string,
}; };
export function Select({ export function Select({ label, choices, helpText, ...props }) {
label, choices, helpText, ...props
}) {
const uid = useUID(); const uid = useUID();
const options = Object.keys(choices).map( const options = Object.keys(choices).map((choice) => (
(key) => <option key={key} value={key}>{choices[key]}</option>, <option key={choice} value={choice}>
); {choices[choice]}
</option>
));
return ( return (
<div className="form-group col-sm-12 offset-lg-1 col-lg-10"> <div className="form-group">
<label htmlFor={uid}>{label}</label> <label htmlFor={uid}>{label}</label>
<select <select className="custom-select" id={uid} {...props}>
className="custom-select"
id={uid}
{...props}
>
{options} {options}
</select> </select>
{helpText ? <small className="form-text text-muted">{helpText}</small> : null} {helpText ? (
<small className="form-text text-muted">{helpText}</small>
) : null}
</div> </div>
); );
} }

View File

@@ -1,13 +1,14 @@
Select with options Bootstrap component input with label and predefined sizes and structure for using in foris forms. Select with options Bootstrap component input with label and predefined sizes
and structure for using in foris forms.
All additional `props` are passed to the `<select>` HTML component. All additional `props` are passed to the `<select>` HTML component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const CHOICES={ const CHOICES = {
apple:'Apple', apple: "Apple",
banana:'Banana', banana: "Banana",
peach:'Peach', peach: "Peach",
}; };
const [value, setValue] = useState(Object.keys(CHOICES)[0]); const [value, setValue] = useState(Object.keys(CHOICES)[0]);
@@ -17,9 +18,9 @@ const [value, setValue] = useState(Object.keys(CHOICES)[0]);
label="Fruit" label="Fruit"
value={value} value={value}
choices={CHOICES} choices={CHOICES}
onChange={event=>setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
/> />
<p>Selected choice label: {CHOICES[value]}</p> <p>Selected choice label: {CHOICES[value]}</p>
<p>Selected choice value: {value}</p> <p>Selected choice value: {value}</p>
</> </>;
``` ```

33
src/bootstrap/Spinner.css Normal file
View File

@@ -0,0 +1,33 @@
.spinner-wrapper .spinner-border {
width: 4rem;
height: 4rem;
color: #00a2e2;
}
.spinner-fs-background {
background-color: rgba(2, 2, 2, 0.5);
color: rgb(230, 230, 230);
position: fixed;
width: 100%;
height: 100%;
top: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
/*
* Set to high value to me sure that it always overlaps all components
* https://getbootstrap.com/docs/4.3/layout/overview/#z-index
*/
z-index: 1100;
}
.spinner-fs-wrapper .spinner-border {
width: 6rem;
height: 6rem;
}
.spinner-fs-wrapper .spinner-text {
margin: 1rem;
}

View File

@@ -8,6 +8,8 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import "./Spinner.css";
Spinner.propTypes = { Spinner.propTypes = {
/** Children components put into `div` with "spinner-text" class. */ /** Children components put into `div` with "spinner-text" class. */
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
@@ -23,19 +25,19 @@ Spinner.defaultProps = {
fullScreen: false, fullScreen: false,
}; };
export function Spinner({ export function Spinner({ fullScreen, children, className }) {
fullScreen, children, className, ...props
}) {
if (!fullScreen) { if (!fullScreen) {
return ( return (
<div className={`spinner-wrapper ${className || ""}`} {...props}> <div
className={`spinner-wrapper ${className || "my-3 text-center"}`}
>
<SpinnerElement>{children}</SpinnerElement> <SpinnerElement>{children}</SpinnerElement>
</div> </div>
); );
} }
return ( return (
<div className="spinner-fs-wrapper" {...props}> <div className="spinner-fs-wrapper">
<div className="spinner-fs-background"> <div className="spinner-fs-background">
<SpinnerElement>{children}</SpinnerElement> <SpinnerElement>{children}</SpinnerElement>
</div> </div>
@@ -46,6 +48,8 @@ export function Spinner({
SpinnerElement.propTypes = { SpinnerElement.propTypes = {
/** Spinner's size */ /** Spinner's size */
small: PropTypes.bool, small: PropTypes.bool,
/** Additional className */
className: PropTypes.string,
/** Children components */ /** Children components */
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node), PropTypes.arrayOf(PropTypes.node),
@@ -53,13 +57,18 @@ SpinnerElement.propTypes = {
]), ]),
}; };
export function SpinnerElement({ small, children }) { export function SpinnerElement({ small, className, children }) {
return ( return (
<> <>
<div className={`spinner-border ${small ? "spinner-border-sm" : ""}`} role="status"> <div
className={`spinner-border ${
small ? "spinner-border-sm" : ""
} ${className || ""}`.trim()}
role="status"
>
<span className="sr-only" /> <span className="sr-only" />
</div> </div>
<div className="spinner-text">{children}</div> {children && <div className="spinner-text">{children}</div>}
</> </>
); );
} }

49
src/bootstrap/Switch.js Normal file
View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
import { useUID } from "react-uid";
Switch.propTypes = {
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]).isRequired,
helpText: PropTypes.string,
switchHeading: PropTypes.bool,
};
export function Switch({ label, helpText, switchHeading, ...props }) {
const uid = useUID();
return (
<div className={`form-group ${switchHeading ? "switch" : ""}`.trim()}>
<div
className={`custom-control custom-switch ${
!helpText ? "custom-control-inline" : ""
} ${switchHeading ? "switch-heading" : ""}`.trim()}
>
<input
type="checkbox"
className="custom-control-input"
id={uid}
{...props}
/>
<label className="custom-control-label" htmlFor={uid}>
{label}
</label>
{helpText && (
<small className="form-text text-muted mt-0 mb-3">
{helpText}
</small>
)}
</div>
</div>
);
}

5
src/bootstrap/Switch.md Normal file
View File

@@ -0,0 +1,5 @@
Switch example:
```js
<Switch label="Enable Switch" helpText="Toggle that switch!" />
```

View File

@@ -10,10 +10,8 @@ import PropTypes from "prop-types";
import { Input } from "./Input"; import { Input } from "./Input";
export const TextInput = ({ ...props }) => <Input type="text" {...props} />; export const TextInput = ({ ...props }) => <Input type="text" {...props} />;
TextInput.propTypes = { TextInput.propTypes = {
/** Field label. */ /** Field label. */
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,

View File

@@ -1,15 +1,16 @@
Text Bootstrap component input with label and predefined sizes and structure for using in foris forms. Text Bootstrap component input with label and predefined sizes and structure for
using in foris forms.
All additional `props` are passed to the `<input type="text">` HTML component. All additional `props` are passed to the `<input type="text">` HTML component.
```js ```js
import {useState} from 'react'; import { useState } from "react";
const [value, setValue] = useState('Bla bla'); const [value, setValue] = useState("Bla bla");
<TextInput <TextInput
value={value} value={value}
label="Some text" label="Some text"
helpText="Read the small text!" helpText="Read the small text!"
onChange={event =>setValue(event.target.value)} onChange={(event) => setValue(event.target.value)}
/> />;
``` ```

View File

@@ -14,19 +14,18 @@ import { Button } from "../Button";
describe("<Button />", () => { describe("<Button />", () => {
it("Render button correctly", () => { it("Render button correctly", () => {
const { container } = render(<Button>Test Button</Button>); const { container } = render(<Button>Test Button</Button>);
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
it("Render button with custom classes", () => { it("Render button with custom classes", () => {
const { container } = render(<Button className="one two three">Test Button</Button>); const { container } = render(
expect(container.firstChild) <Button className="one two three">Test Button</Button>
.toMatchSnapshot(); );
expect(container.firstChild).toMatchSnapshot();
}); });
it("Render button with spinner", () => { it("Render button with spinner", () => {
const { container } = render(<Button loading={true}>Test Button</Button>); const { container } = render(<Button loading>Test Button</Button>);
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
}); });

View File

@@ -18,22 +18,16 @@ describe("<Checkbox/>", () => {
label="Test label" label="Test label"
checked checked
helpText="Some help text" helpText="Some help text"
onChange={() => { onChange={() => {}}
}}
/> />
); );
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
it("Render uncheked checkbox", () => { it("Render uncheked checkbox", () => {
const { container } = render( const { container } = render(
<CheckBox <CheckBox label="Test label" helpText="Some help text" />
label="Test label"
helpText="Some help text"
/>
); );
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
}); });

View File

@@ -13,7 +13,11 @@ import { DownloadButton } from "../DownloadButton";
describe("<DownloadButton />", () => { describe("<DownloadButton />", () => {
it("should have download attribute", () => { it("should have download attribute", () => {
const { container } = render(<DownloadButton href="http://example.com">Test Button</DownloadButton>); const { container } = render(
<DownloadButton href="http://example.com">
Test Button
</DownloadButton>
);
expect(container.firstChild.getAttribute("download")).not.toBeNull(); expect(container.firstChild.getAttribute("download")).not.toBeNull();
}); });
}); });

View File

@@ -34,12 +34,16 @@ describe("<NumberInput/>", () => {
it("Increase number with button", async () => { it("Increase number with button", async () => {
const increaseButton = getByLabelText(componentContainer, "Increase"); const increaseButton = getByLabelText(componentContainer, "Increase");
fireEvent.mouseDown(increaseButton); fireEvent.mouseDown(increaseButton);
await wait(() => expect(onChangeMock).toHaveBeenCalledWith({"target": {"value": 2}})); await wait(() =>
expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 2 } })
);
}); });
it("Decrease number with button", async () => { it("Decrease number with button", async () => {
const decreaseButton = getByLabelText(componentContainer, "Decrease"); const decreaseButton = getByLabelText(componentContainer, "Decrease");
fireEvent.mouseDown(decreaseButton); fireEvent.mouseDown(decreaseButton);
await wait(() => expect(onChangeMock).toHaveBeenCalledWith({"target": {"value": 0}})); await wait(() =>
expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 0 } })
);
}); });
}); });

View File

@@ -18,11 +18,9 @@ describe("<PasswordInput/>", () => {
label="Test label" label="Test label"
helpText="Some help text" helpText="Some help text"
value="Some password" value="Some password"
onChange={() => { onChange={() => {}}
}}
/> />
); );
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
}); });

View File

@@ -14,32 +14,30 @@ import { RadioSet } from "../RadioSet";
const TEST_CHOICES = [ const TEST_CHOICES = [
{ {
label: "label", label: "label",
value: "value" value: "value",
}, },
{ {
label: "another label", label: "another label",
value: "another value" value: "another value",
}, },
{ {
label: "another one label", label: "another one label",
value: "another on value" value: "another on value",
} },
]; ];
describe("<RadioSet/>", () => { describe("<RadioSet/>", () => {
it("Render radio set", () => { it("Render radio set", () => {
const { container } = render( const { container } = render(
<RadioSet <RadioSet
name={"test_name"} name="test_name"
label='Radios set label' label="Radios set label"
value='value' value="value"
choices={TEST_CHOICES} choices={TEST_CHOICES}
helpText={"Some help text"} helpText="Some help text"
onChange={() => { onChange={() => {}}
}}
/> />
); );
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
}); });

View File

@@ -7,27 +7,31 @@
import React from "react"; import React from "react";
import { fireEvent, getByDisplayValue, getByText, render } from "customTestRender"; import {
fireEvent,
getByDisplayValue,
getByText,
render,
} from "customTestRender";
import { Select } from "../Select"; import { Select } from "../Select";
const TEST_CHOICES = { const TEST_CHOICES = {
"1": "one", 1: "one",
"2": "two", 2: "two",
"3": "three", 3: "three",
}; };
describe("<Select/>", () => { describe("<Select/>", () => {
var selectContainer; let selectContainer;
const onChangeHandler = jest.fn(); const onChangeHandler = jest.fn();
beforeEach(() => { beforeEach(() => {
const { container } = render( const { container } = render(
<Select <Select
label='Test label' label="Test label"
value='1' value="1"
choices={TEST_CHOICES} choices={TEST_CHOICES}
helpText='Help text' helpText="Help text"
onChange={onChangeHandler} onChange={onChangeHandler}
/> />
); );
@@ -35,21 +39,17 @@ describe("<Select/>", () => {
}); });
it("Test with snapshot.", () => { it("Test with snapshot.", () => {
expect(selectContainer) expect(selectContainer).toMatchSnapshot();
.toMatchSnapshot();
}); });
it("Test onChange handling.", () => { it("Test onChange handling.", () => {
const select = getByDisplayValue(selectContainer, "one"); const select = getByDisplayValue(selectContainer, "one");
expect(select.value) expect(select.value).toBe("1");
.toBe("1");
fireEvent.change(select, { target: { value: "2" } }); fireEvent.change(select, { target: { value: "2" } });
const option = getByText(selectContainer, "two"); const option = getByText(selectContainer, "two");
expect(onChangeHandler) expect(onChangeHandler).toBeCalled();
.toBeCalled();
expect(option.value) expect(option.value).toBe("2");
.toBe("2");
}); });
}); });

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import { render } from "customTestRender";
import { Switch } from "../Switch";
describe("<Switch/>", () => {
it("Render switch", () => {
const { container } = render(
<Switch
label="Test label"
checked
helpText="Some help text"
onChange={() => {}}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
it("Render uncheked switch", () => {
const { container } = render(
<Switch label="Test label" helpText="Some help text" />
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@@ -18,11 +18,9 @@ describe("<TextInput/>", () => {
label="Test label" label="Test label"
helpText="Some help text" helpText="Some help text"
value="Some text" value="Some text"
onChange={() => { onChange={() => {}}
}}
/> />
); );
expect(container.firstChild) expect(container.firstChild).toMatchSnapshot();
.toMatchSnapshot();
}); });
}); });

View File

@@ -5,8 +5,6 @@ exports[`<Button /> Render button correctly 1`] = `
class="btn btn-primary " class="btn btn-primary "
type="button" type="button"
> >
Test Button Test Button
</button> </button>
`; `;
@@ -16,8 +14,6 @@ exports[`<Button /> Render button with custom classes 1`] = `
class="btn one two three" class="btn one two three"
type="button" type="button"
> >
Test Button Test Button
</button> </button>
`; `;
@@ -33,8 +29,6 @@ exports[`<Button /> Render button with spinner 1`] = `
role="status" role="status"
/> />
Test Button Test Button
</button> </button>
`; `;

View File

@@ -2,12 +2,10 @@
exports[`<Checkbox/> Render checkbox 1`] = ` exports[`<Checkbox/> Render checkbox 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group"
style="margin-bottom: 1rem;"
> >
<div <div
class="custom-control custom-checkbox" class="custom-control custom-checkbox "
style="margin-bottom: 0px;"
> >
<input <input
checked="" checked=""
@@ -18,27 +16,24 @@ exports[`<Checkbox/> Render checkbox 1`] = `
<label <label
class="custom-control-label" class="custom-control-label"
for="1" for="1"
style="margin-bottom: 0px;"
> >
Test label Test label
<small
class="form-text text-muted"
>
Some help text
</small>
</label> </label>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;
exports[`<Checkbox/> Render uncheked checkbox 1`] = ` exports[`<Checkbox/> Render uncheked checkbox 1`] = `
<div <div
class="col-sm-12 offset-lg-1 col-lg-10" class="form-group"
style="margin-bottom: 1rem;"
> >
<div <div
class="custom-control custom-checkbox" class="custom-control custom-checkbox "
style="margin-bottom: 0px;"
> >
<input <input
class="custom-control-input" class="custom-control-input"
@@ -48,15 +43,14 @@ exports[`<Checkbox/> Render uncheked checkbox 1`] = `
<label <label
class="custom-control-label" class="custom-control-label"
for="1" for="1"
style="margin-bottom: 0px;"
> >
Test label Test label
<small
class="form-text text-muted"
>
Some help text
</small>
</label> </label>
</div> </div>
<small
class="form-text text-muted"
>
Some help text
</small>
</div> </div>
`; `;

View File

@@ -2,7 +2,7 @@
exports[`<NumberInput/> Render number input 1`] = ` exports[`<NumberInput/> Render number input 1`] = `
<div <div
class="form-group col-sm-12 offset-lg-1 col-lg-10" class="form-group"
> >
<label <label
for="1" for="1"

View File

@@ -2,7 +2,7 @@
exports[`<PasswordInput/> Render password input 1`] = ` exports[`<PasswordInput/> Render password input 1`] = `
<div <div
class="form-group col-sm-12 offset-lg-1 col-lg-10" class="form-group"
> >
<label <label
for="1" for="1"
@@ -13,7 +13,7 @@ exports[`<PasswordInput/> Render password input 1`] = `
class="input-group" class="input-group"
> >
<input <input
autocomplete="new-password" autocomplete="current-password"
class="form-control" class="form-control"
id="1" id="1"
type="password" type="password"

View File

@@ -2,18 +2,16 @@
exports[`<RadioSet/> Render radio set 1`] = ` exports[`<RadioSet/> Render radio set 1`] = `
<div <div
class="form-group col-sm-12 offset-lg-1 col-lg-10" class="form-group"
style="margin-bottom: 1rem;"
> >
<label <label
class="col-12" class="d-block"
for="1" for="1"
style="padding-left: 0px;"
> >
Radios set label Radios set label
</label> </label>
<div <div
class="custom-control custom-radio custom-control-inline" class="custom-control custom-radio"
> >
<input <input
checked="" checked=""
@@ -31,7 +29,7 @@ exports[`<RadioSet/> Render radio set 1`] = `
</label> </label>
</div> </div>
<div <div
class="custom-control custom-radio custom-control-inline" class="custom-control custom-radio"
> >
<input <input
class="custom-control-input" class="custom-control-input"
@@ -48,7 +46,7 @@ exports[`<RadioSet/> Render radio set 1`] = `
</label> </label>
</div> </div>
<div <div
class="custom-control custom-radio custom-control-inline" class="custom-control custom-radio"
> >
<input <input
class="custom-control-input" class="custom-control-input"

View File

@@ -3,7 +3,7 @@
exports[`<Select/> Test with snapshot. 1`] = ` exports[`<Select/> Test with snapshot. 1`] = `
<div> <div>
<div <div
class="form-group col-sm-12 offset-lg-1 col-lg-10" class="form-group"
> >
<label <label
for="1" for="1"

View File

@@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Switch/> Render switch 1`] = `
<div
class="form-group"
>
<div
class="custom-control custom-switch"
>
<input
checked=""
class="custom-control-input"
id="1"
type="checkbox"
/>
<label
class="custom-control-label"
for="1"
>
Test label
</label>
<small
class="form-text text-muted mt-0 mb-3"
>
Some help text
</small>
</div>
</div>
`;
exports[`<Switch/> Render uncheked switch 1`] = `
<div
class="form-group"
>
<div
class="custom-control custom-switch"
>
<input
class="custom-control-input"
id="1"
type="checkbox"
/>
<label
class="custom-control-label"
for="1"
>
Test label
</label>
<small
class="form-text text-muted mt-0 mb-3"
>
Some help text
</small>
</div>
</div>
`;

View File

@@ -2,7 +2,7 @@
exports[`<TextInput/> Render text input 1`] = ` exports[`<TextInput/> Render text input 1`] = `
<div <div
class="form-group col-sm-12 offset-lg-1 col-lg-10" class="form-group"
> >
<label <label
for="1" for="1"

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@@ -7,4 +7,5 @@
/** Bootstrap column size for form fields */ /** Bootstrap column size for form fields */
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export const formFieldsSize = "col-sm-12 offset-lg-1 col-lg-10"; export const formFieldsSize = "card p-4 col-sm-12 col-lg-12 p-0 mb-4";
export const buttonFormFieldsSize = "col-sm-12 col-lg-12 p-0 mb-3";

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { useAPIPost } from "../api/hooks";
import { API_STATE } from "../api/utils";
import { ForisURLs } from "../utils/forisUrls";
import { Button } from "../bootstrap/Button";
import { Modal, ModalHeader, ModalBody, ModalFooter } from "../bootstrap/Modal";
import { useAlert } from "../alertContext/AlertContext";
export function RebootButton(props) {
const [triggered, setTriggered] = useState(false);
const [modalShown, setModalShown] = useState(false);
const [triggerRebootStatus, triggerReboot] = useAPIPost(ForisURLs.reboot);
const [setAlert] = useAlert();
useEffect(() => {
if (triggerRebootStatus.state === API_STATE.ERROR) {
setAlert(_("Reboot request failed."));
}
});
function rebootHandler() {
setTriggered(true);
triggerReboot();
setModalShown(false);
}
return (
<>
<RebootModal
shown={modalShown}
setShown={setModalShown}
onReboot={rebootHandler}
/>
<Button
className="btn-danger"
loading={triggered}
disabled={triggered}
onClick={() => setModalShown(true)}
{...props}
>
{_("Reboot")}
</Button>
</>
);
}
RebootModal.propTypes = {
shown: PropTypes.bool.isRequired,
setShown: PropTypes.func.isRequired,
onReboot: PropTypes.func.isRequired,
};
function RebootModal({ shown, setShown, onReboot }) {
return (
<Modal shown={shown} setShown={setShown}>
<ModalHeader setShown={setShown} title={_("Warning!")} />
<ModalBody>
<p>{_("Are you sure you want to restart the router?")}</p>
</ModalBody>
<ModalFooter>
<Button onClick={() => setShown(false)}>{_("Cancel")}</Button>
<Button className="btn-danger" onClick={onReboot}>
{_("Confirm reboot")}
</Button>
</ModalFooter>
</Modal>
);
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { Button } from "../../bootstrap/Button";
import { useAlert } from "../../alertContext/AlertContext";
import { ALERT_TYPES } from "../../bootstrap/Alert";
import { useAPIPost } from "../../api/hooks";
import { API_STATE } from "../../api/utils";
import { formFieldsSize } from "../../bootstrap/constants";
ResetWiFiSettings.propTypes = {
ws: PropTypes.object.isRequired,
endpoint: PropTypes.string.isRequired,
};
export function ResetWiFiSettings({ ws, endpoint }) {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const module = "wifi";
ws.subscribe(module).bind(module, "reset", () => {
// eslint-disable-next-line no-restricted-globals
setTimeout(() => location.reload(), 1000);
});
}, [ws]);
const [postResetResponse, postReset] = useAPIPost(endpoint);
const [setAlert, dismissAlert] = useAlert();
useEffect(() => {
if (postResetResponse.state === API_STATE.ERROR) {
setAlert(_("An error occurred during resetting Wi-Fi settings."));
} else if (postResetResponse.state === API_STATE.SUCCESS) {
setAlert(
_("Wi-Fi settings are set to defaults."),
ALERT_TYPES.SUCCESS
);
}
}, [postResetResponse, setAlert]);
function onReset() {
dismissAlert();
setIsLoading(true);
postReset();
}
return (
<div className={formFieldsSize}>
<h2>{_("Reset Wi-Fi Settings")}</h2>
<p>
{_(
"If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the current Wi-Fi configuration and restore the default values."
)}
</p>
<div className="text-right">
<Button
className="btn-primary"
forisFormSize
loading={isLoading}
disabled={isLoading}
onClick={onReset}
>
{_("Reset Wi-Fi Settings")}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,287 @@
/*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
import { Switch } from "../../bootstrap/Switch";
import { CheckBox } from "../../bootstrap/CheckBox";
import { PasswordInput } from "../../bootstrap/PasswordInput";
import { RadioSet } from "../../bootstrap/RadioSet";
import { Select } from "../../bootstrap/Select";
import { TextInput } from "../../bootstrap/TextInput";
import WiFiQRCode from "./WiFiQRCode";
import WifiGuestForm from "./WiFiGuestForm";
import { HELP_TEXTS, HTMODES, HWMODES, ENCRYPTIONMODES } from "./constants";
WiFiForm.propTypes = {
formData: PropTypes.shape({ devices: PropTypes.arrayOf(PropTypes.object) })
.isRequired,
formErrors: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
setFormValue: PropTypes.func.isRequired,
hasGuestNetwork: PropTypes.bool,
};
WiFiForm.defaultProps = {
formData: { devices: [] },
setFormValue: () => {},
hasGuestNetwork: true,
};
export default function WiFiForm({
formData,
formErrors,
setFormValue,
hasGuestNetwork,
disabled,
}) {
return formData.devices.map((device, index) => (
<DeviceForm
key={device.id}
formData={device}
deviceIndex={index}
formErrors={(formErrors || [])[index]}
setFormValue={setFormValue}
hasGuestNetwork={hasGuestNetwork}
disabled={disabled}
divider={index + 1 !== formData.devices.length}
/>
));
}
DeviceForm.propTypes = {
formData: PropTypes.shape({
id: PropTypes.number.isRequired,
enabled: PropTypes.bool.isRequired,
SSID: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
hidden: PropTypes.bool.isRequired,
hwmode: PropTypes.string.isRequired,
htmode: PropTypes.string.isRequired,
channel: PropTypes.string.isRequired,
guest_wifi: PropTypes.object.isRequired,
encryption: PropTypes.string.isRequired,
available_bands: PropTypes.array.isRequired,
}),
formErrors: PropTypes.object.isRequired,
setFormValue: PropTypes.func.isRequired,
hasGuestNetwork: PropTypes.bool,
deviceIndex: PropTypes.number,
divider: PropTypes.bool,
};
DeviceForm.defaultProps = {
formErrors: {},
hasGuestNetwork: true,
};
function DeviceForm({
formData,
formErrors,
setFormValue,
hasGuestNetwork,
deviceIndex,
divider,
...props
}) {
const deviceID = formData.id;
const bnds = formData.available_bands;
return (
<>
<Switch
label={<h2>{_(`Wi-Fi ${deviceID + 1}`)}</h2>}
checked={formData.enabled}
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: { enabled: { $set: value } },
},
}))}
switchHeading
{...props}
/>
{formData.enabled ? (
<>
<TextInput
label="SSID"
value={formData.SSID}
error={formErrors.SSID || null}
helpText={HELP_TEXTS.ssid}
required
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: {
SSID: { $set: value },
},
},
}))}
{...props}
>
<div className="input-group-append">
<WiFiQRCode
SSID={formData.SSID}
password={formData.password}
/>
</div>
</TextInput>
<PasswordInput
withEye
label={_("Password")}
value={formData.password}
error={formErrors.password}
helpText={HELP_TEXTS.password}
required
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: { password: { $set: value } },
},
}))}
{...props}
/>
<CheckBox
label={_("Hide SSID")}
helpText={HELP_TEXTS.hidden}
checked={formData.hidden}
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: { hidden: { $set: value } },
},
}))}
{...props}
/>
<RadioSet
name={`hwmode-${deviceID}`}
label="GHz"
choices={getHwmodeChoices(formData)}
value={formData.hwmode}
helpText={HELP_TEXTS.hwmode}
inline
onChange={setFormValue((value) => {
// Get the last item in an array of available HT modes
const [best2] = bnds[0].available_htmodes.slice(-1);
const [best5] = bnds[1].available_htmodes.slice(-1);
return {
devices: {
[deviceIndex]: {
hwmode: { $set: value },
channel: { $set: "0" },
htmode: {
$set:
// Set HT mode depending on checked frequency
value === "11a" ? best5 : best2,
},
},
},
};
})}
{...props}
/>
<Select
label={_("802.11n/ac/ax mode")}
choices={getHtmodeChoices(formData)}
value={formData.htmode}
helpText={HELP_TEXTS.htmode}
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: { htmode: { $set: value } },
},
}))}
{...props}
/>
<Select
label={_("Channel")}
choices={getChannelChoices(formData)}
value={formData.channel}
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: { channel: { $set: value } },
},
}))}
{...props}
/>
<Select
label={_("Encryption")}
choices={getEncryptionChoices(formData)}
helpText={HELP_TEXTS.wpa3}
value={formData.encryption}
onChange={setFormValue((value) => ({
devices: {
[deviceIndex]: { encryption: { $set: value } },
},
}))}
{...props}
/>
{hasGuestNetwork && (
<WifiGuestForm
formData={{
id: deviceIndex,
...formData.guest_wifi,
}}
formErrors={formErrors.guest_wifi || {}}
setFormValue={setFormValue}
{...props}
/>
)}
</>
) : null}
{divider ? <hr /> : null}
</>
);
}
function getChannelChoices(device) {
const channelChoices = {
0: _("auto"),
};
device.available_bands.forEach((availableBand) => {
if (availableBand.hwmode !== device.hwmode) return;
availableBand.available_channels.forEach((availableChannel) => {
channelChoices[availableChannel.number.toString()] = `
${availableChannel.number}
(${availableChannel.frequency} MHz ${
availableChannel.radar ? " ,DFS" : ""
})
`;
});
});
return channelChoices;
}
function getHtmodeChoices(device) {
const htmodeChoices = {};
device.available_bands.forEach((availableBand) => {
if (availableBand.hwmode !== device.hwmode) return;
availableBand.available_htmodes.forEach((availableHtmod) => {
htmodeChoices[availableHtmod] = HTMODES[availableHtmod];
});
});
return htmodeChoices;
}
function getHwmodeChoices(device) {
return device.available_bands.map((availableBand) => ({
label: HWMODES[availableBand.hwmode],
value: availableBand.hwmode,
}));
}
function getEncryptionChoices(device) {
if (device.encryption === "custom") {
ENCRYPTIONMODES.custom = _("Custom");
}
return ENCRYPTIONMODES;
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
import { TextInput } from "../../bootstrap/TextInput";
import { Switch } from "../../bootstrap/Switch";
import { PasswordInput } from "../../bootstrap/PasswordInput";
import WiFiQRCode from "./WiFiQRCode";
import { HELP_TEXTS } from "./constants";
WifiGuestForm.propTypes = {
formData: PropTypes.shape({
id: PropTypes.number.isRequired,
SSID: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
enabled: PropTypes.bool.isRequired,
}),
formErrors: PropTypes.shape({
SSID: PropTypes.string,
password: PropTypes.string,
}),
setFormValue: PropTypes.func.isRequired,
deviceIndex: PropTypes.string,
};
export default function WifiGuestForm({
formData,
formErrors,
setFormValue,
deviceIndex,
...props
}) {
return (
<>
<Switch
label={_("Enable Guest Wi-Fi")}
checked={formData.enabled}
helpText={HELP_TEXTS.guest_wifi_enabled}
onChange={setFormValue((value) => ({
devices: {
[formData.id]: {
guest_wifi: { enabled: { $set: value } },
},
},
}))}
{...props}
/>
{formData.enabled ? (
<>
<TextInput
label="SSID"
value={formData.SSID}
error={formErrors.SSID}
helpText={HELP_TEXTS.ssid}
onChange={setFormValue((value) => ({
devices: {
[formData.id]: {
guest_wifi: { SSID: { $set: value } },
},
},
}))}
{...props}
>
<div className="input-group-append">
<WiFiQRCode
SSID={formData.SSID}
password={formData.password}
/>
</div>
</TextInput>
<PasswordInput
withEye
label={_("Password")}
value={formData.password}
helpText={HELP_TEXTS.password}
error={formErrors.password}
required
onChange={setFormValue((value) => ({
devices: {
[formData.id]: {
guest_wifi: { password: { $set: value } },
},
},
}))}
{...props}
/>
</>
) : null}
</>
);
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React, { useState } from "react";
import QRCode from "qrcode.react";
import PropTypes from "prop-types";
import { ForisURLs } from "../../utils/forisUrls";
import { Button } from "../../bootstrap/Button";
import {
Modal,
ModalBody,
ModalFooter,
ModalHeader,
} from "../../bootstrap/Modal";
import { createAndDownloadPdf, toQRCodeContent } from "./qrCodeHelpers";
WiFiQRCode.propTypes = {
SSID: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
};
const QR_ICON_PATH = `${ForisURLs.static}/imgs/QR_icon.svg`;
export default function WiFiQRCode({ SSID, password }) {
const [modal, setModal] = useState(false);
return (
<>
<button
type="button"
className="input-group-text"
onClick={(e) => {
e.preventDefault();
setModal(true);
}}
>
<img
width="20"
src={QR_ICON_PATH}
alt="QR"
style={{ opacity: 0.67 }}
/>
</button>
{modal ? (
<QRCodeModal
setShown={setModal}
shown={modal}
SSID={SSID}
password={password}
/>
) : null}
</>
);
}
QRCodeModal.propTypes = {
SSID: PropTypes.string.isRequired,
password: PropTypes.string.isRequired,
shown: PropTypes.bool.isRequired,
setShown: PropTypes.func.isRequired,
};
function QRCodeModal({ shown, setShown, SSID, password }) {
return (
<Modal setShown={setShown} shown={shown}>
<ModalHeader setShown={setShown} title={_("Wi-Fi QR Code")} />
<ModalBody>
<QRCode
renderAs="svg"
value={toQRCodeContent(SSID, password)}
level="M"
size={350}
includeMargin
style={{ display: "block", margin: "auto" }}
/>
</ModalBody>
<ModalFooter>
<Button
className="btn-outline-primary"
onClick={(e) => {
e.preventDefault();
createAndDownloadPdf(SSID, password);
}}
>
<i className="fas fa-arrow-down mr-2" />
{_("Download PDF")}
</Button>
</ModalFooter>
</Modal>
);
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import PropTypes from "prop-types";
import { ForisForm } from "../../form/components/ForisForm";
import WiFiForm from "./WiFiForm";
import { ResetWiFiSettings } from "./ResetWiFiSettings";
WiFiSettings.propTypes = {
ws: PropTypes.object.isRequired,
endpoint: PropTypes.string.isRequired,
resetEndpoint: PropTypes.string.isRequired,
hasGuestNetwork: PropTypes.bool,
};
export function WiFiSettings({ ws, endpoint, resetEndpoint, hasGuestNetwork }) {
return (
<>
<ForisForm
ws={ws}
forisConfig={{
endpoint,
wsModule: "wifi",
}}
prepData={prepData}
prepDataToSubmit={prepDataToSubmit}
validator={validator}
>
<WiFiForm hasGuestNetwork={hasGuestNetwork} />
</ForisForm>
<ResetWiFiSettings ws={ws} endpoint={resetEndpoint} />
</>
);
}
function prepData(formData) {
formData.devices.forEach((device, idx) => {
formData.devices[idx].channel = device.channel.toString();
});
return formData;
}
function prepDataToSubmit(formData) {
formData.devices.forEach((device, idx) => {
delete device.available_bands;
formData.devices[idx].channel = parseInt(device.channel);
if (!device.enabled) {
formData.devices[idx] = { id: device.id, enabled: false };
return;
}
if (!device.guest_wifi.enabled)
formData.devices[idx].guest_wifi = { enabled: false };
});
return formData;
}
export function byteCount(string) {
const buffer = Buffer.from(string, "utf-8");
const count = buffer.byteLength;
return count;
}
export function validator(formData) {
const formErrors = formData.devices.map((device) => {
if (!device.enabled) return {};
const errors = {};
if (device.SSID.length > 32)
errors.SSID = _("SSID can't be longer than 32 symbols");
if (device.SSID.length === 0) errors.SSID = _("SSID can't be empty");
if (byteCount(device.SSID) > 32)
errors.SSID = _("SSID can't be longer than 32 bytes");
if (device.password.length < 8)
errors.password = _("Password must contain at least 8 symbols");
if (device.password.length >= 64)
errors.password = _(
"Password must not contain more than 63 symbols"
);
if (!device.guest_wifi.enabled) return errors;
const guest_wifi_errors = {};
if (device.guest_wifi.SSID.length > 32)
guest_wifi_errors.SSID = _("SSID can't be longer than 32 symbols");
if (device.guest_wifi.SSID.length === 0)
guest_wifi_errors.SSID = _("SSID can't be empty");
if (byteCount(device.guest_wifi.SSID) > 32)
guest_wifi_errors.SSID = _("SSID can't be longer than 32 bytes");
if (device.guest_wifi.password.length < 8)
guest_wifi_errors.password = _(
"Password must contain at least 8 symbols"
);
if (device.guest_wifi.password.length >= 64)
guest_wifi_errors.password = _(
"Password must not contain more than 63 symbols"
);
if (guest_wifi_errors.SSID || guest_wifi_errors.password) {
errors.guest_wifi = guest_wifi_errors;
}
return errors;
});
return JSON.stringify(formErrors).match(/\[[{},?]+\]/) ? null : formErrors;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import { render, fireEvent, wait } from "customTestRender";
import mockAxios from "jest-mock-axios";
import { WebSockets } from "webSockets/WebSockets";
import { mockJSONError } from "testUtils/network";
import { mockSetAlert } from "testUtils/alertContextMock";
import { ALERT_TYPES } from "../../../bootstrap/Alert";
import { ResetWiFiSettings } from "../ResetWiFiSettings";
describe("<ResetWiFiSettings/>", () => {
const webSockets = new WebSockets();
const endpoint = "/reforis/api/wifi-reset";
let getAllByText;
beforeEach(() => {
({ getAllByText } = render(
<ResetWiFiSettings ws={webSockets} endpoint={endpoint} />
));
});
it("should display alert on open ports - success", async () => {
fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
expect(mockAxios.post).toBeCalledWith(
endpoint,
undefined,
expect.anything()
);
mockAxios.mockResponse({ data: { foo: "bar" } });
await wait(() =>
expect(mockSetAlert).toBeCalledWith(
"Wi-Fi settings are set to defaults.",
ALERT_TYPES.SUCCESS
)
);
});
it("should display alert on open ports - failure", async () => {
fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
mockJSONError();
await wait(() =>
expect(mockSetAlert).toBeCalledWith(
"An error occurred during resetting Wi-Fi settings."
)
);
});
});

View File

@@ -0,0 +1,244 @@
/*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import diffSnapshot from "snapshot-diff";
import mockAxios from "jest-mock-axios";
import { fireEvent, render, wait } from "customTestRender";
import { WebSockets } from "webSockets/WebSockets";
import { mockJSONError } from "testUtils/network";
import {
wifiSettingsFixture,
oneDevice,
twoDevices,
threeDevices,
} from "./__fixtures__/wifiSettings";
import { WiFiSettings, validator, byteCount } from "../WiFiSettings";
describe("<WiFiSettings/>", () => {
let firstRender;
let getAllByText;
let getAllByLabelText;
let getByText;
let getByLabelText;
let asFragment;
const endpoint = "/reforis/api/wifi";
beforeEach(async () => {
const webSockets = new WebSockets();
const renderRes = render(
<WiFiSettings
ws={webSockets}
endpoint={endpoint}
resetEndpoint="foo"
/>
);
asFragment = renderRes.asFragment;
getAllByText = renderRes.getAllByText;
getAllByLabelText = renderRes.getAllByLabelText;
getByLabelText = renderRes.getByLabelText;
getByText = renderRes.getByText;
mockAxios.mockResponse({ data: wifiSettingsFixture() });
await wait(() => renderRes.getByText("Wi-Fi 1"));
firstRender = renderRes.asFragment();
});
it("should handle error", async () => {
const webSockets = new WebSockets();
const { getByText } = render(
<WiFiSettings
ws={webSockets}
endpoint={endpoint}
resetEndpoint="foo"
/>
);
const errorMessage = "An API error occurred.";
mockJSONError(errorMessage);
await wait(() => {
expect(getByText(errorMessage)).toBeTruthy();
});
});
it("Snapshot both modules disabled.", () => {
expect(firstRender).toMatchSnapshot();
});
it("Snapshot one module enabled.", () => {
fireEvent.click(getByText("Wi-Fi 1"));
expect(diffSnapshot(firstRender, asFragment())).toMatchSnapshot();
});
it("Snapshot 2.4 GHz", () => {
fireEvent.click(getByText("Wi-Fi 1"));
const enabledRender = asFragment();
fireEvent.click(getAllByText("2.4")[0]);
expect(diffSnapshot(enabledRender, asFragment())).toMatchSnapshot();
});
it("Snapshot guest network.", () => {
fireEvent.click(getByText("Wi-Fi 1"));
const enabledRender = asFragment();
fireEvent.click(getAllByText("Enable Guest Wi-Fi")[0]);
expect(diffSnapshot(enabledRender, asFragment())).toMatchSnapshot();
});
it("Post form: both modules disabled.", () => {
fireEvent.click(getByText("Save"));
expect(mockAxios.post).toBeCalled();
const data = {
devices: [
{ enabled: false, id: 0 },
{ enabled: false, id: 1 },
],
};
expect(mockAxios.post).toHaveBeenCalledWith(
endpoint,
data,
expect.anything()
);
});
it("Post form: one module enabled.", () => {
fireEvent.click(getByText("Wi-Fi 1"));
fireEvent.click(getByText("Save"));
expect(mockAxios.post).toBeCalled();
const data = {
devices: [
{
SSID: "TestSSID1",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT80",
hwmode: "11a",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
{ enabled: false, id: 1 },
],
};
expect(mockAxios.post).toHaveBeenCalledWith(
endpoint,
data,
expect.anything()
);
});
it("Post form: 2.4 GHz", () => {
fireEvent.click(getByText("Wi-Fi 1"));
fireEvent.click(getAllByText("2.4")[0]);
fireEvent.click(getByText("Save"));
expect(mockAxios.post).toBeCalled();
const data = {
devices: [
{
SSID: "TestSSID1",
channel: 0,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "VHT80",
hwmode: "11g",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
{ enabled: false, id: 1 },
],
};
expect(mockAxios.post).toHaveBeenCalledWith(
endpoint,
data,
expect.anything()
);
});
it("Post form: guest network.", () => {
fireEvent.click(getByText("Wi-Fi 1"));
fireEvent.click(getAllByText("Enable Guest Wi-Fi")[0]);
fireEvent.change(getAllByLabelText("Password")[1], {
target: { value: "test_password" },
});
fireEvent.click(getByText("Save"));
expect(mockAxios.post).toBeCalled();
const data = {
devices: [
{
SSID: "TestSSID1",
channel: 60,
enabled: true,
guest_wifi: {
SSID: "TestGuestSSID",
enabled: true,
password: "test_password",
},
hidden: false,
htmode: "HT80",
hwmode: "11a",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
{ enabled: false, id: 1 },
],
};
expect(mockAxios.post).toHaveBeenCalledWith(
endpoint,
data,
expect.anything()
);
});
it("Validator function using regex for one device", () => {
expect(validator(oneDevice)).toEqual(null);
});
it("Validator function using regex for two devices", () => {
const twoDevicesFormErrors = [{ SSID: "SSID can't be empty" }, {}];
expect(validator(twoDevices)).toEqual(twoDevicesFormErrors);
});
it("Validator function using regex for three devices", () => {
const threeDevicesFormErrors = [
{},
{},
{ password: "Password must contain at least 8 symbols" },
];
expect(validator(threeDevices)).toEqual(threeDevicesFormErrors);
});
it("ByteCount function", () => {
expect(byteCount("abc")).toEqual(3);
});
it("Should validate password length", () => {
const shortErrorFeedback = /Password must contain/i;
const longErrorFeedback = /Password must not contain/i;
fireEvent.click(getByText("Wi-Fi 1"));
const passwordInput = getByLabelText("Password");
const changePassword = (value) =>
fireEvent.change(passwordInput, { target: { value } });
changePassword("12");
expect(getByText(shortErrorFeedback)).toBeDefined();
changePassword(
"longpasswordlongpasswordlongpasswordlongpasswordlongpasswordlong"
);
expect(getByText(longErrorFeedback)).toBeDefined();
});
});

View File

@@ -0,0 +1,404 @@
/*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
export function wifiSettingsFixture() {
return {
devices: [
{
SSID: "TestSSID1",
available_bands: [
{
available_channels: [
{
frequency: 2412,
number: 1,
radar: false,
},
{
frequency: 2417,
number: 2,
radar: false,
},
{
frequency: 2422,
number: 3,
radar: false,
},
{
frequency: 2427,
number: 4,
radar: false,
},
{
frequency: 2432,
number: 5,
radar: false,
},
{
frequency: 2437,
number: 6,
radar: false,
},
{
frequency: 2442,
number: 7,
radar: false,
},
{
frequency: 2447,
number: 8,
radar: false,
},
{
frequency: 2452,
number: 9,
radar: false,
},
{
frequency: 2457,
number: 10,
radar: false,
},
{
frequency: 2462,
number: 11,
radar: false,
},
],
available_htmodes: [
"NOHT",
"HT20",
"HT40",
"VHT20",
"VHT40",
"VHT80",
],
hwmode: "11g",
},
{
available_channels: [
{
frequency: 5180,
number: 36,
radar: false,
},
{
frequency: 5200,
number: 40,
radar: false,
},
{
frequency: 5220,
number: 44,
radar: false,
},
{
frequency: 5240,
number: 48,
radar: false,
},
{
frequency: 5260,
number: 52,
radar: true,
},
{
frequency: 5280,
number: 56,
radar: true,
},
{
frequency: 5300,
number: 60,
radar: true,
},
{
frequency: 5320,
number: 64,
radar: true,
},
{
frequency: 5500,
number: 100,
radar: true,
},
{
frequency: 5520,
number: 104,
radar: true,
},
{
frequency: 5540,
number: 108,
radar: true,
},
{
frequency: 5560,
number: 112,
radar: true,
},
{
frequency: 5580,
number: 116,
radar: true,
},
{
frequency: 5600,
number: 120,
radar: true,
},
{
frequency: 5620,
number: 124,
radar: true,
},
{
frequency: 5640,
number: 128,
radar: true,
},
{
frequency: 5660,
number: 132,
radar: true,
},
{
frequency: 5680,
number: 136,
radar: true,
},
{
frequency: 5700,
number: 140,
radar: true,
},
{
frequency: 5720,
number: 144,
radar: true,
},
{
frequency: 5745,
number: 149,
radar: false,
},
{
frequency: 5765,
number: 153,
radar: false,
},
{
frequency: 5785,
number: 157,
radar: false,
},
{
frequency: 5805,
number: 161,
radar: false,
},
{
frequency: 5825,
number: 165,
radar: false,
},
],
available_htmodes: [
"NOHT",
"HT20",
"HT40",
"VHT20",
"VHT40",
"VHT80",
],
hwmode: "11a",
},
],
channel: 60,
enabled: false,
guest_wifi: {
SSID: "TestGuestSSID",
enabled: false,
password: "",
},
hidden: false,
htmode: "HT80",
hwmode: "11a",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
{
SSID: "Turris",
available_bands: [
{
available_channels: [
{
frequency: 2412,
number: 1,
radar: false,
},
{
frequency: 2417,
number: 2,
radar: false,
},
{
frequency: 2422,
number: 3,
radar: false,
},
{
frequency: 2427,
number: 4,
radar: false,
},
{
frequency: 2432,
number: 5,
radar: false,
},
{
frequency: 2437,
number: 6,
radar: false,
},
{
frequency: 2442,
number: 7,
radar: false,
},
{
frequency: 2447,
number: 8,
radar: false,
},
{
frequency: 2452,
number: 9,
radar: false,
},
{
frequency: 2457,
number: 10,
radar: false,
},
{
frequency: 2462,
number: 11,
radar: false,
},
],
available_htmodes: ["NOHT", "HT20", "HT40"],
hwmode: "11g",
},
],
channel: 11,
enabled: false,
guest_wifi: {
SSID: "TestSSID",
enabled: false,
password: "",
},
hidden: false,
htmode: "HT40",
hwmode: "11g",
id: 1,
password: "TestPass",
encryption: "WPA3",
},
],
};
}
const oneDevice = {
devices: [
{
SSID: "Turris1",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
],
};
const twoDevices = {
devices: [
{
SSID: "",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
{
SSID: "Turris2",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 1,
password: "TestPass",
encryption: "WPA3",
},
],
};
const threeDevices = {
devices: [
{
SSID: "Turris1",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass",
encryption: "WPA3",
},
{
SSID: "Turris2",
channel: 60,
enabled: false,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 1,
password: "TestPass",
encryption: "WPA3",
},
{
SSID: "Turris3",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 2,
password: "",
encryption: "WPA3",
},
],
};
export { oneDevice, twoDevices, threeDevices };

View File

@@ -0,0 +1,941 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<WiFiSettings/> Snapshot 2.4 GHz 1`] = `
"Snapshot Diff:
- First value
+ Second value
@@ -241,207 +241,95 @@
value=\\"0\\"
>
auto
</option>
<option
- value=\\"36\\"
+ value=\\"1\\"
>
- 36
- (5180 MHz )
+ 1
+ (2412 MHz )
</option>
<option
- value=\\"40\\"
+ value=\\"2\\"
>
- 40
- (5200 MHz )
+ 2
+ (2417 MHz )
</option>
<option
- value=\\"44\\"
+ value=\\"3\\"
>
- 44
- (5220 MHz )
+ 3
+ (2422 MHz )
</option>
<option
- value=\\"48\\"
- >
-
- 48
- (5240 MHz )
-
- </option>
- <option
- value=\\"52\\"
- >
-
- 52
- (5260 MHz ,DFS)
-
- </option>
- <option
- value=\\"56\\"
- >
-
- 56
- (5280 MHz ,DFS)
-
- </option>
- <option
- value=\\"60\\"
+ value=\\"4\\"
>
- 60
- (5300 MHz ,DFS)
+ 4
+ (2427 MHz )
</option>
<option
- value=\\"64\\"
+ value=\\"5\\"
>
- 64
- (5320 MHz ,DFS)
+ 5
+ (2432 MHz )
</option>
<option
- value=\\"100\\"
+ value=\\"6\\"
>
- 100
- (5500 MHz ,DFS)
+ 6
+ (2437 MHz )
</option>
<option
- value=\\"104\\"
+ value=\\"7\\"
>
- 104
- (5520 MHz ,DFS)
+ 7
+ (2442 MHz )
</option>
<option
- value=\\"108\\"
+ value=\\"8\\"
>
- 108
- (5540 MHz ,DFS)
+ 8
+ (2447 MHz )
</option>
<option
- value=\\"112\\"
+ value=\\"9\\"
>
- 112
- (5560 MHz ,DFS)
+ 9
+ (2452 MHz )
</option>
<option
- value=\\"116\\"
+ value=\\"10\\"
>
- 116
- (5580 MHz ,DFS)
+ 10
+ (2457 MHz )
</option>
<option
- value=\\"120\\"
+ value=\\"11\\"
>
- 120
- (5600 MHz ,DFS)
-
- </option>
- <option
- value=\\"124\\"
- >
-
- 124
- (5620 MHz ,DFS)
-
- </option>
- <option
- value=\\"128\\"
- >
-
- 128
- (5640 MHz ,DFS)
-
- </option>
- <option
- value=\\"132\\"
- >
-
- 132
- (5660 MHz ,DFS)
-
- </option>
- <option
- value=\\"136\\"
- >
-
- 136
- (5680 MHz ,DFS)
-
- </option>
- <option
- value=\\"140\\"
- >
-
- 140
- (5700 MHz ,DFS)
-
- </option>
- <option
- value=\\"144\\"
- >
-
- 144
- (5720 MHz ,DFS)
-
- </option>
- <option
- value=\\"149\\"
- >
-
- 149
- (5745 MHz )
-
- </option>
- <option
- value=\\"153\\"
- >
-
- 153
- (5765 MHz )
-
- </option>
- <option
- value=\\"157\\"
- >
-
- 157
- (5785 MHz )
-
- </option>
- <option
- value=\\"161\\"
- >
-
- 161
- (5805 MHz )
-
- </option>
- <option
- value=\\"165\\"
- >
-
- 165
- (5825 MHz )
+ 11
+ (2462 MHz )
</option>
</select>
</div>
<div"
`;
exports[`<WiFiSettings/> Snapshot both modules disabled. 1`] = `
<DocumentFragment>
<div
class="card p-4 col-sm-12 col-lg-12 p-0 mb-4"
>
<form>
<div
class="form-group switch"
>
<div
class="custom-control custom-switch custom-control-inline switch-heading"
>
<input
class="custom-control-input"
id="1"
type="checkbox"
/>
<label
class="custom-control-label"
for="1"
>
<h2>
Wi-Fi 1
</h2>
</label>
</div>
</div>
<hr />
<div
class="form-group switch"
>
<div
class="custom-control custom-switch custom-control-inline switch-heading"
>
<input
class="custom-control-input"
id="2"
type="checkbox"
/>
<label
class="custom-control-label"
for="2"
>
<h2>
Wi-Fi 2
</h2>
</label>
</div>
</div>
<div
class="text-right"
>
<button
class="btn btn-primary col-sm-12 col-md-3 col-lg-2"
type="submit"
>
Save
</button>
</div>
</form>
</div>
<div
class="card p-4 col-sm-12 col-lg-12 p-0 mb-4"
>
<h2>
Reset Wi-Fi Settings
</h2>
<p>
If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the current Wi-Fi configuration and restore the default values.
</p>
<div
class="text-right"
>
<button
class="btn btn-primary col-sm-12 col-md-3 col-lg-2"
type="button"
>
Reset Wi-Fi Settings
</button>
</div>
</div>
</DocumentFragment>
`;
exports[`<WiFiSettings/> Snapshot guest network. 1`] = `
"Snapshot Diff:
- First value
+ Second value
@@ -500,10 +500,92 @@
>
Enables Wi-Fi for guests, which is separated from LAN network. Devices connected to this network are allowed to access the internet, but aren't allowed to access other devices and the configuration interface of the router. Parameters of the guest network can be set in the Guest network tab.
</small>
</div>
</div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"22\\"
+ >
+ SSID
+ </label>
+ <div
+ class=\\"input-group\\"
+ >
+ <input
+ class=\\"form-control\\"
+ id=\\"22\\"
+ type=\\"text\\"
+ value=\\"TestGuestSSID\\"
+ />
+ <div
+ class=\\"input-group-append\\"
+ >
+ <button
+ class=\\"input-group-text\\"
+ type=\\"button\\"
+ >
+ <img
+ alt=\\"QR\\"
+ src=\\"/reforis/static/reforis/imgs/QR_icon.svg\\"
+ style=\\"opacity: 0.67;\\"
+ width=\\"20\\"
+ />
+ </button>
+ </div>
+ </div>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ SSID which contains non-standard characters could cause problems on some devices.
+ </small>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"23\\"
+ >
+ Password
+ </label>
+ <div
+ class=\\"input-group\\"
+ >
+ <input
+ autocomplete=\\"current-password\\"
+ class=\\"form-control is-invalid\\"
+ id=\\"23\\"
+ required=\\"\\"
+ type=\\"password\\"
+ value=\\"\\"
+ />
+ <div
+ class=\\"input-group-append\\"
+ >
+ <button
+ class=\\"input-group-text\\"
+ type=\\"button\\"
+ >
+ <i
+ class=\\"fa fa-eye\\"
+ />
+ </button>
+ </div>
+ </div>
+ <div
+ class=\\"invalid-feedback\\"
+ >
+ Password must contain at least 8 symbols
+ </div>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ WPA2/3 pre-shared key, that is required to connect to the network.
+ </small>
+ </div>
<hr />
<div
class=\\"form-group switch\\"
>
<div
@@ -527,10 +609,11 @@
<div
class=\\"text-right\\"
>
<button
class=\\"btn btn-primary col-sm-12 col-md-3 col-lg-2\\"
+ disabled=\\"\\"
type=\\"submit\\"
>
Save
</button>
</div>"
`;
exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
"Snapshot Diff:
- First value
+ Second value
@@ -22,10 +22,488 @@
Wi-Fi 1
</h2>
</label>
</div>
</div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"4\\"
+ >
+ SSID
+ </label>
+ <div
+ class=\\"input-group\\"
+ >
+ <input
+ class=\\"form-control\\"
+ id=\\"4\\"
+ required=\\"\\"
+ type=\\"text\\"
+ value=\\"TestSSID1\\"
+ />
+ <div
+ class=\\"input-group-append\\"
+ >
+ <button
+ class=\\"input-group-text\\"
+ type=\\"button\\"
+ >
+ <img
+ alt=\\"QR\\"
+ src=\\"/reforis/static/reforis/imgs/QR_icon.svg\\"
+ style=\\"opacity: 0.67;\\"
+ width=\\"20\\"
+ />
+ </button>
+ </div>
+ </div>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ SSID which contains non-standard characters could cause problems on some devices.
+ </small>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"5\\"
+ >
+ Password
+ </label>
+ <div
+ class=\\"input-group\\"
+ >
+ <input
+ autocomplete=\\"current-password\\"
+ class=\\"form-control\\"
+ id=\\"5\\"
+ required=\\"\\"
+ type=\\"password\\"
+ value=\\"TestPass\\"
+ />
+ <div
+ class=\\"input-group-append\\"
+ >
+ <button
+ class=\\"input-group-text\\"
+ type=\\"button\\"
+ >
+ <i
+ class=\\"fa fa-eye\\"
+ />
+ </button>
+ </div>
+ </div>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ WPA2/3 pre-shared key, that is required to connect to the network.
+ </small>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <div
+ class=\\"custom-control custom-checkbox \\"
+ >
+ <input
+ class=\\"custom-control-input\\"
+ id=\\"6\\"
+ type=\\"checkbox\\"
+ />
+ <label
+ class=\\"custom-control-label\\"
+ for=\\"6\\"
+ >
+ Hide SSID
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ If set, network is not visible when scanning for available networks.
+ </small>
+ </label>
+ </div>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ class=\\"d-block\\"
+ for=\\"7\\"
+ >
+ GHz
+ </label>
+ <div
+ class=\\"custom-control custom-radio custom-control-inline\\"
+ >
+ <input
+ class=\\"custom-control-input\\"
+ id=\\"hwmode-0-0\\"
+ name=\\"hwmode-0\\"
+ type=\\"radio\\"
+ value=\\"11g\\"
+ />
+ <label
+ class=\\"custom-control-label\\"
+ for=\\"hwmode-0-0\\"
+ >
+ 2.4
+ </label>
+ </div>
+ <div
+ class=\\"custom-control custom-radio custom-control-inline\\"
+ >
+ <input
+ checked=\\"\\"
+ class=\\"custom-control-input\\"
+ id=\\"hwmode-0-1\\"
+ name=\\"hwmode-0\\"
+ type=\\"radio\\"
+ value=\\"11a\\"
+ />
+ <label
+ class=\\"custom-control-label\\"
+ for=\\"hwmode-0-1\\"
+ >
+ 5
+ </label>
+ </div>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ The 2.4 GHz band is more widely supported by clients, but tends to have more interference. The 5 GHz band is a newer standard and may not be supported by all your devices. It usually has less interference, but the signal does not carry so well indoors.
+ </small>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"8\\"
+ >
+ 802.11n/ac/ax mode
+ </label>
+ <select
+ class=\\"custom-select\\"
+ id=\\"8\\"
+ >
+ <option
+ value=\\"NOHT\\"
+ >
+ Disabled
+ </option>
+ <option
+ value=\\"HT20\\"
+ >
+ 802.11n - 20 MHz wide channel
+ </option>
+ <option
+ value=\\"HT40\\"
+ >
+ 802.11n - 40 MHz wide channel
+ </option>
+ <option
+ value=\\"VHT20\\"
+ >
+ 802.11ac - 20 MHz wide channel
+ </option>
+ <option
+ value=\\"VHT40\\"
+ >
+ 802.11ac - 40 MHz wide channel
+ </option>
+ <option
+ value=\\"VHT80\\"
+ >
+ 802.11ac - 80 MHz wide channel
+ </option>
+ </select>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ Change this to adjust 802.11n/ac/ax mode of operation. 802.11n with 40 MHz wide channels can yield higher throughput but can cause more interference in the network. If you don't know what to choose, use the default option with 20 MHz wide channel.
+ </small>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"9\\"
+ >
+ Channel
+ </label>
+ <select
+ class=\\"custom-select\\"
+ id=\\"9\\"
+ >
+ <option
+ value=\\"0\\"
+ >
+ auto
+ </option>
+ <option
+ value=\\"36\\"
+ >
+
+ 36
+ (5180 MHz )
+
+ </option>
+ <option
+ value=\\"40\\"
+ >
+
+ 40
+ (5200 MHz )
+
+ </option>
+ <option
+ value=\\"44\\"
+ >
+
+ 44
+ (5220 MHz )
+
+ </option>
+ <option
+ value=\\"48\\"
+ >
+
+ 48
+ (5240 MHz )
+
+ </option>
+ <option
+ value=\\"52\\"
+ >
+
+ 52
+ (5260 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"56\\"
+ >
+
+ 56
+ (5280 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"60\\"
+ >
+
+ 60
+ (5300 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"64\\"
+ >
+
+ 64
+ (5320 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"100\\"
+ >
+
+ 100
+ (5500 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"104\\"
+ >
+
+ 104
+ (5520 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"108\\"
+ >
+
+ 108
+ (5540 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"112\\"
+ >
+
+ 112
+ (5560 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"116\\"
+ >
+
+ 116
+ (5580 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"120\\"
+ >
+
+ 120
+ (5600 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"124\\"
+ >
+
+ 124
+ (5620 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"128\\"
+ >
+
+ 128
+ (5640 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"132\\"
+ >
+
+ 132
+ (5660 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"136\\"
+ >
+
+ 136
+ (5680 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"140\\"
+ >
+
+ 140
+ (5700 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"144\\"
+ >
+
+ 144
+ (5720 MHz ,DFS)
+
+ </option>
+ <option
+ value=\\"149\\"
+ >
+
+ 149
+ (5745 MHz )
+
+ </option>
+ <option
+ value=\\"153\\"
+ >
+
+ 153
+ (5765 MHz )
+
+ </option>
+ <option
+ value=\\"157\\"
+ >
+
+ 157
+ (5785 MHz )
+
+ </option>
+ <option
+ value=\\"161\\"
+ >
+
+ 161
+ (5805 MHz )
+
+ </option>
+ <option
+ value=\\"165\\"
+ >
+
+ 165
+ (5825 MHz )
+
+ </option>
+ </select>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <label
+ for=\\"10\\"
+ >
+ Encryption
+ </label>
+ <select
+ class=\\"custom-select\\"
+ id=\\"10\\"
+ >
+ <option
+ value=\\"WPA3\\"
+ >
+ WPA3 only
+ </option>
+ <option
+ value=\\"WPA2/3\\"
+ >
+ WPA3 with WPA2 as fallback (default)
+ </option>
+ <option
+ value=\\"WPA2\\"
+ >
+ WPA2 only
+ </option>
+ </select>
+ <small
+ class=\\"form-text text-muted\\"
+ >
+ The WPA3 standard is the new most secure encryption method that is suggested to be used with any device that supports it. The older devices without WPA3 support require older WPA2. If you experience issues with connecting older devices, try to enable WPA2.
+ </small>
+ </div>
+ <div
+ class=\\"form-group\\"
+ >
+ <div
+ class=\\"custom-control custom-switch\\"
+ >
+ <input
+ class=\\"custom-control-input\\"
+ id=\\"11\\"
+ type=\\"checkbox\\"
+ />
+ <label
+ class=\\"custom-control-label\\"
+ for=\\"11\\"
+ >
+ Enable Guest Wi-Fi
+ </label>
+ <small
+ class=\\"form-text text-muted mt-0 mb-3\\"
+ >
+ Enables Wi-Fi for guests, which is separated from LAN network. Devices connected to this network are allowed to access the internet, but aren't allowed to access other devices and the configuration interface of the router. Parameters of the guest network can be set in the Guest network tab.
+ </small>
+ </div>
+ </div>
<hr />
<div
class=\\"form-group switch\\"
>
<div"
`;

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
export const HTMODES = {
NOHT: _("Disabled"),
HT20: _("802.11n - 20 MHz wide channel"),
HT40: _("802.11n - 40 MHz wide channel"),
VHT20: _("802.11ac - 20 MHz wide channel"),
VHT40: _("802.11ac - 40 MHz wide channel"),
VHT80: _("802.11ac - 80 MHz wide channel"),
VHT160: _("802.11ac - 160 MHz wide channel"),
HE20: _("802.11ax - 20 MHz wide channel"),
HE40: _("802.11ax - 40 MHz wide channel"),
HE80: _("802.11ax - 80 MHz wide channel"),
HE160: _("802.11ax - 160 MHz wide channel"),
};
export const HWMODES = {
"11g": "2.4",
"11a": "5",
};
export const ENCRYPTIONMODES = {
WPA3: _("WPA3 only"),
"WPA2/3": _("WPA3 with WPA2 as fallback (default)"),
WPA2: _("WPA2 only"),
};
export const HELP_TEXTS = {
ssid: _(
"SSID which contains non-standard characters could cause problems on some devices."
),
password: _(
"WPA2/3 pre-shared key, that is required to connect to the network."
),
hidden: _(
"If set, network is not visible when scanning for available networks."
),
hwmode: _(
"The 2.4 GHz band is more widely supported by clients, but tends to have more interference. The 5 GHz band is a newer standard and may not be supported by all your devices. It usually has less interference, but the signal does not carry so well indoors."
),
htmode: _(
"Change this to adjust 802.11n/ac/ax mode of operation. 802.11n with 40 MHz wide channels can yield higher throughput but can cause more interference in the network. If you don't know what to choose, use the default option with 20 MHz wide channel."
),
guest_wifi_enabled: _(
"Enables Wi-Fi for guests, which is separated from LAN network. Devices connected to this network are allowed to access the internet, but aren't allowed to access other devices and the configuration interface of the router. Parameters of the guest network can be set in the Guest network tab."
),
wpa3: _(
"The WPA3 standard is the new most secure encryption method that is suggested to be used with any device that supports it. The older devices without WPA3 support require older WPA2. If you experience issues with connecting older devices, try to enable WPA2."
),
};

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
export function createAndDownloadPdf(SSID, password) {
const docDefinition = {
content: [
{
text: "Wi-Fi",
style: "header",
fontSize: 55,
alignment: "center",
},
{
qr: toQRCodeContent(SSID, password),
fit: "350",
margin: [0, 80],
alignment: "center",
},
{
text: `SSID: ${SSID}`,
fontSize: 25,
alignment: "center",
},
{
text: `Password: ${password}`,
fontSize: 25,
alignment: "center",
},
],
};
// pdfmake is exposed by reForis main application. Thus we can use it from globals.
window.pdfMake.createPdf(docDefinition).download("wifi.pdf");
}
export function toQRCodeContent(SSID, password) {
return `WIFI:S:${SSID};T:WPA2;P:${password};;`;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
import React from "react";
import {
fireEvent,
getByText,
queryByText,
render,
wait,
} from "customTestRender";
import mockAxios from "jest-mock-axios";
import { mockJSONError } from "testUtils/network";
import { mockSetAlert } from "testUtils/alertContextMock";
import { RebootButton } from "../RebootButton";
describe("<RebootButton/>", () => {
let componentContainer;
beforeEach(() => {
const { container } = render(
<>
<div id="modal-container" />
<RebootButton />
</>
);
componentContainer = container;
});
it("Render.", () => {
expect(componentContainer).toMatchSnapshot();
});
it("Render modal.", () => {
expect(queryByText(componentContainer, "Confirm reboot")).toBeNull();
fireEvent.click(getByText(componentContainer, "Reboot"));
expect(componentContainer).toMatchSnapshot();
});
it("Confirm reboot.", () => {
fireEvent.click(getByText(componentContainer, "Reboot"));
fireEvent.click(getByText(componentContainer, "Confirm reboot"));
expect(mockAxios.post).toHaveBeenCalledWith(
"/reforis/api/reboot",
undefined,
expect.anything()
);
});
it("Hold error.", async () => {
fireEvent.click(getByText(componentContainer, "Reboot"));
fireEvent.click(getByText(componentContainer, "Confirm reboot"));
mockJSONError();
await wait(() =>
expect(mockSetAlert).toBeCalledWith("Reboot request failed.")
);
});
});

View File

@@ -0,0 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<RebootButton/> Render modal. 1`] = `
<div>
<div
id="modal-container"
>
<div
class="modal fade show"
role="dialog"
>
<div
class="modal-dialog modal-dialog-centered"
role="document"
>
<div
class="modal-content"
>
<div
class="modal-header"
>
<h5
class="modal-title"
>
Warning!
</h5>
<button
class="close"
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
</div>
<div
class="modal-body"
>
<p>
Are you sure you want to restart the router?
</p>
</div>
<div
class="modal-footer"
>
<button
class="btn btn-primary "
type="button"
>
Cancel
</button>
<button
class="btn btn-danger"
type="button"
>
Confirm reboot
</button>
</div>
</div>
</div>
</div>
</div>
<button
class="btn btn-danger"
type="button"
>
Reboot
</button>
</div>
`;
exports[`<RebootButton/> Render. 1`] = `
<div>
<div
id="modal-container"
/>
<button
class="btn btn-danger"
type="button"
>
Reboot
</button>
</div>
`;

View File

@@ -1,24 +0,0 @@
/*
* Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information.
*/
export const REFORIS_URL_PREFIX = process.env.LIGHTTPD ? "/reforis" : "";
export const ForisURLs = {
login: `${REFORIS_URL_PREFIX}/login`,
static: `${REFORIS_URL_PREFIX}/static/reforis`,
wifi: `${REFORIS_URL_PREFIX}/network-settings/wifi`,
packageManagement: {
updateSettings: `${REFORIS_URL_PREFIX}/package-management/update-settings`,
updates: `${REFORIS_URL_PREFIX}/package-management/updates`,
},
// Notifications links are used with <Link/> inside Router, thus url subdir is not required.
notifications: "/notifications",
notificationsSettings: "/administration/notifications-settings",
luci: "/cgi-bin/luci",
};

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