1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2025-06-15 13:36:35 +02:00

Compare commits

...

22 Commits

Author SHA1 Message Date
1009169315 Bump v5.0.3 2020-09-23 15:07:56 +02:00
661f92bbcf Fix infinity loop caused by WebSockets 2020-09-14 06:38:23 +02:00
12b862c568 Bump v5.0.1 2020-07-21 11:59:13 +02:00
54f9f984f1 NPM audit fix & update of packages 2020-07-21 11:59:12 +02:00
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
e7f9fbca96 Merge branch 'dev' into 'new-channel-bandwidth'
# Conflicts:
#   src/common/WiFiSettings/WiFiForm.js
2020-07-17 16:24:43 +02:00
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
cea8aa0c12 Fix a Wi-Fi Form bug with additional Wi-Fi modules 2020-07-17 14:33:35 +02:00
16a7a6c52d Update Snapshots 2020-07-17 14:19:56 +02:00
597b6fcf4c Add Natural sort order for list of options 2020-07-17 14:19:56 +02:00
5eb6b90ed4 Add 802.11ac 160 MHz wide channel to constants 2020-07-17 13:38:30 +02:00
48c323c1a1 Fix Wi-Fi Form bug with additional Wi-Fi modules 2020-07-17 12:26:46 +02:00
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
67e4abe4d1 Add test suites for a Wi-Fi form submission 2020-07-07 11:35:58 +02:00
57f1ccced8 Fix form submission button for one or more Wi-Fi modules. 2020-06-29 13:24:42 +02:00
1e95bff7ff Merge branch 'dev' into 'master'
Dev

See merge request turris/reforis/foris-js!114
2020-06-04 22:56:37 +02:00
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
a5e096dc00 Fix and update docs. 2020-06-04 22:52:24 +02:00
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
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
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
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
21 changed files with 1967 additions and 275 deletions

View File

@ -33,3 +33,12 @@ externals: {
} }
``` ```
### Docs
Build or watch docs to get more info about library:
```bash
make docs
```
or
```bash
make docs-watch
```

22
docs/development.md Normal file
View File

@ -0,0 +1,22 @@
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 [`npm link`](https://docs.npmjs.com/cli/link).
Please, notice that it will not work if you link library just from root of the repo. It happens due to location of
sources `./src`. You need to pack library first `make pack` and then link it from `./dist` directory.
Yeah it's not such comfortable solution for development. But it can fixed by writing small script similar as `make pack`
but with linking every file and directory from `./src` to the some directory and linking then from it. Notice that you
need to link `package.json` and `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 ;)

View File

@ -1 +1,4 @@
Foris JS library is set of componets and utils for Foris JS application and plugins. Foris JS library is set of components and utils for Foris JS application and plugins.
Please notice that all of these components or utils are used in reForis and plugins. If you like to study by example I would
recommend to full-text search these repos.

1954
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "foris", "name": "foris",
"version": "4.5.1", "version": "5.0.3",
"description": "Set of components and utils for Foris and its plugins.", "description": "Set of components and utils for Foris and its plugins.",
"author": "CZ.NIC, z.s.p.o.", "author": "CZ.NIC, z.s.p.o.",
"repository": { "repository": {
@ -38,8 +38,11 @@
"@testing-library/react": "^8.0.9", "@testing-library/react": "^8.0.9",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"bootstrap": "^4.5.0",
"css-loader": "^3.5.3",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-reforis": "^1.0.0", "eslint-config-reforis": "^1.0.0",
"file-loader": "^6.0.0",
"jest": "^25.2.0", "jest": "^25.2.0",
"jest-mock-axios": "^3.2.0", "jest-mock-axios": "^3.2.0",
"moment-timezone": "^0.5.28", "moment-timezone": "^0.5.28",
@ -48,7 +51,9 @@
"react-dom": "16.9.0", "react-dom": "16.9.0",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-styleguidist": "^10.6.2", "react-styleguidist": "^10.6.2",
"snapshot-diff": "^0.7.0" "snapshot-diff": "^0.7.0",
"style-loader": "^1.2.1",
"webpack": "^4.43.0"
}, },
"scripts": { "scripts": {
"lint": "eslint src", "lint": "eslint src",

View File

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

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

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

@ -3,13 +3,39 @@ Bootstrap component for file input. Includes label and has predefined sizes and
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

@ -6,8 +6,6 @@ it's required to have an element `<div id={"modal-container"}/>` somewhere on th
<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';

View File

@ -28,10 +28,11 @@ export function Select({
}) { }) {
const uid = useUID(); const uid = useUID();
const options = Object.keys(choices).map( const options = Object.keys(choices).sort(
(a, b) => a - b || a.toString().localeCompare(b.toString()),
).map(
(key) => <option key={key} value={key}>{choices[key]}</option>, (key) => <option key={key} value={key}>{choices[key]}</option>,
); );
return ( return (
<div className="form-group"> <div className="form-group">
<label htmlFor={uid}>{label}</label> <label htmlFor={uid}>{label}</label>

View File

@ -31,18 +31,19 @@ WiFiForm.propTypes = {
WiFiForm.defaultProps = { WiFiForm.defaultProps = {
formData: { devices: [] }, formData: { devices: [] },
setFormValue: () => {}, setFormValue: () => { },
hasGuestNetwork: true, hasGuestNetwork: true,
}; };
export default function WiFiForm({ export default function WiFiForm({
formData, formErrors, setFormValue, hasGuestNetwork, disabled, formData, formErrors, setFormValue, hasGuestNetwork, disabled,
}) { }) {
return formData.devices.map((device) => ( return formData.devices.map((device, index) => (
<DeviceForm <DeviceForm
key={device.id} key={device.id}
formData={device} formData={device}
formErrors={(formErrors || [])[device.id]} deviceIndex={index}
formErrors={(formErrors || [])[index]}
setFormValue={setFormValue} setFormValue={setFormValue}
hasGuestNetwork={hasGuestNetwork} hasGuestNetwork={hasGuestNetwork}
disabled={disabled} disabled={disabled}
@ -65,6 +66,7 @@ DeviceForm.propTypes = {
formErrors: PropTypes.object.isRequired, formErrors: PropTypes.object.isRequired,
setFormValue: PropTypes.func.isRequired, setFormValue: PropTypes.func.isRequired,
hasGuestNetwork: PropTypes.bool, hasGuestNetwork: PropTypes.bool,
deviceIndex: PropTypes.number,
}; };
DeviceForm.defaultProps = { DeviceForm.defaultProps = {
@ -73,7 +75,7 @@ DeviceForm.defaultProps = {
}; };
function DeviceForm({ function DeviceForm({
formData, formErrors, setFormValue, hasGuestNetwork, ...props formData, formErrors, setFormValue, hasGuestNetwork, deviceIndex, ...props
}) { }) {
const deviceID = formData.id; const deviceID = formData.id;
return ( return (
@ -84,7 +86,7 @@ function DeviceForm({
checked={formData.enabled} checked={formData.enabled}
onChange={setFormValue( onChange={setFormValue(
(value) => ({ devices: { [deviceID]: { enabled: { $set: value } } } }), (value) => ({ devices: { [deviceIndex]: { enabled: { $set: value } } } }),
)} )}
{...props} {...props}
@ -98,7 +100,13 @@ function DeviceForm({
error={formErrors.SSID || null} error={formErrors.SSID || null}
required required
onChange={setFormValue( onChange={setFormValue(
(value) => ({ devices: { [deviceID]: { SSID: { $set: value } } } }), (value) => ({
devices: {
[deviceIndex]: {
SSID: { $set: value },
},
},
}),
)} )}
{...props} {...props}
@ -121,7 +129,7 @@ function DeviceForm({
onChange={setFormValue( onChange={setFormValue(
(value) => ( (value) => (
{ devices: { [deviceID]: { password: { $set: value } } } } { devices: { [deviceIndex]: { password: { $set: value } } } }
), ),
)} )}
@ -135,7 +143,7 @@ function DeviceForm({
onChange={setFormValue( onChange={setFormValue(
(value) => ( (value) => (
{ devices: { [deviceID]: { hidden: { $set: value } } } } { devices: { [deviceIndex]: { hidden: { $set: value } } } }
), ),
)} )}
@ -152,7 +160,7 @@ function DeviceForm({
onChange={setFormValue( onChange={setFormValue(
(value) => ({ (value) => ({
devices: { devices: {
[deviceID]: { [deviceIndex]: {
hwmode: { $set: value }, hwmode: { $set: value },
channel: { $set: "0" }, channel: { $set: "0" },
}, },
@ -171,7 +179,7 @@ function DeviceForm({
onChange={setFormValue( onChange={setFormValue(
(value) => ( (value) => (
{ devices: { [deviceID]: { htmode: { $set: value } } } } { devices: { [deviceIndex]: { htmode: { $set: value } } } }
), ),
)} )}
@ -185,7 +193,7 @@ function DeviceForm({
onChange={setFormValue( onChange={setFormValue(
(value) => ( (value) => (
{ devices: { [deviceID]: { channel: { $set: value } } } } { devices: { [deviceIndex]: { channel: { $set: value } } } }
), ),
)} )}
@ -194,7 +202,7 @@ function DeviceForm({
{hasGuestNetwork && ( {hasGuestNetwork && (
<WifiGuestForm <WifiGuestForm
formData={{ id: deviceID, ...formData.guest_wifi }} formData={{ id: deviceIndex, ...formData.guest_wifi }}
formErrors={formErrors.guest_wifi || {}} formErrors={formErrors.guest_wifi || {}}
setFormValue={setFormValue} setFormValue={setFormValue}

View File

@ -64,7 +64,7 @@ function prepDataToSubmit(formData) {
return formData; return formData;
} }
function validator(formData) { export function validator(formData) {
const formErrors = formData.devices.map( const formErrors = formData.devices.map(
(device) => { (device) => {
if (!device.enabled) return {}; if (!device.enabled) return {};
@ -89,5 +89,5 @@ function validator(formData) {
return errors; return errors;
}, },
); );
return JSON.stringify(formErrors) === "[{},{}]" ? null : formErrors; return JSON.stringify(formErrors).match(/\[[{},?]+\]/) ? null : formErrors;
} }

View File

@ -13,8 +13,8 @@ import { fireEvent, render, wait } from "customTestRender";
import { WebSockets } from "webSockets/WebSockets"; import { WebSockets } from "webSockets/WebSockets";
import { mockJSONError } from "testUtils/network"; import { mockJSONError } from "testUtils/network";
import { wifiSettingsFixture } from "./__fixtures__/wifiSettings"; import { wifiSettingsFixture, oneDevice, twoDevices, threeDevices } from "./__fixtures__/wifiSettings";
import { WiFiSettings } from "../WiFiSettings"; import { WiFiSettings, validator } from "../WiFiSettings";
describe("<WiFiSettings/>", () => { describe("<WiFiSettings/>", () => {
let firstRender; let firstRender;
@ -159,4 +159,18 @@ describe("<WiFiSettings/>", () => {
}; };
expect(mockAxios.post).toHaveBeenCalledWith(endpoint, data, expect.anything()); 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);
});
}); });

View File

@ -316,3 +316,85 @@ export function wifiSettingsFixture() {
], ],
}; };
} }
const oneDevice = {
devices: [
{
SSID: "Turris1",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass"
}
]
};
const twoDevices = {
devices: [
{
SSID: "",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass"
},
{
SSID: "Turris2",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass"
}
]
};
const threeDevices = {
devices: [
{
SSID: "Turris1",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass"
},
{
SSID: "Turris2",
channel: 60,
enabled: false,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: "TestPass"
},
{
SSID: "Turris3",
channel: 60,
enabled: true,
guest_wifi: { enabled: false },
hidden: false,
htmode: "HT40",
hwmode: "11a",
id: 0,
password: ""
}
]
};
export {oneDevice, twoDevices, threeDevices};

View File

@ -617,11 +617,6 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
+ id=\\"8\\" + id=\\"8\\"
+ > + >
+ <option + <option
+ value=\\"NOHT\\"
+ >
+ Disabled
+ </option>
+ <option
+ value=\\"HT20\\" + value=\\"HT20\\"
+ > + >
+ 802.11n - 20 MHz wide channel + 802.11n - 20 MHz wide channel
@ -632,6 +627,11 @@ exports[`<WiFiSettings/> Snapshot one module enabled. 1`] = `
+ 802.11n - 40 MHz wide channel + 802.11n - 40 MHz wide channel
+ </option> + </option>
+ <option + <option
+ value=\\"NOHT\\"
+ >
+ Disabled
+ </option>
+ <option
+ value=\\"VHT20\\" + value=\\"VHT20\\"
+ > + >
+ 802.11ac - 20 MHz wide channel + 802.11ac - 20 MHz wide channel

View File

@ -12,6 +12,7 @@ export const HTMODES = {
VHT20: _("802.11ac - 20 MHz wide channel"), VHT20: _("802.11ac - 20 MHz wide channel"),
VHT40: _("802.11ac - 40 MHz wide channel"), VHT40: _("802.11ac - 40 MHz wide channel"),
VHT80: _("802.11ac - 80 MHz wide channel"), VHT80: _("802.11ac - 80 MHz wide channel"),
VHT160: _("802.11ac - 160 MHz wide channel"),
}; };
export const HWMODES = { export const HWMODES = {
"11g": "2.4", "11g": "2.4",

View File

@ -0,0 +1,12 @@
/*
* 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.
*/
// Mock babel (gettext)
global._ = (str) => str;
global.ngettext = (str) => str;
global.babel = { format: (str) => str };
global.ForisTranslations = { locale: "en" };

View File

@ -7,18 +7,13 @@
import mockAxios from "jest-mock-axios"; import mockAxios from "jest-mock-axios";
import moment from "moment-timezone"; import moment from "moment-timezone";
import "./mockGlobals";
// Setup axios cleanup // Setup axios cleanup
global.afterEach(() => { global.afterEach(() => {
mockAxios.reset(); mockAxios.reset();
}); });
// Mock babel (gettext)
global._ = (str) => str;
global.ngettext = (str) => str;
global.babel = { format: (str) => str };
global.ForisTranslations = { locale: "en" };
// Mock web sockets // Mock web sockets
window.WebSocket = jest.fn(); window.WebSocket = jest.fn();

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.
@ -12,7 +12,7 @@ import { ForisURLs } from "../utils/forisUrls";
const PROTOCOL = window.location.protocol === "http:" ? "ws" : "wss"; const PROTOCOL = window.location.protocol === "http:" ? "ws" : "wss";
const URL = process.env.LIGHTTPD const URL = process.env.LIGHTTPD
? `${PROTOCOL}://${window.location.hostname}/foris-ws` ? `${PROTOCOL}://${window.location.host}/foris-ws`
: `${PROTOCOL}://${window.location.hostname}:${9081}`; : `${PROTOCOL}://${window.location.hostname}:${9081}`;
const WAITING_FOR_CONNECTION_TIMEOUT = 500; const WAITING_FOR_CONNECTION_TIMEOUT = 500;

View File

@ -14,6 +14,10 @@ module.exports = {
name: "Foris JS", name: "Foris JS",
content: "docs/intro.md", content: "docs/intro.md",
}, },
{
name: "Development (Linking)",
content: "docs/development.md",
},
{ {
name: "Foris forms", name: "Foris forms",
components: [ components: [
@ -24,6 +28,14 @@ module.exports = {
exampleMode: "expand", exampleMode: "expand",
usageMode: "expand", usageMode: "expand",
}, },
{
name: "Alert Context",
components: [
"src/alertContext/AlertContext.js",
],
exampleMode: "expand",
usageMode: "expand",
},
{ {
name: "Bootstrap components", name: "Bootstrap components",
description: "Set of bootstrap components.", description: "Set of bootstrap components.",
@ -37,6 +49,7 @@ module.exports = {
], ],
require: [ require: [
"babel-polyfill", "babel-polyfill",
path.join(__dirname, "src/testUtils/mockGlobals"),
path.join(__dirname, "node_modules/bootstrap/dist/css/bootstrap.min.css"), path.join(__dirname, "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(__dirname, "node_modules/@fortawesome/fontawesome-free/css/all.min.css"), path.join(__dirname, "node_modules/@fortawesome/fontawesome-free/css/all.min.css"),
], ],