├── .gitignore ├── .gitlab-ci.yml ├── .gitlab └── issue_templates │ └── Bug.md ├── .weblate ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── babel.cfg ├── docs ├── Makefile ├── assets │ ├── reforis_logo.png │ └── reforis_screenshot.png ├── make.bat └── source │ ├── api.rst │ ├── api_blueprint.rst │ ├── application_factory.rst │ ├── auth.rst │ ├── backend.rst │ ├── conf.py │ ├── guide_blueprint.html │ ├── guide_blueprint.rst │ ├── http_directive_patch.py │ ├── import_mock.py │ ├── index.rst │ ├── locale.rst │ ├── plugins.rst │ └── views.rst ├── js ├── .eslintignore ├── .eslintrc.js ├── .prettierrc ├── babel.config.js ├── docs │ └── introduction.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src │ ├── __mocks__ │ │ ├── axios.js │ │ └── styleMock.js │ ├── about │ │ ├── About.js │ │ └── __tests__ │ │ │ └── About.test.js │ ├── app.css │ ├── app.js │ ├── common │ │ ├── API.js │ │ ├── FactoryResetButton.js │ │ ├── RebootButton.js │ │ ├── ScrollToTopArrow.css │ │ ├── ScrollToTopArrow.js │ │ └── network │ │ │ ├── DHCP6ClientsList.js │ │ │ ├── DHCPClientForm.js │ │ │ ├── DHCPClientsList.js │ │ │ ├── DHCPServerForm.js │ │ │ ├── DHCPValidators.js │ │ │ ├── StaticIPForm.js │ │ │ ├── __tests__ │ │ │ ├── DHCPClientsList.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── DHCPClientsList.js │ │ │ └── __snapshots__ │ │ │ │ └── DHCPClientsList.test.js.snap │ │ │ ├── utils.js │ │ │ └── validators.js │ ├── connectionTest │ │ ├── ConnectionTest.js │ │ ├── ConnectionTestButton.js │ │ ├── ConnectionTestResult.js │ │ ├── __tests__ │ │ │ ├── ConnectionTest.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── testResults.js │ │ │ └── __snapshots__ │ │ │ │ └── ConnectionTest.test.js.snap │ │ └── hooks.js │ ├── dns │ │ ├── DNS.js │ │ ├── DNSForm.js │ │ ├── DNSSECDisableModal.js │ │ ├── Forwarders │ │ │ ├── Forwarder │ │ │ │ ├── ForwarderForm.js │ │ │ │ ├── ForwarderModal.js │ │ │ │ ├── IPAddressesForm.js │ │ │ │ ├── hooks.js │ │ │ │ └── validator.js │ │ │ ├── Forwarders.js │ │ │ ├── ForwardersTable.js │ │ │ ├── hooks.js │ │ │ └── propTypes.js │ │ ├── __tests__ │ │ │ ├── DNS.test.js │ │ │ ├── ForwarderForm.test.js │ │ │ ├── Forwarders.test.js │ │ │ ├── __fixtures__ │ │ │ │ ├── dns.js │ │ │ │ └── forwarders.js │ │ │ └── __snapshots__ │ │ │ │ ├── DNS.test.js.snap │ │ │ │ └── ForwarderForm.test.js.snap │ │ └── constants.js │ ├── docsUtils │ │ └── setup.js │ ├── guestNetwork │ │ ├── GuestNetwork.js │ │ ├── GuestNetworkDHCPClientsList.js │ │ ├── GuestNetworkForm.js │ │ ├── GuestNetworkNotification.js │ │ └── __tests__ │ │ │ ├── GuestNetwork.test.js │ │ │ ├── __fixtures__ │ │ │ └── guestNetwork.js │ │ │ └── __snapshots__ │ │ │ └── GuestNetwork.test.js.snap │ ├── guide │ │ ├── Guide.css │ │ ├── Guide.js │ │ ├── GuideControls │ │ │ ├── GuideControls.css │ │ │ ├── GuideControls.js │ │ │ ├── NextStepButton.js │ │ │ └── SkipGuideButton.js │ │ ├── GuideHelper.js │ │ ├── GuideNavigation │ │ │ ├── GuideNavigation.js │ │ │ └── GuideNavigationItem.js │ │ ├── GuidePage.js │ │ ├── GuidePages │ │ │ ├── GuideFinish.js │ │ │ └── WorkflowSelect.js │ │ ├── GuideRouter.js │ │ ├── __tests__ │ │ │ ├── Guide.test.js │ │ │ ├── GuideFinish.test.js │ │ │ ├── GuideHelper.test.js │ │ │ ├── GuideNavigation.test.js │ │ │ ├── WorkflowSelect.test.js │ │ │ ├── __fixtures__ │ │ │ │ ├── guide.js │ │ │ │ └── workflowSelect.js │ │ │ └── __snapshots__ │ │ │ │ ├── Guide.test.js.snap │ │ │ │ ├── GuideFinish.test.js.snap │ │ │ │ ├── GuideHelper.test.js.snap │ │ │ │ ├── GuideNavigation.test.js.snap │ │ │ │ └── WorkflowSelect.test.js.snap │ │ ├── constants.js │ │ ├── hooks.js │ │ └── steps.js │ ├── hostname │ │ ├── Hostname.js │ │ ├── HostnameForm.js │ │ └── __tests__ │ │ │ ├── Hostname.test.js │ │ │ └── __snapshots__ │ │ │ └── Hostname.test.js.snap │ ├── interfaces │ │ ├── Interface.js │ │ ├── Interfaces.css │ │ ├── Interfaces.js │ │ ├── InterfacesForm.js │ │ ├── Network.js │ │ ├── SelectedInterface.js │ │ ├── __tests__ │ │ │ ├── Interfaces.test.js │ │ │ ├── InterfacesForm.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── interfaces.js │ │ │ └── __snapshots__ │ │ │ │ └── Interfaces.test.js.snap │ │ └── constants.js │ ├── lan │ │ ├── LAN.js │ │ ├── LANForm.js │ │ ├── LANManagedForm.js │ │ ├── LANUnmanagedForm.js │ │ ├── LAN_DHCP_ClientsList.js │ │ └── __tests__ │ │ │ ├── LAN.test.js │ │ │ ├── __fixtures__ │ │ │ └── lanSettings.js │ │ │ └── __snapshots__ │ │ │ └── LAN.test.js.snap │ ├── main │ │ ├── Main.js │ │ ├── TopBar │ │ │ ├── LogoutButton.js │ │ │ ├── NotificationsDropdown │ │ │ │ ├── NotificationsDropdown.css │ │ │ │ ├── NotificationsDropdown.js │ │ │ │ ├── NotificationsDropdownButton.css │ │ │ │ ├── NotificationsDropdownButton.js │ │ │ │ ├── NotificationsDropdownItem.css │ │ │ │ ├── NotificationsDropdownItem.js │ │ │ │ ├── NotificationsDropdownMenu.css │ │ │ │ └── NotificationsDropdownMenu.js │ │ │ ├── TopBar.css │ │ │ ├── TopBar.js │ │ │ ├── languagesDropdown │ │ │ │ ├── LanguagesDropdown.css │ │ │ │ ├── LanguagesDropdown.js │ │ │ │ └── hooks.js │ │ │ ├── rebootDropdown │ │ │ │ ├── RebootDropdown.css │ │ │ │ ├── RebootDropdown.js │ │ │ │ └── __tests__ │ │ │ │ │ ├── RebootDropdown.test.js │ │ │ │ │ └── __snapshots__ │ │ │ │ │ └── RebootDropdown.test.js.snap │ │ │ └── updatesDropdown │ │ │ │ ├── UpdatesDropdown.js │ │ │ │ └── __tests__ │ │ │ │ ├── UpdatesDropdown.test.js │ │ │ │ └── __snapshots__ │ │ │ │ └── UpdatesDropdown.test.js.snap │ │ ├── __tests__ │ │ │ ├── Main.test.js │ │ │ ├── __fixtures__ │ │ │ │ ├── pages.js │ │ │ │ └── plugins.js │ │ │ ├── __snapshots__ │ │ │ │ └── Main.test.js.snap │ │ │ ├── pages.test.js │ │ │ └── utils.test.js │ │ ├── constants.js │ │ ├── customizationContext.js │ │ ├── pages.js │ │ ├── routing.js │ │ └── utils.js │ ├── maintenance │ │ ├── FactoryReset.js │ │ ├── Maintenance.js │ │ ├── Reboot.js │ │ └── Syslog │ │ │ ├── Syslog.js │ │ │ ├── SyslogForm.js │ │ │ └── __tests__ │ │ │ ├── Syslog.test.js │ │ │ ├── __fixtures__ │ │ │ └── syslog.js │ │ │ └── __snapshots__ │ │ │ └── Syslog.test.js.snap │ ├── navigation │ │ ├── FaIcon.js │ │ ├── Navigation.js │ │ ├── NavigationItem.js │ │ ├── NavigationMainItem.js │ │ ├── NavigationToggle.js │ │ └── utils.js │ ├── notifications │ │ ├── NotificationIcon.js │ │ ├── Notifications │ │ │ ├── DismissAllButton.js │ │ │ ├── Notifications.css │ │ │ ├── Notifications.js │ │ │ ├── NotificationsList.js │ │ │ ├── TruncatedText.css │ │ │ └── TruncatedText.js │ │ ├── __tests__ │ │ │ ├── NotificationsCenter.test.js │ │ │ ├── NotificationsDropdown.test.js │ │ │ ├── TruncatedText.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── notifications.js │ │ │ ├── __snapshots__ │ │ │ │ ├── NotificationsCenter.test.js.snap │ │ │ │ ├── NotificationsDropdown.test.js.snap │ │ │ │ └── TruncatedText.test.js.snap │ │ │ └── hooks.test.js │ │ ├── constants.js │ │ ├── hooks.js │ │ └── utils.js │ ├── notificationsSettings │ │ ├── CommonForm.js │ │ ├── NotificationsEmailSettingsForm.js │ │ ├── NotificationsSettings.js │ │ ├── SMTPCustomForm.js │ │ ├── SMTPTurrisForm.js │ │ ├── TestNotification.js │ │ ├── __tests__ │ │ │ ├── NotificationSettings.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── notificationsSettings.js │ │ │ └── __snapshots__ │ │ │ │ └── NotificationSettings.test.js.snap │ │ ├── helpTexts.js │ │ └── validator.js │ ├── overview │ │ ├── Cards │ │ │ ├── AutomaticUpdatesCard.js │ │ │ ├── DataCollectionCard.js │ │ │ ├── DynamicFirewallCard.js │ │ │ ├── NetmetrCard.css │ │ │ ├── NetmetrCard.js │ │ │ ├── OpenVPNClientsCard.js │ │ │ └── __tests__ │ │ │ │ ├── Cards.test.js │ │ │ │ └── __snapshots__ │ │ │ │ └── Cards.test.js.snap │ │ ├── Overview.css │ │ ├── Overview.js │ │ ├── __tests__ │ │ │ ├── Overview.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── overview.js │ │ │ └── __snapshots__ │ │ │ │ └── Overview.test.js.snap │ │ └── utils.js │ ├── packageManagement │ │ ├── languages │ │ │ ├── Languages.js │ │ │ ├── LanguagesForm.js │ │ │ └── __tests__ │ │ │ │ ├── Languages.test.js │ │ │ │ ├── __fixtures__ │ │ │ │ └── languages.js │ │ │ │ └── __snapshots__ │ │ │ │ └── Languages.test.js.snap │ │ ├── packages │ │ │ ├── Labels │ │ │ │ ├── Label.css │ │ │ │ ├── Label.js │ │ │ │ └── Labels.js │ │ │ ├── Package.js │ │ │ ├── PackageCheckBox.js │ │ │ ├── Packages.js │ │ │ ├── PackagesForm.css │ │ │ ├── PackagesForm.js │ │ │ ├── UserOptions │ │ │ │ ├── UserOption.js │ │ │ │ └── UserOptions.js │ │ │ └── __tests__ │ │ │ │ ├── Packages.test.js │ │ │ │ ├── __fixtures__ │ │ │ │ └── packages.js │ │ │ │ └── __snapshots__ │ │ │ │ └── Packages.test.js.snap │ │ ├── updateSettings │ │ │ ├── LicenceModal.js │ │ │ ├── UpdateSettings.js │ │ │ ├── __tests__ │ │ │ │ ├── UpdateSettings.test.js │ │ │ │ ├── __fixtures__ │ │ │ │ │ └── updates.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── UpdateSettings.test.js.snap │ │ │ └── forms │ │ │ │ ├── RestartAfterUpdateForm.js │ │ │ │ ├── UpdateApprovalForm.js │ │ │ │ └── UpdatesForm.js │ │ ├── updates │ │ │ ├── UpdateApproval.js │ │ │ ├── UpdateChecker.js │ │ │ ├── UpdateManager.js │ │ │ ├── Updates.js │ │ │ ├── __tests__ │ │ │ │ ├── UpdateApproval.test.js │ │ │ │ ├── UpdateChecker.test.js │ │ │ │ ├── Updates.test.js │ │ │ │ ├── __fixtures__ │ │ │ │ │ └── updates.js │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── UpdateApproval.test.js.snap │ │ │ │ │ ├── UpdateChecker.test.js.snap │ │ │ │ │ └── Updates.test.js.snap │ │ │ └── hooks.js │ │ └── utils │ │ │ ├── DisableIfUpdaterIsDisabled.js │ │ │ └── DisabledUpdaterAlert.js │ ├── password │ │ ├── ForisPasswordForm.js │ │ ├── Password.js │ │ ├── RootPasswordForm.js │ │ └── __tests__ │ │ │ ├── Password.test.js │ │ │ └── __snapshots__ │ │ │ └── Password.test.js.snap │ ├── regionAndTime │ │ ├── RegionAndTime.js │ │ ├── RegionForm.js │ │ ├── TimeForm.css │ │ ├── TimeForm.js │ │ ├── __tests__ │ │ │ ├── RegionAndTime.test.js │ │ │ ├── __fixtures__ │ │ │ │ └── regionAndTime.js │ │ │ └── __snapshots__ │ │ │ │ └── RegionAndTime.test.js.snap │ │ └── hooks.js │ ├── routerStateHandler │ │ ├── NetworkRestartHandler.js │ │ ├── RebootHandler.js │ │ ├── RouterStateHandler.js │ │ ├── hooks.js │ │ └── utils.js │ ├── styles │ │ ├── content.css │ │ ├── dropdown.css │ │ ├── login.css │ │ ├── navigation.css │ │ └── sidebar.css │ ├── utils │ │ ├── ErrorBoundary.css │ │ ├── ErrorBoundary.js │ │ ├── __tests__ │ │ │ ├── ErrorBoundary.test.js │ │ │ └── __snapshots__ │ │ │ │ └── ErrorBoundary.test.js.snap │ │ ├── constants.js │ │ └── timezones.js │ ├── wan │ │ ├── MACForm.js │ │ ├── WAN.js │ │ ├── WAN6Form.js │ │ ├── WANForm.js │ │ └── __tests__ │ │ │ ├── WAN.test.js │ │ │ ├── __fixtures__ │ │ │ └── wanSettings.js │ │ │ └── __snapshots__ │ │ │ └── WAN.test.js.snap │ └── wifi │ │ ├── WiFi.js │ │ └── __tests__ │ │ ├── WiFi.test.js │ │ ├── __fixtures__ │ │ └── WiFiFixtures.js │ │ └── __snapshots__ │ │ └── WiFi.test.js.snap ├── styleguide.config.js └── webpack.config.js ├── pycodestyle ├── pylintrc ├── reforis ├── __init__.py ├── __main__.py ├── api │ └── __init__.py ├── auth.py ├── backend.py ├── cli.py ├── config │ ├── __init__.py │ ├── dev.py │ ├── prod.py │ └── test.py ├── foris_controller_api │ ├── __init__.py │ ├── modules │ │ ├── __init__.py │ │ ├── about.py │ │ ├── dns.py │ │ ├── guest.py │ │ ├── haas.py │ │ ├── lan.py │ │ ├── maintain.py │ │ ├── networks.py │ │ ├── password.py │ │ ├── router_notifications.py │ │ ├── system.py │ │ ├── time.py │ │ ├── updater.py │ │ ├── utils.py │ │ ├── wan.py │ │ ├── web.py │ │ └── wifi.py │ └── utils.py ├── guide.py ├── locale.py ├── plugins.py ├── sessions │ ├── LICENSE │ ├── __init__.py │ └── sessions.py ├── templates │ ├── base.html │ ├── errors │ │ ├── 400.html │ │ ├── 401.html │ │ ├── 404.html │ │ └── 500.html │ ├── guide.html │ ├── includes │ │ ├── locale.html │ │ ├── navigation.html │ │ ├── plugins.html │ │ └── top-bar.html │ ├── index.html │ └── login.html ├── test_utils │ ├── __init__.py │ ├── fixtures.py │ └── mocked_send.py ├── translations │ ├── cs │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── da │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── de │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── el │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── en │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── es │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── fi │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── fo │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── hr │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── hu │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── it │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── ja │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── ko │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── lt │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── messages.pot │ ├── nb_NO │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── nl │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── ro │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── sk │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── sv │ │ └── LC_MESSAGES │ │ │ └── messages.po │ └── zh_Hant │ │ └── LC_MESSAGES │ │ └── messages.po ├── utils.py └── views.py ├── reforis_static ├── __init__.py └── reforis │ ├── imgs │ ├── QR_icon.svg │ ├── favicon.ico │ ├── favicon.png │ ├── favicon.svg │ ├── logo-turris-white.svg │ ├── logo-turris.svg │ ├── workflow-bridge.svg │ ├── workflow-min.svg │ └── workflow-router.svg │ └── js │ └── babel.js ├── scripts └── make_timezones.py ├── setup.py └── tests ├── conftest.py ├── foris_controller_api ├── test_foris_controller_api.py ├── test_packages_and_languages.py ├── test_password.py └── test_updates.py ├── test_api.py ├── test_auth.py └── test_timeout.py /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: python:3.7-slim-stretch 2 | 3 | stages: 4 | - test 5 | 6 | before_script: 7 | - apt-get update && apt-get -y install sudo make curl git 8 | 9 | test: 10 | stage: test 11 | script: 12 | - make prepare-dev 13 | - make compile-messages 14 | - make test 15 | 16 | lint: 17 | stage: test 18 | script: 19 | - make prepare-dev 20 | - make lint 21 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/Bug.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | This is an example bug report. 3 | 4 | # Steps To Reproduce 5 | 1. foo 6 | 2. bar 7 | 3. baz 8 | 9 | # Expected Result 10 | Should work. 11 | 12 | # Actual Result 13 | Doesn't work. 14 | -------------------------------------------------------------------------------- /.weblate: -------------------------------------------------------------------------------- 1 | [weblate] 2 | url = https://hosted.weblate.org/api/ 3 | translation = turris/reforis 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include reforis/templates * 2 | recursive-include reforis_static/reforis * 3 | -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: reforis/**.py] 2 | [jinja2: reforis/templates/**.html] 3 | [javascript: js/src/**.js] 4 | extensions = jinja2.ext.autoescape,jinja2.ext.with_ 5 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/assets/reforis_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CZ-NIC/reforis/41a239b2dc95946c3eb777a8f90ecbe87cbe29d8/docs/assets/reforis_logo.png -------------------------------------------------------------------------------- /docs/assets/reforis_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CZ-NIC/reforis/41a239b2dc95946c3eb777a8f90ecbe87cbe29d8/docs/assets/reforis_screenshot.png -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API endpoints 2 | ############# 3 | 4 | See `API Blueprint `__ for more implementation details. 5 | 6 | .. autoflask:: reforis:create_app('test') 7 | :blueprints: ForisAPI 8 | -------------------------------------------------------------------------------- /docs/source/api_blueprint.rst: -------------------------------------------------------------------------------- 1 | API Utils 2 | ############# 3 | 4 | .. automodule:: foris_controller_api.utils 5 | :members: _foris_controller_settings_call, APIError 6 | -------------------------------------------------------------------------------- /docs/source/application_factory.rst: -------------------------------------------------------------------------------- 1 | Application Factory 2 | ################### 3 | 4 | .. automodule:: reforis 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/auth.rst: -------------------------------------------------------------------------------- 1 | Auth 2 | #### 3 | 4 | .. automodule:: auth 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/backend.rst: -------------------------------------------------------------------------------- 1 | Backend helpers 2 | ############### 3 | 4 | .. automodule:: backend 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/guide_blueprint.rst: -------------------------------------------------------------------------------- 1 | Guide Blueprint 2 | ############### 3 | 4 | .. automodule:: guide 5 | 6 | .. autoflask:: reforis:create_app('test') 7 | :blueprints: ForisGuide 8 | -------------------------------------------------------------------------------- /docs/source/import_mock.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | 4 | class Mock(MagicMock): 5 | @classmethod 6 | def __getattr__(cls, name): 7 | return MagicMock() 8 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | reForis's documentation 2 | ======================= 3 | Foris is a simplified web interface for Turris routers administration. Original Foris was redesigned 4 | and got a new name reForis (which means redesigned Foris). 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | application_factory 11 | backend 12 | auth 13 | locale 14 | views 15 | api_blueprint 16 | guide_blueprint 17 | api 18 | plugins 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/source/locale.rst: -------------------------------------------------------------------------------- 1 | Locale 2 | ###### 3 | 4 | .. automodule:: reforis.locale 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/plugins.rst: -------------------------------------------------------------------------------- 1 | Plugin system 2 | ############# 3 | 4 | .. automodule:: reforis.plugins 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/views.rst: -------------------------------------------------------------------------------- 1 | Views Blueprint (Pages) 2 | ####################### 3 | 4 | .. automodule:: views 5 | 6 | .. autoflask:: reforis:create_app('test') 7 | :blueprints: Foris 8 | -------------------------------------------------------------------------------- /js/.eslintignore: -------------------------------------------------------------------------------- 1 | **/__tests__/* 2 | src/testUtils/ 3 | -------------------------------------------------------------------------------- /js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-reforis", "prettier"], 3 | plugins: ["prettier"], 4 | rules: { 5 | "prettier/prettier": ["error"], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /js/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "printWidth": 80, 4 | "proseWrap": "always", 5 | "tabWidth": 4, 6 | "useTabs": false, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "semi": true 11 | } 12 | -------------------------------------------------------------------------------- /js/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@babel/preset-react"], 3 | plugins: ["@babel/plugin-transform-runtime"], 4 | }; 5 | -------------------------------------------------------------------------------- /js/docs/introduction.md: -------------------------------------------------------------------------------- 1 | ## reForis React application 2 | 3 | reForis application has set of main components, which represent singe pages such 4 | as ``, `` and others. 5 | 6 | The reForis forms components are created in order to be used inside 7 | `` component, which is part of Foris JS library. It component is 8 | Higher Order Component which encapsulates entire form logic and provided 9 | children required props. This component structure provides comfort API and 10 | allows to create typical foris forms easily. 11 | -------------------------------------------------------------------------------- /js/jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | // https://jestjs.io/docs/en/configuration.html 9 | module.exports = { 10 | moduleDirectories: ["node_modules", "/src/"], 11 | moduleNameMapper: { 12 | "\\.(css|less)$": "/src/__mocks__/styleMock.js", 13 | "^lodash-es$": "lodash", 14 | }, 15 | clearMocks: true, 16 | collectCoverageFrom: ["src/**/*.{js,jsx}"], 17 | coverageDirectory: "coverage", 18 | testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/"], 19 | transformIgnorePatterns: ["node_modules/(?!(foris)/)"], 20 | verbose: false, 21 | setupFilesAfterEnv: [ 22 | "@testing-library/react/cleanup-after-each", 23 | "foris/testUtils/setup", 24 | ], 25 | globals: { 26 | TZ: "utc", 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /js/src/__mocks__/axios.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import mockAxios from "jest-mock-axios"; 9 | 10 | export default mockAxios; 11 | -------------------------------------------------------------------------------- /js/src/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | module.exports = {}; 9 | -------------------------------------------------------------------------------- /js/src/about/__tests__/About.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import mockAxios from "jest-mock-axios"; 10 | import { render, wait } from "foris/testUtils/customTestRender"; 11 | import { mockJSONError } from "foris/testUtils/network"; 12 | 13 | import About from "../About"; 14 | 15 | describe("About", () => { 16 | it("should handle errors", async () => { 17 | const { getByText } = render(); 18 | expect(mockAxios.get).toBeCalledWith( 19 | "/reforis/api/about", 20 | expect.anything() 21 | ); 22 | mockJSONError(); 23 | await wait(() => getByText("An error occurred while fetching data.")); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /js/src/app.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-line 2 | /* 3 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 4 | * 5 | * This is free software, licensed under the GNU General Public License v3. 6 | * See /LICENSE for more information. 7 | */ 8 | 9 | // It force ReactRouterDOM to be exposed. See: 10 | // https://github.com/webpack-contrib/expose-loader/issues/20. 11 | // eslint-disable-next-line 12 | import "expose-loader?ReactRouterDOM!react-router-dom"; 13 | 14 | // eslint-disable-next-line 15 | import pdfMake from "expose-loader?pdfMake!pdfmake/build/pdfmake"; 16 | // eslint-disable-next-line 17 | import pdfFonts from "pdfmake/build/vfs_fonts"; 18 | 19 | import React from "react"; 20 | import { render } from "react-dom"; 21 | import "@fortawesome/fontawesome-free/js/all.min"; 22 | import "bootstrap/dist/js/bootstrap.min"; 23 | import "bootswatch/dist/flatly/bootstrap.css"; 24 | import "./app.css"; 25 | 26 | import { WebSockets } from "foris"; 27 | import RouterStateHandler from "routerStateHandler/RouterStateHandler"; 28 | 29 | import Main from "main/Main"; 30 | import Guide from "guide/Guide"; 31 | 32 | pdfMake.vfs = pdfFonts.pdfMake.vfs; 33 | 34 | const ws = new WebSockets(); 35 | 36 | window.AlertContext = React.createContext(); 37 | 38 | window.addEventListener( 39 | "load", 40 | () => { 41 | const guideContainer = document.getElementById("guide-container"); 42 | const mainContainer = document.getElementById("app-container"); 43 | if (guideContainer) { 44 | render(, guideContainer); 45 | } else if (mainContainer) { 46 | render(
, mainContainer); 47 | } 48 | 49 | const routerStateHandlerContainer = document.getElementById( 50 | "router-state-handler" 51 | ); 52 | if (routerStateHandlerContainer) { 53 | render(, routerStateHandlerContainer); 54 | } 55 | }, 56 | false 57 | ); 58 | -------------------------------------------------------------------------------- /js/src/common/ScrollToTopArrow.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | .scrollToTop { 9 | position: fixed; 10 | bottom: 20px; 11 | right: 8px; 12 | font-size: 2.5em; 13 | line-height: 0; 14 | align-items: flex-end; 15 | justify-content: center; 16 | z-index: 1000; 17 | cursor: pointer; 18 | animation: fadeIn 0.3s; 19 | transition: opacity 0.4s; 20 | opacity: 0.5; 21 | width: 40px; 22 | background-color: black; 23 | color: white; 24 | border-radius: 3px; 25 | } 26 | 27 | .scrollToTop:hover { 28 | opacity: 1; 29 | } 30 | 31 | @keyframes fadeIn { 32 | 0% { 33 | opacity: 0; 34 | } 35 | 100% { 36 | opacity: 0.5; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /js/src/common/ScrollToTopArrow.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React, { useState } from "react"; 9 | import "./ScrollToTopArrow.css"; 10 | 11 | const ScrollToTopArrow = () => { 12 | const [showScrollArrow, setShowScrollArrow] = useState(false); 13 | 14 | const checkScrollToTop = () => { 15 | if (!showScrollArrow && window.pageYOffset > 400) { 16 | setShowScrollArrow(true); 17 | } else if (showScrollArrow && window.pageYOffset <= 400) { 18 | setShowScrollArrow(false); 19 | } 20 | }; 21 | 22 | const scrollToTop = () => { 23 | window.scrollTo({ top: 0, behavior: "smooth" }); 24 | }; 25 | 26 | window.addEventListener("scroll", checkScrollToTop); 27 | 28 | return ( 29 |
40 | 41 |
42 | ); 43 | }; 44 | 45 | export default ScrollToTopArrow; 46 | -------------------------------------------------------------------------------- /js/src/common/network/DHCPClientForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { TextInput, validateDomain } from "foris"; 12 | 13 | const HELP_TEXTS = { 14 | hostname: _("Hostname which will be provided to DHCP server."), 15 | }; 16 | 17 | DHCPClientForm.propTypes = { 18 | formData: PropTypes.shape({ 19 | hostname: PropTypes.string, 20 | }).isRequired, 21 | formErrors: PropTypes.shape({ 22 | hostname: PropTypes.string, 23 | }), 24 | setFormValue: PropTypes.func.isRequired, 25 | updateRule: PropTypes.func.isRequired, 26 | disabled: PropTypes.bool, 27 | }; 28 | 29 | export default function DHCPClientForm({ 30 | formData, 31 | formErrors, 32 | setFormValue, 33 | updateRule, 34 | disabled, 35 | }) { 36 | return ( 37 | 43 | updateRule({ hostname: { $set: value } }) 44 | )} 45 | disabled={disabled} 46 | /> 47 | ); 48 | } 49 | 50 | export function validateDHCPForm(formData) { 51 | const error = { hostname: validateDomain(formData.hostname) }; 52 | return error.hostname ? error : null; 53 | } 54 | -------------------------------------------------------------------------------- /js/src/common/network/__tests__/DHCPClientsList.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { render } from "foris/testUtils/customTestRender"; 10 | 11 | import DHCPClientsList from "../DHCPClientsList"; 12 | import { clients } from "./__fixtures__/DHCPClientsList"; 13 | 14 | describe("", () => { 15 | it("Test with snapshot.", () => { 16 | const { container } = render(); 17 | expect(container).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /js/src/common/network/__tests__/__fixtures__/DHCPClientsList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const clients = [ 9 | { 10 | ip: "192.168.1.1", 11 | mac: "11:22:33:44:55:66", 12 | expires: 1539350186, 13 | active: true, 14 | hostname: "first", 15 | }, 16 | { 17 | ip: "192.168.2.1", 18 | mac: "99:88:77:66:55:44", 19 | expires: 1539350188, 20 | active: false, 21 | hostname: "*", 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /js/src/common/network/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import ipaddr from "ipaddr"; 9 | 10 | /* 11 | * Add a number of addresses to start one. 12 | * - start - address object (parsed by ipaddr) 13 | * - increment - number of addresses to add 14 | */ 15 | export function addToAddress(start, increment) { 16 | const octets = start.toByteArray(); 17 | let remainder = increment; 18 | // Increase value of octets - in backwards order 19 | for (let i = 3; i > 0; i--) { 20 | const increasedValue = octets[i] + remainder; 21 | if (increasedValue > 255) { 22 | octets[i] = increasedValue % 256; 23 | // Value for next octets 24 | remainder = Math.floor(increasedValue / 256); 25 | } else { 26 | octets[i] += remainder; 27 | remainder = 0; 28 | } 29 | } 30 | return ipaddr.fromByteArray(octets); 31 | } 32 | 33 | /* 34 | * Return first address allocated by DHCP (as an object). 35 | * - routerIPString - address as a string 36 | * - DHCPStart - last octet (if less than 256) or number of addresses away from routerIP 37 | */ 38 | export function getDHCPStart(routerIPString, DHCPStart) { 39 | const routerIP = ipaddr.parse(routerIPString); 40 | let DHCPStartAddress; 41 | if (DHCPStart > 255) { 42 | DHCPStartAddress = addToAddress(routerIP, DHCPStart - 1); 43 | } else { 44 | // Make a copy of router's IP address and change last octet 45 | DHCPStartAddress = ipaddr.parse(routerIP.toString()); 46 | DHCPStartAddress.octets[3] = DHCPStart; 47 | } 48 | return DHCPStartAddress; 49 | } 50 | -------------------------------------------------------------------------------- /js/src/common/network/validators.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import ipaddr from "ipaddr"; 9 | 10 | export function validateNetworkMask(networkMask) { 11 | if (ipaddr.IPv4.parse(networkMask).prefixLengthFromSubnetMask() === null) { 12 | return _("This is not a valid network mask."); 13 | } 14 | return undefined; 15 | } 16 | 17 | export function validateRequiredField(value) { 18 | if ([null, undefined, ""].includes(value)) { 19 | return _("This field is required."); 20 | } 21 | return undefined; 22 | } 23 | -------------------------------------------------------------------------------- /js/src/connectionTest/ConnectionTest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import useConnectionTest from "./hooks"; 12 | import ConnectionTestResults from "./ConnectionTestResult"; 13 | import ConnectionTestButton from "./ConnectionTestButton"; 14 | 15 | ConnectionTest.propTypes = { 16 | ws: PropTypes.object.isRequired, 17 | type: PropTypes.oneOf(["wan", "dns", "overview"]).isRequired, 18 | }; 19 | 20 | export default function ConnectionTest({ ws, type }) { 21 | const [state, testResults, triggerTest] = useConnectionTest(ws, type); 22 | 23 | function onSubmit(e) { 24 | e.preventDefault(); 25 | triggerTest(); 26 | } 27 | 28 | const insideCard = type === "overview" ? "" : "card p-4 mb-3"; 29 | 30 | return ( 31 |
32 |
33 | 34 |
35 | 40 |
41 | 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /js/src/connectionTest/ConnectionTestButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { Button } from "foris"; 12 | import { TEST_STATES } from "./hooks"; 13 | 14 | ConnectionTestButton.propTypes = { 15 | state: PropTypes.oneOf( 16 | Object.keys(TEST_STATES).map((key) => TEST_STATES[key]) 17 | ).isRequired, 18 | type: PropTypes.oneOf(["wan", "dns", "overview"]).isRequired, 19 | }; 20 | 21 | export default function ConnectionTestButton({ state, type, ...props }) { 22 | const isRunning = state === TEST_STATES.RUNNING; 23 | let labelSubmitButton; 24 | switch (state) { 25 | case TEST_STATES.RUNNING: 26 | labelSubmitButton = _("Test is running..."); 27 | break; 28 | case TEST_STATES.FINISHED: 29 | labelSubmitButton = _("Test connection again"); 30 | break; 31 | default: 32 | labelSubmitButton = _("Test connection"); 33 | } 34 | 35 | return ( 36 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /js/src/connectionTest/__tests__/__fixtures__/testResults.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export default function wsTestResultMessage(testId, type) { 9 | return { 10 | module: "wan", 11 | action: "connection_test_finished", 12 | data: { 13 | passed: true, 14 | test_id: testId, 15 | data: testResults(testId, type), 16 | }, 17 | }; 18 | } 19 | 20 | function testResults(testId, type) { 21 | const dnsResults = type.startsWith("dns"); 22 | const ipResults = !dnsResults; 23 | return { 24 | ipv6: ipResults, 25 | ipv6_gateway: ipResults, 26 | ipv4: ipResults, 27 | ipv4_gateway: ipResults, 28 | dns: dnsResults, 29 | dnssec: dnsResults, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /js/src/dns/DNSSECDisableModal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { Modal, ModalBody, ModalFooter, ModalHeader, Button } from "foris"; 12 | 13 | const DNSSEC_DISABLE_MESSAGE = _(` 14 | DNSSEC is a security technology that protects the DNS communication against attacks on the DNS infrastructure. 15 | We strongly recommend keeping DNSSEC validation enabled unless you know that you will be connecting your device in the 16 | network where DNSSEC is broken. 17 | 18 | Do you still want to continue and stay unprotected? 19 | `); 20 | 21 | DNSSECDisableModal.propTypes = { 22 | shown: PropTypes.bool.isRequired, 23 | setShown: PropTypes.func.isRequired, 24 | callback: PropTypes.func.isRequired, 25 | }; 26 | 27 | export default function DNSSECDisableModal({ shown, setShown, callback }) { 28 | return ( 29 | 30 | 31 | 32 |

{DNSSEC_DISABLE_MESSAGE}

33 |
34 | 35 | 43 | 52 | 53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /js/src/dns/Forwarders/Forwarder/ForwarderModal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React, { useCallback } from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { Modal, ModalBody, ModalHeader } from "foris"; 12 | import ForwarderForm from "./ForwarderForm"; 13 | 14 | ForwarderModal.propTypes = { 15 | forwarder: PropTypes.object, 16 | shown: PropTypes.bool, 17 | setShown: PropTypes.func, 18 | title: PropTypes.string, 19 | }; 20 | 21 | export default function ForwarderModal({ shown, setShown, forwarder, title }) { 22 | const postCallback = useCallback(() => { 23 | setShown(false); 24 | }, [setShown]); 25 | 26 | return ( 27 | 28 | 29 | 30 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /js/src/dns/Forwarders/propTypes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import PropTypes from "prop-types"; 9 | 10 | const forwarderPropTypes = PropTypes.shape({ 11 | name: PropTypes.string.isRequired, 12 | description: PropTypes.string.isRequired, 13 | ipaddresses: PropTypes.shape({ 14 | ipv4: PropTypes.arrayOf(PropTypes.string), 15 | ipv6: PropTypes.arrayOf(PropTypes.string), 16 | }), 17 | tls_type: PropTypes.oneOf(["no", "hostname", "pin"]), 18 | tls_hostname: PropTypes.string, 19 | tls_pin: PropTypes.string, 20 | }); 21 | 22 | export default forwarderPropTypes; 23 | -------------------------------------------------------------------------------- /js/src/dns/__tests__/Forwarders.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import mockAxios from "jest-mock-axios"; 10 | import { render, wait } from "foris/testUtils/customTestRender"; 11 | import { mockJSONError } from "foris/testUtils/network"; 12 | 13 | import Forwarders from "../Forwarders/Forwarders"; 14 | 15 | describe("Forwarders", () => { 16 | it("should handle errors", async () => { 17 | const { getByText } = render(); 18 | expect(mockAxios.get).toBeCalledWith( 19 | "/reforis/api/dns/forwarders", 20 | expect.anything() 21 | ); 22 | mockJSONError(); 23 | await wait(() => getByText("An error occurred while fetching data.")); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /js/src/dns/__tests__/__fixtures__/dns.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const dnsFixture = { 9 | available_forwarders: [ 10 | { 11 | description: "", 12 | name: "", 13 | }, 14 | { 15 | description: "Cloudflare (TLS)", 16 | name: "99_cloudflare", 17 | }, 18 | { 19 | description: "Quad9 (TLS)", 20 | name: "99_quad9", 21 | }, 22 | { 23 | description: "CZ.NIC (TLS)", 24 | name: "00_odvr-cznic", 25 | }, 26 | { 27 | description: "Google", 28 | name: "99_google", 29 | }, 30 | ], 31 | dns_from_dhcp_domain: "lan", 32 | dns_from_dhcp_enabled: false, 33 | dnssec_enabled: true, 34 | forwarder: "", 35 | forwarding_enabled: false, 36 | }; 37 | -------------------------------------------------------------------------------- /js/src/dns/__tests__/__fixtures__/forwarders.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const forwardersFixture = { 9 | forwarders: [ 10 | { 11 | description: "Google", 12 | editable: false, 13 | ipaddresses: { 14 | ipv4: ["8.8.8.8"], 15 | ipv6: ["2001:4860:4860::8888"], 16 | }, 17 | name: "99_google", 18 | tls_hostname: "", 19 | tls_pin: "", 20 | tls_type: "no", 21 | }, 22 | { 23 | description: "Custom forwarder", 24 | editable: true, 25 | ipaddresses: { 26 | ipv4: ["1.2.3.4"], 27 | }, 28 | name: "custom_fwd", 29 | tls_hostname: "dns.custom.net", 30 | tls_type: "hostname", 31 | }, 32 | { 33 | description: "CZ.NIC (TLS)", 34 | editable: false, 35 | ipaddresses: { 36 | ipv4: ["193.17.47.1"], 37 | ipv6: ["2001:148f:ffff::1"], 38 | }, 39 | name: "00_odvr-cznic", 40 | tls_hostname: "odvr.nic.cz", 41 | tls_pin: "", 42 | tls_type: "hostname", 43 | }, 44 | { 45 | description: "Cloudflare (TLS)", 46 | editable: false, 47 | ipaddresses: { 48 | ipv4: ["1.1.1.1"], 49 | ipv6: ["2606:4700:4700::1111"], 50 | }, 51 | name: "99_cloudflare", 52 | tls_hostname: "cloudflare-dns.com", 53 | tls_pin: "", 54 | tls_type: "hostname", 55 | }, 56 | ], 57 | }; 58 | -------------------------------------------------------------------------------- /js/src/dns/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | const PROVIDER_FORWARDER = "provider_forwarder"; 9 | 10 | export default PROVIDER_FORWARDER; 11 | -------------------------------------------------------------------------------- /js/src/docsUtils/setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | // Fake babel (gettext) used for docs 9 | global._ = (str) => str; 10 | global.babel = { format: (str) => str }; 11 | global.ForisTranslations = {}; 12 | -------------------------------------------------------------------------------- /js/src/guestNetwork/GuestNetworkDHCPClientsList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import ReactDOM from "react-dom"; 11 | 12 | import DHCPClientsList from "common/network/DHCPClientsList"; 13 | 14 | GuestNetworkDHCPClientsList.propTypes = { 15 | formData: PropTypes.shape({ 16 | dhcp: PropTypes.shape({ 17 | enabled: PropTypes.bool.isRequired, 18 | clients: PropTypes.arrayOf(PropTypes.object).isRequired, 19 | }).isRequired, 20 | }), 21 | }; 22 | 23 | export default function GuestNetworkDHCPClientsList({ formData }) { 24 | if (!formData.enabled || !formData.dhcp.enabled) return null; 25 | 26 | const container = document.getElementById("dhcp-clients-container"); 27 | return ReactDOM.createPortal( 28 | , 29 | container 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /js/src/guestNetwork/GuestNetworkNotification.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import { Portal, Alert, ALERT_TYPES } from "foris"; 11 | 12 | export const NO_INTERFACE_WARNING = _( 13 | "This network currently doesn't contain any devices. The changes you make here will become fully functional after you assign a network interface to this network." 14 | ); 15 | 16 | export const NO_INTERFACE_UP_WARNING = _( 17 | "All network interfaces of this network are currently down." 18 | ); 19 | 20 | GuestNetworkNotification.propTypes = { 21 | formData: PropTypes.shape({ 22 | interface_count: PropTypes.number, 23 | interface_up_count: PropTypes.number, 24 | enabled: PropTypes.bool, 25 | }).isRequired, 26 | }; 27 | 28 | GuestNetworkNotification.defaultProps = { 29 | formData: {}, 30 | }; 31 | 32 | export default function GuestNetworkNotification({ formData }) { 33 | return ( 34 | <> 35 | {formData.enabled && ( 36 | 37 | {formData.interface_count === 0 ? ( 38 | 39 | {NO_INTERFACE_WARNING} 40 | 41 | ) : ( 42 | formData.interface_up_count === 0 && ( 43 | 44 | {NO_INTERFACE_UP_WARNING} 45 | 46 | ) 47 | )} 48 | 49 | )} 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /js/src/guide/Guide.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React, { useEffect } from "react"; 9 | import PropTypes from "prop-types"; 10 | import { useAPIGet } from "foris"; 11 | 12 | import API_URLs from "common/API"; 13 | 14 | import "./Guide.css"; 15 | import "styles/dropdown.css"; 16 | import GuideRouterWithErrorAndSpinner from "./GuideRouter"; 17 | 18 | Guide.propTypes = { 19 | ws: PropTypes.object.isRequired, 20 | }; 21 | 22 | export default function Guide({ ws }) { 23 | const [guideData, getGuideData] = useAPIGet(API_URLs.guide); 24 | const [getCustomizationResponse, getCustomization] = useAPIGet( 25 | API_URLs.about 26 | ); 27 | useEffect(() => { 28 | getGuideData(); 29 | getCustomization(); 30 | }, [getCustomization, getGuideData]); 31 | 32 | return ( 33 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /js/src/guide/GuideControls/GuideControls.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | .guide-controls { 9 | display: flex; 10 | flex-direction: row; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .guide-controls-button { 16 | display: flex; 17 | flex-direction: row; 18 | align-items: center; 19 | justify-content: center; 20 | margin-right: 1rem; 21 | } 22 | 23 | .guide-controls-button:last-child { 24 | margin-right: 0; 25 | } 26 | 27 | .dropdown:hover .dropdown-menu { 28 | transform: scale(1, 1) translateY(0); 29 | opacity: 1; 30 | visibility: visible; 31 | } 32 | -------------------------------------------------------------------------------- /js/src/guide/GuideControls/GuideControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (https://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import "./GuideControls.css"; 12 | import SkipGuideButtonWithRouter from "./SkipGuideButton"; 13 | import NextStepButtonWithRouter from "./NextStepButton"; 14 | import LanguagesDropdown from "../../main/TopBar/languagesDropdown/LanguagesDropdown"; 15 | 16 | GuideControls.propTypes = { 17 | ws: PropTypes.object.isRequired, 18 | next_step: PropTypes.string.isRequired, 19 | }; 20 | 21 | export default function GuideControls({ ws, next_step }) { 22 | return ( 23 |
24 | 28 | 29 | 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /js/src/guide/GuideControls/NextStepButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { Link, withRouter } from "react-router-dom"; 10 | import PropTypes from "prop-types"; 11 | 12 | NextStepButton.propTypes = { 13 | next_step: PropTypes.string.isRequired, 14 | location: PropTypes.object.isRequired, 15 | }; 16 | 17 | export function NextStepButton({ next_step, location }) { 18 | const disabled = location.pathname === `/${next_step}`; 19 | return ( 20 | 27 | 28 | {_("Next step")} 29 |   30 | 31 | 32 | 33 | ); 34 | } 35 | 36 | const NextStepButtonWithRouter = withRouter(NextStepButton); 37 | 38 | export default NextStepButtonWithRouter; 39 | -------------------------------------------------------------------------------- /js/src/guide/GuideControls/SkipGuideButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (https://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { withRouter } from "react-router-dom"; 10 | import { Button } from "foris"; 11 | import PropTypes from "prop-types"; 12 | 13 | import useGuideFinish from "../hooks"; 14 | 15 | SkipGuideButton.propTypes = { 16 | next_step: PropTypes.string.isRequired, 17 | }; 18 | 19 | export function SkipGuideButton({ next_step }) { 20 | const onGuideFinishHandler = useGuideFinish(); 21 | const disabled = next_step === "password"; 22 | return ( 23 | 37 | ); 38 | } 39 | 40 | const SkipGuideButtonWithRouter = withRouter(SkipGuideButton); 41 | 42 | export default SkipGuideButtonWithRouter; 43 | -------------------------------------------------------------------------------- /js/src/guide/GuideNavigation/GuideNavigation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import STEPS from "../steps"; 12 | import GuideNavigationItem from "./GuideNavigationItem"; 13 | 14 | GuideNavigation.propTypes = { 15 | workflow_steps: PropTypes.arrayOf(PropTypes.string).isRequired, 16 | passed: PropTypes.arrayOf(PropTypes.string).isRequired, 17 | next_step: PropTypes.string.isRequired, 18 | }; 19 | 20 | export default function GuideNavigation({ workflow_steps, passed, next_step }) { 21 | const navigationItems = workflow_steps.map((step) => ( 22 | 29 | )); 30 | 31 | return ( 32 | <> 33 |
    {navigationItems}
34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /js/src/guide/GuideNavigation/GuideNavigationItem.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { NavLink } from "react-router-dom"; 10 | import PropTypes from "prop-types"; 11 | 12 | GuideNavigationItem.propTypes = { 13 | name: PropTypes.string.isRequired, 14 | url: PropTypes.string.isRequired, 15 | passed: PropTypes.bool.isRequired, 16 | next: PropTypes.bool.isRequired, 17 | }; 18 | 19 | export default function GuideNavigationItem({ name, url, next, passed }) { 20 | const passedClassName = passed ? "passed" : ""; 21 | const nextClassName = next ? "next" : ""; 22 | 23 | const content = ( 24 | <> 25 | 26 | {name} 27 | 28 | ); 29 | 30 | return ( 31 |
  • 32 | {passed || next ? ( 33 | 37 | {content} 38 | 39 | ) : ( 40 | // eslint-disable-next-line jsx-a11y/anchor-is-valid 41 | {content} 42 | )} 43 |
  • 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /js/src/guide/GuidePage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React, { useEffect } from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { useAlert } from "foris"; 12 | 13 | import STEPS from "./steps"; 14 | import GuideHelper from "./GuideHelper"; 15 | 16 | GuidePage.propTypes = { 17 | ws: PropTypes.object.isRequired, 18 | step: PropTypes.string.isRequired, 19 | next_step: PropTypes.string.isRequired, 20 | passed: PropTypes.arrayOf(PropTypes.string).isRequired, 21 | current_workflow: PropTypes.string.isRequired, 22 | available_workflows: PropTypes.arrayOf(PropTypes.string).isRequired, 23 | getGuideData: PropTypes.func.isRequired, 24 | }; 25 | 26 | export default function GuidePage({ 27 | ws, 28 | step, 29 | next_step, 30 | current_workflow, 31 | available_workflows, 32 | passed, 33 | getGuideData, 34 | }) { 35 | const Component = STEPS[step].component; 36 | const [, dismissAlert] = useAlert(); 37 | useEffect(() => { 38 | dismissAlert(); 39 | }, [dismissAlert]); 40 | return ( 41 | <> 42 | 49 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /js/src/guide/GuidePages/GuideFinish.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | import { Button, buttonFormFieldsSize } from "foris"; 11 | 12 | import useGuideFinish from "../hooks"; 13 | 14 | export default function GuideFinished() { 15 | const onGuideFinishHandler = useGuideFinish(); 16 | return ( 17 | <> 18 |

    {_("Guide Finished")}

    19 |

    20 | {_( 21 | "Once you leave this guide you'll be granted access to the full configuration interface of this device." 22 | )} 23 |

    24 |

    25 | {_( 26 | "To further improve your security consider installing data collection plugin (via Package Management). This will allow you to be part of our security research to discover new attackers and give you access to dynamic firewall updates blocking known attackers." 27 | )} 28 |

    29 |
    30 | 33 |
    34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/Guide.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { 10 | render, 11 | wait, 12 | getByText, 13 | getAllByText, 14 | } from "foris/testUtils/customTestRender"; 15 | import { WebSockets } from "foris"; 16 | import { mockJSONError } from "foris/testUtils/network"; 17 | import mockAxios from "jest-mock-axios"; 18 | import { interfacesFixture } from "interfaces/__tests__/__fixtures__/interfaces"; 19 | 20 | import Guide from "../Guide"; 21 | import { guideFixtures } from "./__fixtures__/guide"; 22 | 23 | describe(" ", () => { 24 | let guideContainer; 25 | 26 | beforeEach(async () => { 27 | const webSockets = new WebSockets(); 28 | const { container } = render(); 29 | 30 | mockAxios.mockResponse({ data: guideFixtures }); 31 | mockAxios.mockResponse({}); 32 | await wait(() => getByText(container, "Network Interfaces")); 33 | 34 | mockAxios.mockResponse({ data: "en" }); 35 | mockAxios.mockResponse({ data: ["en", "cs", "ru"] }); 36 | await wait(() => getAllByText(container, "en")); 37 | 38 | mockAxios.mockResponse({ data: interfacesFixture() }); 39 | await wait(() => getByText(container, "LAN1")); 40 | guideContainer = container; 41 | }); 42 | 43 | it("Snapshot. Just check if render correct page.", () => { 44 | expect(guideContainer).toMatchSnapshot(); 45 | }); 46 | 47 | it("Should handle error.", async () => { 48 | const webSockets = new WebSockets(); 49 | const { container } = render(); 50 | mockJSONError(); 51 | mockJSONError(); 52 | await wait(() => 53 | getByText(container, "An error occurred while fetching data.") 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/GuideFinish.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { 10 | fireEvent, 11 | getByText, 12 | render, 13 | wait, 14 | } from "foris/testUtils/customTestRender"; 15 | import { mockJSONError } from "foris/testUtils/network"; 16 | import { mockSetAlert } from "foris/testUtils/alertContextMock"; 17 | 18 | import mockAxios from "jest-mock-axios"; 19 | import GuideFinish from "../GuidePages/GuideFinish"; 20 | 21 | describe(" and useGuideFinish hook", () => { 22 | let guideFinishContainer; 23 | 24 | beforeEach(async () => { 25 | const { container } = render(); 26 | guideFinishContainer = container; 27 | }); 28 | 29 | it("Snapshot.", () => { 30 | expect(guideFinishContainer).toMatchSnapshot(); 31 | }); 32 | 33 | it("useGuideFinish hook", () => { 34 | fireEvent.click(getByText(guideFinishContainer, "Continue")); 35 | expect(mockAxios.post).toHaveBeenCalledWith( 36 | "/reforis/api/finish-guide", 37 | undefined, 38 | expect.anything() 39 | ); 40 | }); 41 | 42 | it("handle POST error", async () => { 43 | fireEvent.click(getByText(guideFinishContainer, "Continue")); 44 | mockJSONError(); 45 | await wait(() => 46 | expect(mockSetAlert).toBeCalledWith( 47 | "Cannot mark guide as finished." 48 | ) 49 | ); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/GuideNavigation.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { getByText, render, wait } from "foris/testUtils/customTestRender"; 10 | 11 | import GuideNavigation from "../GuideNavigation/GuideNavigation"; 12 | import { guideFixtures } from "./__fixtures__/guide"; 13 | 14 | describe("", () => { 15 | let guideNavigationContainer; 16 | 17 | beforeEach(async () => { 18 | const { container } = render(); 19 | await wait(() => getByText(container, /Password/)); 20 | guideNavigationContainer = container; 21 | }); 22 | 23 | it("Snapshot.", () => { 24 | expect(guideNavigationContainer).toMatchSnapshot(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/__fixtures__/guide.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const guideFixtures = { 9 | available_workflows: ["router", "min", "bridge"], 10 | current_workflow: "router", 11 | enabled: true, 12 | next_step: "networks", 13 | passed: ["password", "profile"], 14 | recommended_workflow: "router", 15 | workflow: "router", 16 | workflow_steps: [ 17 | "password", 18 | "profile", 19 | "networks", 20 | "wan", 21 | "time", 22 | "dns", 23 | "updater", 24 | "finished", 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/__fixtures__/workflowSelect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const workflowFixture = ["router", "min", "bridge"]; 9 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/__snapshots__/GuideFinish.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` and useGuideFinish hook Snapshot. 1`] = ` 4 |
    5 |

    6 | Guide Finished 7 |

    8 |

    9 | Once you leave this guide you'll be granted access to the full configuration interface of this device. 10 |

    11 |

    12 | To further improve your security consider installing data collection plugin (via Package Management). This will allow you to be part of our security research to discover new attackers and give you access to dynamic firewall updates blocking known attackers. 13 |

    14 |
    17 | 23 |
    24 |
    25 | `; 26 | -------------------------------------------------------------------------------- /js/src/guide/__tests__/__snapshots__/GuideNavigation.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` Snapshot. 1`] = ` 4 |
    5 | 92 |
    93 | `; 94 | -------------------------------------------------------------------------------- /js/src/guide/hooks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import { useEffect } from "react"; 9 | 10 | import { useAPIPost, REFORIS_URL_PREFIX, API_STATE, useAlert } from "foris"; 11 | import API_URLs from "common/API"; 12 | 13 | export default function useGuideFinish() { 14 | const [finishGuidePostData, finishGuidePost] = useAPIPost( 15 | API_URLs.finishGuide 16 | ); 17 | const [setAlert] = useAlert(); 18 | 19 | useEffect(() => { 20 | if (finishGuidePostData.state === API_STATE.SUCCESS) { 21 | window.location.assign(`${REFORIS_URL_PREFIX}/`); 22 | } else if (finishGuidePostData.state === API_STATE.ERROR) { 23 | setAlert(_("Cannot mark guide as finished.")); 24 | } 25 | }, [finishGuidePostData, setAlert]); 26 | 27 | function onGuideFinishHandler(e) { 28 | e.persist(); 29 | finishGuidePost(); 30 | } 31 | 32 | return onGuideFinishHandler; 33 | } 34 | -------------------------------------------------------------------------------- /js/src/guide/steps.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import Password from "../password/Password"; 9 | import WorkflowSelect from "./GuidePages/WorkflowSelect"; 10 | import Interfaces from "../interfaces/Interfaces"; 11 | import RegionAndTime from "../regionAndTime/RegionAndTime"; 12 | import DNS from "../dns/DNS"; 13 | import Updates from "../packageManagement/updateSettings/UpdateSettings"; 14 | import WAN from "../wan/WAN"; 15 | import LAN from "../lan/LAN"; 16 | import GuideFinished from "./GuidePages/GuideFinish"; 17 | 18 | const STEPS = { 19 | password: { 20 | name: _("Password"), 21 | component: Password, 22 | }, 23 | profile: { 24 | name: _("Workflow"), 25 | component: WorkflowSelect, 26 | }, 27 | networks: { 28 | name: _("Interfaces"), 29 | component: Interfaces, 30 | }, 31 | time: { 32 | name: _("Time"), 33 | component: RegionAndTime, 34 | }, 35 | dns: { 36 | name: _("DNS"), 37 | component: DNS, 38 | }, 39 | updater: { 40 | name: _("Updates"), 41 | component: Updates, 42 | }, 43 | wan: { 44 | name: _("WAN"), 45 | component: WAN, 46 | }, 47 | lan: { 48 | name: _("LAN"), 49 | component: LAN, 50 | }, 51 | finished: { 52 | name: _("Finish"), 53 | component: GuideFinished, 54 | }, 55 | }; 56 | 57 | export default STEPS; 58 | -------------------------------------------------------------------------------- /js/src/hostname/Hostname.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import { ForisForm } from "foris"; 11 | import API_URLs from "../common/API"; 12 | import HostnameForm from "./HostnameForm"; 13 | 14 | Hostname.propTypes = { 15 | ws: PropTypes.object.isRequired, 16 | }; 17 | 18 | export default function Hostname({ ws }) { 19 | return ( 20 | <> 21 |

    {_("Hostname")}

    22 |

    {_("Here you can check and set the name of your device.")}

    23 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | function validator(formData) { 38 | const errors = {}; 39 | 40 | if (!/^[A-Za-z-_0-9]{1,63}$/.test(formData.hostname)) { 41 | errors.hostname = _( 42 | "The hostname can contain only alphanumeric characters, hyphens, underscores and can't be empty." 43 | ); 44 | } 45 | return errors.hostname ? errors : undefined; 46 | } 47 | -------------------------------------------------------------------------------- /js/src/hostname/HostnameForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import { TextInput } from "foris"; 11 | 12 | const HELP_TEXT = { 13 | device_hostname: _("Rewrite the hostname to set a new one."), 14 | }; 15 | 16 | HostnameForm.defaultProps = { 17 | formErrors: {}, 18 | }; 19 | 20 | HostnameForm.propTypes = { 21 | formData: PropTypes.shape({ 22 | hostname: PropTypes.string.isRequired, 23 | }), 24 | formErrors: PropTypes.shape({ 25 | hostname: PropTypes.string, 26 | }), 27 | setFormValue: PropTypes.func, 28 | }; 29 | 30 | export default function HostnameForm({ formData, setFormValue, formErrors }) { 31 | return ( 32 | <> 33 |

    {_("Hostname Settings")}

    34 | ({ 40 | hostname: { $set: value }, 41 | }))} 42 | /> 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /js/src/hostname/__tests__/__snapshots__/Hostname.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Hostname Snapshot 1`] = ` 4 |
    5 |

    6 | Hostname 7 |

    8 |

    9 | Here you can check and set the name of your device. 10 |

    11 |
    14 |
    15 |

    16 | Hostname Settings 17 |

    18 |
    21 | 26 |
    29 | 35 |
    36 | 39 | Rewrite the hostname to set a new one. 40 | 41 |
    42 |
    45 | 51 |
    52 |
    53 |
    54 |
    55 | `; 56 | -------------------------------------------------------------------------------- /js/src/interfaces/Interfaces.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | .network .scrollable { 9 | display: flex; 10 | overflow-x: auto; 11 | } 12 | 13 | .network ::-webkit-scrollbar { 14 | height: 5px; 15 | } 16 | 17 | .network ::-webkit-scrollbar-thumb { 18 | background: #95a5a6; 19 | border-radius: 0.2em; 20 | } 21 | 22 | .network ::-webkit-scrollbar-thumb:hover { 23 | background: #4e5a5b; 24 | } 25 | 26 | .network .interface { 27 | display: flex; 28 | flex-direction: column; 29 | margin: 0.5rem; 30 | align-items: center; 31 | cursor: pointer; 32 | background: transparent; 33 | border: none; 34 | padding: 0; 35 | } 36 | 37 | @keyframes interfaceSelectedFade { 38 | from { 39 | -webkit-filter: none; 40 | filter: none; 41 | } 42 | to { 43 | -webkit-filter: drop-shadow(0 0 0.25rem #00a2e2); 44 | filter: drop-shadow(0 0 0.25rem #00a2e2); 45 | } 46 | } 47 | 48 | .network .interface-selected i svg { 49 | animation-name: interfaceSelectedFade; 50 | animation-duration: 0.3s; 51 | -webkit-filter: drop-shadow(0 0 0.5rem #00a2e2); 52 | filter: drop-shadow(0 0 0.25rem #00a2e2); 53 | } 54 | 55 | .network .interface-selected .fa-inverse { 56 | display: none; 57 | } 58 | 59 | .interface:focus { 60 | outline: none; 61 | } 62 | 63 | .interface:active { 64 | color: inherit; 65 | } 66 | -------------------------------------------------------------------------------- /js/src/interfaces/__tests__/InterfacesForm.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { render } from "foris/testUtils/customTestRender"; 10 | import { mockSetAlert } from "foris/testUtils/alertContextMock"; 11 | 12 | import { interfacesFixture } from "./__fixtures__/interfaces"; 13 | import InterfacesForm from "../InterfacesForm"; 14 | 15 | describe("", () => { 16 | it("should display alert on open ports", () => { 17 | render(); 18 | expect(mockSetAlert).toBeCalledWith( 19 | "Ports are open on your WAN interface. It's better to reconfigure your interface settings to avoid security issues." 20 | ); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /js/src/interfaces/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const NETWORKS_CHOICES = { 9 | wan: _("WAN"), 10 | lan: _("LAN"), 11 | guest: _("Guest Network"), 12 | none: _("Unassigned"), 13 | }; 14 | export const NETWORKS_TYPES = ["wan", "lan", "guest", "none"]; 15 | 16 | export const BUSES = ["eth", "pci", "usb", "sdio", "sfp"]; 17 | export const INTERFACE_TYPES = { 18 | eth: "eth", 19 | wifi: "wifi", 20 | wwan: "wwan", 21 | }; 22 | export const INTERFACE_STATES = { 23 | up: "up", 24 | down: "down", 25 | }; 26 | -------------------------------------------------------------------------------- /js/src/lan/LAN_DHCP_ClientsList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import ReactDOM from "react-dom"; 11 | 12 | import DHCPClientsList from "common/network/DHCPClientsList"; 13 | import DHCP6ClientsList from "common/network/DHCP6ClientsList"; 14 | import { LAN_MODES } from "./LANForm"; 15 | 16 | LAN_DHCP_ClientsList.propTypes = { 17 | formData: PropTypes.shape({ 18 | mode: PropTypes.oneOf(Object.keys(LAN_MODES)), 19 | mode_managed: PropTypes.shape({ 20 | dhcp: PropTypes.shape({ 21 | enabled: PropTypes.bool.isRequired, 22 | clients: PropTypes.arrayOf(PropTypes.object).isRequired, 23 | ipv6clients: PropTypes.arrayOf(PropTypes.object).isRequired, 24 | }).isRequired, 25 | }), 26 | }), 27 | }; 28 | 29 | export default function LAN_DHCP_ClientsList({ formData }) { 30 | if ( 31 | formData.mode !== LAN_MODES.managed || 32 | !formData.mode_managed.dhcp.enabled 33 | ) 34 | return null; 35 | 36 | const lanContainer = document.getElementById("dhcp-clients-container"); 37 | 38 | return ReactDOM.createPortal( 39 | <> 40 | 41 | 44 | , 45 | lanContainer 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /js/src/main/TopBar/LogoutButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React, { useEffect } from "react"; 9 | import { API_STATE, ForisURLs, useAPIPost } from "foris"; 10 | import ReactTooltip from "react-tooltip"; 11 | 12 | import API_URLs from "common/API"; 13 | 14 | export default function LogoutButton() { 15 | const [logout, postLogout] = useAPIPost(API_URLs.logout); 16 | 17 | useEffect(() => { 18 | if (logout.state === API_STATE.SUCCESS) { 19 | window.location.replace(ForisURLs.login); 20 | } 21 | }, [logout.state]); 22 | return ( 23 |
    24 | 25 | {_("Logout")} 26 | 27 | 37 |
    38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /js/src/main/TopBar/NotificationsDropdown/NotificationsDropdown.css: -------------------------------------------------------------------------------- 1 | #notifications .dropdown-menu { 2 | width: 20rem; 3 | } 4 | 5 | #notifications .dropdown-menu .btn-link { 6 | color: #95a5a6; 7 | } 8 | 9 | #notifications .dropdown-menu .btn-link:hover { 10 | color: #00a2e2; 11 | text-decoration: none; 12 | } 13 | 14 | #notifications .dropdown-divider-top { 15 | margin-bottom: 0; 16 | } 17 | 18 | #notifications .dropdown-divider-bottom { 19 | margin-top: 0; 20 | } 21 | 22 | #notifications .simplebar-scrollbar::before { 23 | background: black !important; 24 | } 25 | 26 | #notifications .scrollable-menu { 27 | max-height: 20rem; 28 | } 29 | 30 | @media (max-width: 769px) { 31 | #top-bar #notifications.dropdown:hover .dropdown-menu { 32 | display: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /js/src/main/TopBar/NotificationsDropdown/NotificationsDropdown.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import NotificationsDropdownButton from "./NotificationsDropdownButton"; 12 | import NotificationsDropdownMenu from "./NotificationsDropdownMenu"; 13 | import "./NotificationsDropdown.css"; 14 | 15 | NotificationsDropdown.propTypes = { 16 | notifications: PropTypes.array.isRequired, 17 | newNotification: PropTypes.bool.isRequired, 18 | isLoading: PropTypes.bool.isRequired, 19 | dismiss: PropTypes.func.isRequired, 20 | dismissAll: PropTypes.func.isRequired, 21 | }; 22 | 23 | export default function NotificationsDropdown({ 24 | notifications, 25 | dismiss, 26 | dismissAll, 27 | isLoading, 28 | newNotification, 29 | }) { 30 | return ( 31 |
    32 | 37 | 42 |
    43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /js/src/main/TopBar/NotificationsDropdown/NotificationsDropdownButton.css: -------------------------------------------------------------------------------- 1 | #notifications-btn .fa-bell { 2 | font-size: 1.5rem; 3 | } 4 | 5 | #notifications-counter { 6 | display: flex; 7 | justify-content: flex-end; 8 | } 9 | 10 | #notifications-counter .number { 11 | background: #e74c3c; 12 | font-size: 0.8rem; 13 | color: white; 14 | width: 1.2rem; 15 | height: 1.2rem; 16 | border-radius: 50%; 17 | z-index: 1; 18 | } 19 | 20 | .jump { 21 | animation: jump-keyframes 1s; 22 | } 23 | 24 | @keyframes jump-keyframes { 25 | 0% { 26 | transform: scale(1, 1) translateY(0); 27 | } 28 | 10% { 29 | transform: scale(1.1, 0.9) translateY(0); 30 | } 31 | 30% { 32 | transform: scale(0.9, 1.1) translateY(-2rem); 33 | } 34 | 50% { 35 | transform: scale(1.05, 0.95) translateY(0); 36 | } 37 | 57% { 38 | transform: scale(1, 1) translateY(-0.2rem); 39 | } 40 | 64% { 41 | transform: scale(1, 1) translateY(0); 42 | } 43 | 100% { 44 | transform: scale(1, 1) translateY(0); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /js/src/main/TopBar/NotificationsDropdown/NotificationsDropdownItem.css: -------------------------------------------------------------------------------- 1 | .notification-item i svg { 2 | display: flex; 3 | } 4 | 5 | /* Align dismiss icon with cog icon in header */ 6 | .notification-item .dismiss { 7 | width: 1.75rem; 8 | flex-shrink: 0; 9 | } 10 | 11 | .notification-item { 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | padding: 0.5rem 1.5rem; 16 | } 17 | 18 | .notification-item button { 19 | padding: 0; 20 | } 21 | 22 | .notification-item + .dropdown-divider { 23 | margin: 0; 24 | } 25 | 26 | .notification-item:hover { 27 | background: white; 28 | color: #95a5a6; 29 | } 30 | 31 | .notification-item:hover i > svg { 32 | opacity: 0.7; 33 | } 34 | 35 | .notifications-info { 36 | flex-grow: 1; 37 | min-width: 0; 38 | padding-left: 1rem; 39 | padding-right: 0.5rem; 40 | } 41 | 42 | .notifications-info p { 43 | overflow: hidden; 44 | text-overflow: ellipsis; 45 | margin-bottom: 0.3em; 46 | } 47 | 48 | .dismiss i svg { 49 | margin: 0 auto; 50 | } 51 | -------------------------------------------------------------------------------- /js/src/main/TopBar/NotificationsDropdown/NotificationsDropdownMenu.css: -------------------------------------------------------------------------------- 1 | #notifications-header { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: space-between; 5 | align-items: baseline; 6 | } 7 | 8 | /* Remove spacing for fa-cog link in header */ 9 | #notifications-header .btn-link { 10 | padding: 0; 11 | margin: 0; 12 | } 13 | 14 | #notifications-header .fa-cog { 15 | font-size: 1.25rem; 16 | } 17 | 18 | #notifications-footer { 19 | display: flex; 20 | flex-direction: row; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | 25 | #notifications-footer .btn-link { 26 | font-weight: bold; 27 | padding: 0; 28 | } 29 | 30 | .no-notifications { 31 | text-align: center; 32 | margin-top: 0.5rem; 33 | } 34 | -------------------------------------------------------------------------------- /js/src/main/TopBar/TopBar.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | #top-bar, 9 | #top-bar-container { 10 | display: flex; 11 | justify-content: flex-end; 12 | } 13 | 14 | #top-bar-container button.nav-item:hover { 15 | cursor: default; 16 | } 17 | 18 | #top-bar-container button.nav-item:hover:last-child { 19 | cursor: pointer; 20 | } 21 | 22 | #top-bar { 23 | align-items: center; 24 | position: absolute; 25 | right: 30px; 26 | z-index: 1000; 27 | } 28 | 29 | #top-bar-container { 30 | align-items: stretch; 31 | } 32 | 33 | #top-bar .nav-item { 34 | text-decoration: none; 35 | color: black; 36 | height: 100%; 37 | } 38 | 39 | @media (max-width: 768px) { 40 | #top-bar { 41 | justify-content: center; 42 | position: initial; 43 | margin-bottom: 1rem; 44 | } 45 | #content-container h1:first-child { 46 | margin-top: 3.1rem; 47 | margin-bottom: 0.5rem; 48 | } 49 | } 50 | 51 | #top-bar a:hover { 52 | text-decoration: none; 53 | color: #00a2e2; 54 | } 55 | 56 | #top-bar .dropdown-header, 57 | #top-bar .dropdown-header a { 58 | color: #333333; 59 | } 60 | 61 | #top-bar .dropdown-header h5 { 62 | margin-bottom: 0; 63 | } 64 | 65 | #top-bar .dropdown:hover .dropdown-menu { 66 | transform: scale(1, 1) translateY(0); 67 | opacity: 1; 68 | visibility: visible; 69 | } 70 | -------------------------------------------------------------------------------- /js/src/main/TopBar/TopBar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import useNotifications, { 12 | useNewNotification, 13 | } from "../../notifications/hooks"; 14 | import RebootDropdown from "./rebootDropdown/RebootDropdown"; 15 | import UpdatesDropdown from "./updatesDropdown/UpdatesDropdown"; 16 | import NotificationsDropdown from "./NotificationsDropdown/NotificationsDropdown"; 17 | import LanguagesDropdown from "./languagesDropdown/LanguagesDropdown"; 18 | import LogoutButton from "./LogoutButton"; 19 | 20 | import "./TopBar.css"; 21 | 22 | TopBar.propTypes = { 23 | ws: PropTypes.object.isRequired, 24 | }; 25 | 26 | export default function TopBar({ ws }) { 27 | const [notifications, dismiss, dismissAll, isLoading] = useNotifications( 28 | ws 29 | ); 30 | const newNotification = useNewNotification(ws); 31 | return ( 32 | <> 33 | 34 | 35 | 42 | 43 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /js/src/main/TopBar/languagesDropdown/LanguagesDropdown.css: -------------------------------------------------------------------------------- 1 | #languages-dropdown-menu .dropdown-item:hover { 2 | color: #00a2e2; 3 | } 4 | -------------------------------------------------------------------------------- /js/src/main/TopBar/languagesDropdown/hooks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import { useEffect } from "react"; 9 | 10 | import { useAPIGet } from "foris"; 11 | import API_URLs from "common/API"; 12 | 13 | export function useLanguages() { 14 | const [languageState, getLanguage] = useAPIGet(API_URLs.language); 15 | const [languagesState, getLanguages] = useAPIGet(API_URLs.languages); 16 | 17 | useEffect(() => { 18 | getLanguage(); 19 | getLanguages(); 20 | }, [getLanguage, getLanguages]); 21 | 22 | return [languageState.data, languagesState.data]; 23 | } 24 | 25 | export function useWSSetLanguageRefresh(ws) { 26 | useEffect(() => { 27 | const module = "web"; 28 | ws.subscribe(module).bind(module, "set_language", () => 29 | window.location.reload() 30 | ); 31 | }, [ws]); 32 | } 33 | -------------------------------------------------------------------------------- /js/src/main/TopBar/rebootDropdown/RebootDropdown.css: -------------------------------------------------------------------------------- 1 | @keyframes rebootFade { 2 | from { 3 | opacity: 0.1; 4 | } 5 | to { 6 | opacity: 1; 7 | } 8 | } 9 | 10 | #reboot-dropdown-toggle { 11 | animation-name: rebootFade; 12 | animation-duration: 1.5s; 13 | animation-iteration-count: infinite; 14 | animation-direction: alternate; 15 | } 16 | 17 | #reboot-dropdown-toggle i svg { 18 | color: red; 19 | } 20 | -------------------------------------------------------------------------------- /js/src/main/TopBar/rebootDropdown/__tests__/RebootDropdown.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { render, wait } from "foris/testUtils/customTestRender"; 10 | 11 | import { notificationsFixture } from "../../../../notifications/__tests__/__fixtures__/notifications"; 12 | 13 | import RebootDropdown from "../RebootDropdown"; 14 | 15 | describe("", () => { 16 | let rebootDropdownContainer; 17 | 18 | beforeEach(async () => { 19 | const { container, getByText } = render( 20 | 23 | ); 24 | rebootDropdownContainer = container; 25 | await wait(() => { 26 | getByText("Reboot Required"); 27 | }); 28 | }); 29 | 30 | it("Test with snapshot", () => { 31 | expect(rebootDropdownContainer).toMatchSnapshot(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /js/src/main/TopBar/rebootDropdown/__tests__/__snapshots__/RebootDropdown.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` Test with snapshot 1`] = ` 4 |
    5 | 57 | `; 58 | -------------------------------------------------------------------------------- /js/src/main/TopBar/updatesDropdown/__tests__/__snapshots__/UpdatesDropdown.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` Loading (spinner visible) 1`] = ` 4 |
    5 | 23 |
    24 | `; 25 | 26 | exports[` Updates awaiting - snapshot 1`] = ` 27 |
    28 | 81 | `; 82 | -------------------------------------------------------------------------------- /js/src/main/__tests__/Main.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | import { render, wait, getByText } from "foris/testUtils/customTestRender"; 11 | import { WebSockets } from "foris"; 12 | import mockAxios from "jest-mock-axios"; 13 | 14 | import Main from "../Main"; 15 | 16 | function mockBrokenComponent() { 17 | throw new Error("This component is broken!"); 18 | } 19 | 20 | jest.mock("../pages", () => { 21 | return () => [ 22 | { 23 | name: "Overview", 24 | path: "/overview", 25 | icon: "chart-line", 26 | component: mockBrokenComponent, 27 | }, 28 | ]; 29 | }); 30 | 31 | describe("
    ", () => { 32 | it("should use error boundary when cannot render component", async () => { 33 | global.ForisPlugins = []; 34 | const webSockets = new WebSockets(); 35 | const originalError = console.error; 36 | 37 | console.error = jest.fn(); 38 | const { container } = render( 39 | <> 40 |
    41 |
    42 | 43 | ); 44 | mockAxios.mockResponse({ data: {} }); 45 | await wait(() => getByText(container, "An Error Occurred")); 46 | expect(console.error).toBeCalled(); 47 | console.error = originalError; 48 | 49 | expect(container).toMatchSnapshot(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /js/src/main/__tests__/__fixtures__/pages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | export const PAGES = [ 11 | { 12 | path: "/one", 13 | name: "One", 14 | component: () => () =>

    One

    , 15 | }, 16 | { 17 | path: "/two", 18 | name: "Two", 19 | component: () =>

    Two

    , 20 | }, 21 | { 22 | path: "/three", 23 | name: "Three", 24 | component: () =>

    Three

    , 25 | }, 26 | { 27 | path: "/first-submenu", 28 | submenuId: "first-submenu", 29 | name: "First submenu (Four)", 30 | pages: [ 31 | { 32 | path: "/sub-one", 33 | name: "SubOne", 34 | component: () =>

    SubOne

    , 35 | }, 36 | { 37 | path: "/sub-two", 38 | name: "SubTwo", 39 | component: () =>

    SubTwo

    , 40 | }, 41 | { 42 | path: "/sub-three", 43 | name: "SubThree", 44 | component: () =>

    SubThree

    , 45 | }, 46 | ], 47 | }, 48 | { 49 | path: "/five", 50 | name: "Five", 51 | component: () =>

    Five

    , 52 | }, 53 | { 54 | path: "/second-submenu", 55 | submenuId: "second-submenu", 56 | name: "Second submenu (Six - Last)", 57 | pages: [ 58 | { 59 | path: "/sec-sub-one", 60 | name: "SecSubOne", 61 | component: () =>

    SecSubOne

    , 62 | }, 63 | ], 64 | }, 65 | ]; 66 | -------------------------------------------------------------------------------- /js/src/main/__tests__/__fixtures__/plugins.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | export const pluginInRoot = () => ({ 11 | name: "Plugin in root", 12 | path: "/plugin-in-root", 13 | icon: "icon", 14 | component: () =>

    Root

    , 15 | }); 16 | 17 | export const pluginInRootWithoutIcon = () => ({ 18 | name: "Plugin in root", 19 | path: "/plugin-in-root", 20 | component: () =>

    Root

    , 21 | }); 22 | 23 | export const pluginInExistedSubmenu = (submenuId, weight) => ({ 24 | path: "/plugin-in-submenu", 25 | name: "Plugin in submenu", 26 | icon: "icon", 27 | submenuId, 28 | weight, 29 | component: () =>

    SubPlugin

    , 30 | }); 31 | 32 | export const newSubmenu = (submenuId, weight) => ({ 33 | submenuId, 34 | name: "New Submenu", 35 | icon: "icon", 36 | weight, 37 | path: "/plugin-in-submenu", 38 | pages: [ 39 | { 40 | name: "SubPlugin one", 41 | path: "/sub-plugin-one", 42 | component: () =>

    SubPlugin One

    , 43 | }, 44 | { 45 | name: "SubPlugin two", 46 | path: "/sub-plugin-two", 47 | component: () =>

    SubPlugin Two

    , 48 | }, 49 | ], 50 | }); 51 | -------------------------------------------------------------------------------- /js/src/main/__tests__/__snapshots__/Main.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`
    should use error boundary when cannot render component 1`] = ` 4 |
    5 |
    8 |

    9 | An Error Occurred 10 |

    11 | 14 | Error: This component is broken! 15 | 16 |

    17 | More detailed information is available in the console of your web browser - on most browsers accessible after pressing Ctrl+Shift+J or F12. 18 |

    19 |

    20 | Please report this error to our support team via e-mail: 21 | 24 | tech.support@turris.cz 25 | 26 | . 27 |

    28 |
    29 |
    30 | `; 31 | -------------------------------------------------------------------------------- /js/src/main/__tests__/pages.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import getPages from "../pages"; 9 | import { newSubmenu, pluginInExistedSubmenu } from "./__fixtures__/plugins"; 10 | 11 | describe("Test plugging in the menu.", () => { 12 | let customization = false; 13 | 14 | it("Master plugin first, slave second (normal order).", () => { 15 | global.ForisPlugins = [ 16 | newSubmenu("nonexisted-submenu", 1), 17 | pluginInExistedSubmenu("nonexisted-submenu", 1), 18 | ]; 19 | const pages = getPages(customization); 20 | 21 | expect(pages[4].submenuId).toBe("nonexisted-submenu"); 22 | expect(pages[4].pages[0].name).toBe("Plugin in submenu"); 23 | }); 24 | 25 | it("Slave plugin first, master second (reversed order).", () => { 26 | global.ForisPlugins = [ 27 | pluginInExistedSubmenu("nonexisted-submenu", 1), 28 | newSubmenu("nonexisted-submenu", 1), 29 | ]; 30 | const pages = getPages(customization); 31 | expect(pages[4].submenuId).toBe("nonexisted-submenu"); 32 | expect(pages[4].pages[0].name).toBe("Plugin in submenu"); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /js/src/main/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | // eslint-disable-next-line import/prefer-default-export 9 | export const REDIRECT_404_PAGE = "/overview"; 10 | -------------------------------------------------------------------------------- /js/src/main/customizationContext.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | export const CustomizationContext = React.createContext(false); 11 | export const CustomizationProvider = CustomizationContext.Provider; 12 | -------------------------------------------------------------------------------- /js/src/maintenance/FactoryReset.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { formFieldsSize } from "foris"; 10 | 11 | import FactoryResetButton from "../common/FactoryResetButton"; 12 | 13 | export default function FactoryReset() { 14 | return ( 15 |
    16 |

    {_(`Factory Reset`)}

    17 |

    18 | {_(`Doing a factory reset on the Turris device will remove all the packages 19 | installed on the device along with the data associated with them. This brings 20 | back all the default settings of the device as it was when the router was new, 21 | giving you a clean slate to start all over again. 22 | `)} 23 |

    24 |
    25 | 26 |
    27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /js/src/maintenance/Maintenance.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { isPluginInstalled } from "foris"; 10 | import Reboot from "./Reboot"; 11 | import FactoryReset from "./FactoryReset"; 12 | import Syslog from "./Syslog/Syslog"; 13 | 14 | export default function Maintenance() { 15 | return ( 16 | <> 17 |

    {_("Maintenance")}

    18 |

    19 | {_( 20 | `The Maintenance tab allows you to reboot the router or bring back all the default settings by performing a factory reset.` 21 | )} 22 |

    23 | 24 | 25 | 26 | {isPluginInstalled("Storage") ? : null} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /js/src/maintenance/Reboot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { formFieldsSize } from "foris"; 10 | 11 | import RebootButton from "../common/RebootButton"; 12 | 13 | export default function Reboot() { 14 | return ( 15 |
    16 |

    {_(`Reboot`)}

    17 |

    18 | {_(` 19 | If you need to reboot the device, click on the following button. The reboot process takes approximately 30 seconds, you 20 | will be required to log in again after the reboot. 21 | `)} 22 |

    23 |
    24 | 25 |
    26 |
    27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /js/src/maintenance/Syslog/Syslog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { ForisForm } from "foris"; 10 | import { API_MODULE_URLs } from "../../common/API"; 11 | import SyslogForm from "./SyslogForm"; 12 | 13 | export default function Syslog() { 14 | return ( 15 | 23 | 24 | 25 | ); 26 | } 27 | 28 | function prepData(formData) { 29 | return formData; 30 | } 31 | 32 | function prepDataToSubmit(formData) { 33 | delete formData.disk_mounted; 34 | delete formData.formating; 35 | delete formData.old_device; 36 | delete formData.old_uuid; 37 | delete formData.state; 38 | delete formData.uuid; 39 | 40 | return formData; 41 | } 42 | 43 | function validator(formData) { 44 | const errors = {}; 45 | if (formData.disk_mounted !== true) { 46 | errors.noDiskMounted = _("A drive has to be configured first."); 47 | } 48 | return errors.noDiskMounted ? errors : undefined; 49 | } 50 | -------------------------------------------------------------------------------- /js/src/maintenance/Syslog/SyslogForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import { CheckBox, ForisURLs } from "foris"; 11 | 12 | SyslogForm.defaultProps = { 13 | formErrors: {}, 14 | }; 15 | 16 | SyslogForm.propTypes = { 17 | formErrors: PropTypes.shape({ 18 | noDiskMounted: PropTypes.string, 19 | }), 20 | formData: PropTypes.shape({ 21 | persistent_logs: PropTypes.bool, 22 | }), 23 | setFormValue: PropTypes.func, 24 | }; 25 | 26 | export default function SyslogForm({ formData, setFormValue, formErrors }) { 27 | return ( 28 | <> 29 |

    {_(`System logs retention`)}

    30 |

    Storage plugin first.` 34 | ), 35 | }} 36 | /> 37 | ({ 42 | persistent_logs: { $set: value }, 43 | }))} 44 | /> 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /js/src/maintenance/Syslog/__tests__/Syslog.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { 10 | fireEvent, 11 | getByText, 12 | render, 13 | wait, 14 | } from "foris/testUtils/customTestRender"; 15 | import Syslog from "../Syslog"; 16 | import syslogFixture from "./__fixtures__/syslog"; 17 | import mockAxios from "jest-mock-axios"; 18 | 19 | describe("Syslog", () => { 20 | let syslogContainer; 21 | 22 | beforeEach(async () => { 23 | const { container } = render(); 24 | mockAxios.mockResponse({ data: syslogFixture }); 25 | await wait(() => getByText(container, "System logs retention")); 26 | syslogContainer = container; 27 | }); 28 | 29 | it("Snapshot form enabled", async () => { 30 | expect(syslogContainer).toMatchSnapshot(); 31 | }); 32 | 33 | it("Snapshot form disabled", async () => { 34 | const { container } = render(); 35 | mockAxios.mockResponse({ data: { disk_mounted: false } }); 36 | await wait(() => getByText(container, "System logs retention")); 37 | syslogContainer = container; 38 | expect(syslogContainer).toMatchSnapshot(); 39 | }); 40 | 41 | it("Success post request", () => { 42 | fireEvent.click(getByText(syslogContainer, "Save")); 43 | expect(mockAxios.post).toHaveBeenCalledWith( 44 | "/reforis/storage/api/settings", 45 | { persistent_logs: true }, 46 | expect.anything() 47 | ); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /js/src/maintenance/Syslog/__tests__/__fixtures__/syslog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | const syslogFixture = { 9 | disk_mounted: true, 10 | formating: false, 11 | old_device: "/dev/1001", 12 | old_uuid: "rootfs", 13 | persistent_logs: true, 14 | state: "none", 15 | uuid: "", 16 | }; 17 | 18 | export default syslogFixture; 19 | -------------------------------------------------------------------------------- /js/src/navigation/FaIcon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import PropTypes from "prop-types"; 9 | import React from "react"; 10 | 11 | FaIcon.propTypes = { 12 | name: PropTypes.string.isRequired, 13 | }; 14 | 15 | export default function FaIcon({ name }) { 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /js/src/navigation/NavigationItem.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | import PropTypes from "prop-types"; 11 | import { NavLink } from "react-router-dom"; 12 | 13 | import smallScreenWidth from "../utils/constants"; 14 | 15 | NavigationItem.propTypes = { 16 | path: PropTypes.string.isRequired, 17 | isLinkOutside: PropTypes.bool, 18 | children: PropTypes.oneOfType([ 19 | PropTypes.arrayOf(PropTypes.node), 20 | PropTypes.node, 21 | ]), 22 | }; 23 | 24 | export default function NavigationItem({ path, children, isLinkOutside }) { 25 | if (isLinkOutside) { 26 | return ( 27 |

  • 28 | 35 | {children} 36 | 37 | 38 | 39 | 40 |
  • 41 | ); 42 | } 43 | 44 | return ( 45 |
  • 53 | {children} 54 |
  • 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /js/src/navigation/NavigationMainItem.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import NavigationItem from "./NavigationItem"; 12 | import getIconElement from "./utils"; 13 | 14 | NavigationMainItem.propTypes = { 15 | icon: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.node]), 16 | name: PropTypes.string.isRequired, 17 | }; 18 | 19 | export default function NavigationMainItem({ icon, name, ...props }) { 20 | const iconElement = getIconElement(icon); 21 | 22 | return ( 23 | 24 | {iconElement} 25 | {name} 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /js/src/navigation/NavigationToggle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React, { useState } from "react"; 9 | import PropTypes from "prop-types"; 10 | import { useUID } from "react-uid"; 11 | 12 | import NavigationItem from "./NavigationItem"; 13 | import getIconElement from "./utils"; 14 | 15 | NavigationToggle.propTypes = { 16 | name: PropTypes.string.isRequired, 17 | icon: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.node]), 18 | active: PropTypes.bool.isRequired, 19 | children: PropTypes.oneOfType([ 20 | PropTypes.arrayOf(PropTypes.node), 21 | PropTypes.node, 22 | ]), 23 | }; 24 | 25 | export function NavigationToggle({ name, icon, active, children }) { 26 | const uid = useUID(); 27 | const iconElement = getIconElement(icon); 28 | const [activeToggle, setActiveToggle] = useState(active); 29 | 30 | return ( 31 |
  • 32 | setActiveToggle(false)} 37 | > 38 | {iconElement} 39 | {name} 40 | 41 |
      47 | {children} 48 |
    49 |
  • 50 | ); 51 | } 52 | 53 | NavigationToggleItem.propTypes = { 54 | name: PropTypes.string.isRequired, 55 | }; 56 | 57 | export function NavigationToggleItem({ name, ...props }) { 58 | return ( 59 | 60 | {name} 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /js/src/navigation/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | 10 | import FaIcon from "./FaIcon"; 11 | 12 | export default function getIconElement(icon) { 13 | let iconElement = null; 14 | if (typeof icon === "string") { 15 | iconElement = ; 16 | } else if (React.isValidElement(icon)) { 17 | iconElement = icon; 18 | } 19 | return iconElement; 20 | } 21 | -------------------------------------------------------------------------------- /js/src/notifications/NotificationIcon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import { SEVERITIES } from "./constants"; 11 | 12 | NotificationIcon.propTypes = { 13 | severity: PropTypes.string.isRequired, 14 | className: PropTypes.string, 15 | }; 16 | 17 | export default function NotificationIcon({ severity, className }) { 18 | let iconName = null; 19 | switch (severity) { 20 | case SEVERITIES.NEWS: 21 | iconName = "newspaper"; 22 | break; 23 | case SEVERITIES.RESTART: 24 | iconName = "power-off"; 25 | break; 26 | case SEVERITIES.ERROR: 27 | iconName = "exclamation-circle"; 28 | break; 29 | case SEVERITIES.UPDATE: 30 | iconName = "sync-alt"; 31 | break; 32 | default: 33 | } 34 | 35 | return ; 36 | } 37 | -------------------------------------------------------------------------------- /js/src/notifications/Notifications/DismissAllButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | DismissAllButton.propTypes = { 12 | dismissAll: PropTypes.func.isRequired, 13 | }; 14 | 15 | export default function DismissAllButton({ dismissAll }) { 16 | return ( 17 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /js/src/notifications/Notifications/Notifications.css: -------------------------------------------------------------------------------- 1 | #notifications-center { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | #notifications-center .card-body { 7 | white-space: pre-wrap; 8 | } 9 | 10 | #notifications-center .card-body + button { 11 | border-radius: 0 0 0.25rem 0.25rem; 12 | } 13 | 14 | #notifications-center .card { 15 | margin-bottom: 1rem; 16 | } 17 | 18 | #notifications-center .bg-light { 19 | background-color: #fbfbfb !important; 20 | } 21 | 22 | #notifications-center .close { 23 | color: inherit; 24 | } 25 | 26 | #notifications-center #btn-dismiss-all { 27 | margin-bottom: 1rem; 28 | } 29 | 30 | #notifications-center .card-header { 31 | display: flex; 32 | flex-direction: row; 33 | align-items: center; 34 | justify-content: space-between; 35 | } 36 | 37 | #notifications-center i .fa-newspaper, 38 | #notifications-center i .fa-sync-alt, 39 | .notification-item i > .fa-newspaper, 40 | .notification-item i > .fa-sync-alt { 41 | color: #00a2e2; 42 | opacity: 0.8; 43 | } 44 | 45 | #notifications-center i .fa-power-off, 46 | #notifications-center i .fa-exclamation-circle, 47 | .notification-item i > .fa-power-off, 48 | .notification-item i > .fa-exclamation-circle { 49 | color: #e74c3c; 50 | opacity: 0.8; 51 | } 52 | 53 | @media screen and (max-width: 402px) { 54 | #btn-dismiss-all { 55 | position: relative; 56 | width: 100%; 57 | float: none !important; 58 | } 59 | } 60 | 61 | #notifications-center .highlight-danger { 62 | background-color: #e74c3c38; 63 | } 64 | 65 | #notifications-center .highlight-info { 66 | background-color: #3498db33; 67 | } 68 | -------------------------------------------------------------------------------- /js/src/notifications/Notifications/TruncatedText.css: -------------------------------------------------------------------------------- 1 | .toggle-collapse { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /js/src/notifications/Notifications/TruncatedText.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import "./TruncatedText.css"; 5 | 6 | TruncatedText.propTypes = { 7 | text: PropTypes.string.isRequired, 8 | charLimit: PropTypes.number.isRequired, 9 | threshold: PropTypes.number.isRequired, 10 | }; 11 | 12 | TruncatedText.defaultProps = { 13 | threshold: 16, 14 | }; 15 | 16 | export default function TruncatedText({ text, charLimit, threshold }) { 17 | const [collapsed, setCollapsed] = useState(true); 18 | let cardText = text; 19 | let toggleCollapse = null; 20 | 21 | if (text.length > charLimit + threshold) { 22 | if (collapsed) { 23 | cardText = text.substring(0, charLimit).replace(/(\s|\W)$/, ""); 24 | cardText = `${cardText}...`; 25 | } 26 | toggleCollapse = ( 27 | 31 | ); 32 | } 33 | 34 | return ( 35 | <> 36 |

    {cardText}

    37 | {toggleCollapse} 38 | 39 | ); 40 | } 41 | 42 | ToggleCollapse.propTypes = { 43 | collapseFn: PropTypes.func.isRequired, 44 | shouldCollapse: PropTypes.bool.isRequired, 45 | }; 46 | 47 | function ToggleCollapse({ collapseFn, shouldCollapse }) { 48 | return ( 49 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /js/src/notifications/__tests__/NotificationsCenter.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | import React from "react"; 8 | import { render, wait } from "foris/testUtils/customTestRender"; 9 | 10 | import mockAxios from "jest-mock-axios"; 11 | import { WebSockets } from "foris"; 12 | import { notificationsFixture } from "./__fixtures__/notifications"; 13 | 14 | import NotificationsCenter from "../Notifications/Notifications"; 15 | 16 | describe("", () => { 17 | it("Test with snapshot.", async () => { 18 | const webSockets = new WebSockets(); 19 | const { container, getByText } = render( 20 | 21 | ); 22 | mockAxios.mockResponse({ data: notificationsFixture }); 23 | await wait(() => { 24 | getByText("Notification message."); 25 | }); 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | 29 | it("Test with spinner.", () => { 30 | const webSockets = new WebSockets(); 31 | const { container } = render(); 32 | expect(container).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /js/src/notifications/__tests__/NotificationsDropdown.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { render, wait } from "foris/testUtils/customTestRender"; 10 | 11 | import { 12 | notificationsFixture, 13 | newNotification, 14 | isLoading, 15 | dismissAll, 16 | dismiss, 17 | } from "./__fixtures__/notifications"; 18 | 19 | import NotificationsDropdown from "../../main/TopBar/NotificationsDropdown/NotificationsDropdown"; 20 | 21 | describe("", () => { 22 | let notificationCenterContainer; 23 | 24 | beforeEach(async () => { 25 | const { container, getByText } = render( 26 | 33 | ); 34 | notificationCenterContainer = container; 35 | await wait(() => { 36 | getByText("Notification message."); 37 | }); 38 | }); 39 | 40 | it("Test with snapshot", () => { 41 | expect(notificationCenterContainer).toMatchSnapshot(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /js/src/notifications/__tests__/TruncatedText.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { render } from "foris/testUtils/customTestRender"; 10 | import TruncatedText from "../Notifications/TruncatedText"; 11 | 12 | describe("", () => { 13 | const ipsum = 14 | "Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon azuki bean garlic."; 15 | 16 | it("displays text without show more/less button", () => { 17 | const { container } = render( 18 | 19 | ); 20 | expect(container).toMatchSnapshot(); 21 | }); 22 | 23 | it("displays text with show more/less button", () => { 24 | const { container } = render( 25 | 26 | ); 27 | expect(container).toMatchSnapshot(); 28 | }); 29 | 30 | it("doesn't truncate the text then limit is exceeded below threshold", () => { 31 | const { container } = render( 32 | 33 | ); 34 | expect(container).toMatchSnapshot(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /js/src/notifications/__tests__/__fixtures__/notifications.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const notificationsFixture = { 9 | notifications: [ 10 | { 11 | msg: "Notification message.", 12 | id: "123-123", 13 | created_at: "2000-02-01 00:00:00", 14 | displayed: false, 15 | severity: "news", 16 | }, 17 | { 18 | msg: "Second notification message.", 19 | id: "123-124", 20 | created_at: "2000-02-01 00:00:00", 21 | displayed: false, 22 | severity: "error", 23 | }, 24 | { 25 | msg: "Third notification message.", 26 | id: "808-909", 27 | created_at: "2000-02-01 00:00:00", 28 | displayed: false, 29 | severity: "restart", 30 | }, 31 | { 32 | msg: "Displayed notification message.", 33 | id: "123-125", 34 | created_at: "2000-02-01 00:00:01", 35 | displayed: true, 36 | severity: "error", 37 | }, 38 | ], 39 | }; 40 | 41 | export const newNotification = false; 42 | export const isLoading = false; 43 | export const dismiss = jest.fn(); 44 | export const dismissAll = jest.fn(); 45 | -------------------------------------------------------------------------------- /js/src/notifications/__tests__/__snapshots__/NotificationsCenter.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` Test with snapshot. 1`] = ` 4 |

    5 | Notifications 6 | 16 |

    17 | `; 18 | 19 | exports[` Test with spinner. 1`] = ` 20 |
    21 |

    22 | Notifications 23 |

    24 |
    27 |
    30 |
    34 | 37 |
    38 |
    39 |
    40 |
    41 | `; 42 | -------------------------------------------------------------------------------- /js/src/notifications/__tests__/__snapshots__/TruncatedText.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` displays text with show more/less button 1`] = ` 4 |
    5 |

    8 | Veggies es bonus vobis, proinde vos postulo essum... 9 |

    10 | 16 |
    17 | `; 18 | 19 | exports[` displays text without show more/less button 1`] = ` 20 |
    21 |

    24 | Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon azuki bean garlic. 25 |

    26 |
    27 | `; 28 | 29 | exports[` doesn't truncate the text then limit is exceeded below threshold 1`] = ` 30 |
    31 |

    34 | Veggies es bonus vobis, proinde vos postulo essum magis kohlrabi welsh onion daikon amaranth tatsoi tomatillo melon azuki bean garlic. 35 |

    36 |
    37 | `; 38 | -------------------------------------------------------------------------------- /js/src/notifications/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export const SEVERITIES = { 9 | NEWS: "news", 10 | RESTART: "restart", 11 | ERROR: "error", 12 | UPDATE: "update", 13 | }; 14 | 15 | export const NOT_DISMISSABLE = [SEVERITIES.RESTART]; 16 | -------------------------------------------------------------------------------- /js/src/notifications/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import PropTypes from "prop-types"; 9 | 10 | const SEVERITIES = ["news", "restart", "error", "update"]; 11 | const NOTIFICATION_PROP_TYPES = PropTypes.shape({ 12 | msg: PropTypes.string.isRequired, 13 | id: PropTypes.string.isRequired, 14 | created_at: PropTypes.string.isRequired, 15 | displayed: PropTypes.bool.isRequired, 16 | severity: PropTypes.oneOf(SEVERITIES).isRequired, 17 | }).isRequired; 18 | export default NOTIFICATION_PROP_TYPES; 19 | -------------------------------------------------------------------------------- /js/src/notificationsSettings/SMTPTurrisForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { TextInput } from "foris"; 12 | 13 | import HELP_TEXTS from "./helpTexts"; 14 | 15 | SMTPTurrisForm.propTypes = { 16 | formData: PropTypes.shape({ sender_name: PropTypes.string }).isRequired, 17 | formErrors: PropTypes.shape({ sender_name: PropTypes.string }), 18 | setFormValue: PropTypes.func.isRequired, 19 | disabled: PropTypes.bool, 20 | }; 21 | 22 | SMTPTurrisForm.defaultProps = { 23 | setFormValue: () => {}, 24 | formData: {}, 25 | formErrors: {}, 26 | }; 27 | 28 | export default function SMTPTurrisForm({ 29 | formData, 30 | formErrors, 31 | setFormValue, 32 | disabled, 33 | }) { 34 | return ( 35 | <> 36 | ({ 42 | smtp_turris: { sender_name: { $set: value } }, 43 | }))} 44 | disabled={disabled} 45 | /> 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /js/src/notificationsSettings/helpTexts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | const HELP_TEXTS = { 9 | smtp_type: _( 10 | 'If you set SMTP provider to "Turris", the servers provided to members of the Turris project would be ' + 11 | "used. These servers do not require any additional settings. If you want to set your own SMTP server, please " + 12 | 'select "Custom" and enter required settings.' 13 | ), 14 | common: { 15 | to: _( 16 | "Email address of recipient. Separate multiple addresses by comma." 17 | ), 18 | send_news: _("Send emails about new features."), 19 | }, 20 | smtp_turris: { 21 | sender_name: _( 22 | 'Name of the sender - will be used as a part of the sender\'s email address before the "at" sign.' 23 | ), 24 | }, 25 | smtp_custom: { 26 | from: _("This is the address notifications are send from."), 27 | }, 28 | }; 29 | 30 | export default HELP_TEXTS; 31 | -------------------------------------------------------------------------------- /js/src/notificationsSettings/validator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import { validateMultipleEmails } from "foris"; 9 | 10 | export default function validator(formData) { 11 | const errors = {}; 12 | if (!formData.enabled) return undefined; 13 | 14 | errors.common = commonValidator(formData.common); 15 | if (formData.smtp_type === "turris") 16 | errors.smtp_turris = smtpTurrisValidator(formData.smtp_turris); 17 | else if (formData.smtp_type === "custom") 18 | errors.smtp_custom = smtpCustomValidator(formData.smtp_custom); 19 | return JSON.stringify(errors) !== "{}" ? errors : undefined; 20 | } 21 | 22 | function commonValidator(formData) { 23 | if (formData.to === "") return { to: _("Can't be empty.") }; 24 | 25 | const toError = validateMultipleEmails(formData.to); 26 | return toError ? { to: toError } : undefined; 27 | } 28 | 29 | const SENDER_NAME_RE = /^[0-9a-zA-Z_\\.-]+$/; 30 | 31 | function smtpTurrisValidator(formData) { 32 | if (formData.sender_name === "") 33 | return { sender_name: _("Sender's name can't be empty.") }; 34 | 35 | return !SENDER_NAME_RE.test(formData.sender_name) 36 | ? { 37 | sender_name: _( 38 | "Sender's name can contain only alphanumeric characters, dots and underscores." 39 | ), 40 | } 41 | : undefined; 42 | } 43 | 44 | function smtpCustomValidator(formData) { 45 | if (Number.isNaN(parseInt(formData.port))) 46 | return { port: _("Port is an number.") }; 47 | if (formData.port > 65535) return { port: _("Maximum port is 65535.") }; 48 | if (formData.port < 1) return { port: _("Minimum port is 1.") }; 49 | return undefined; 50 | } 51 | -------------------------------------------------------------------------------- /js/src/overview/Cards/DynamicFirewallCard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | DynamicFirewallCard.propTypes = { 12 | activated: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]) 13 | .isRequired, 14 | }; 15 | 16 | export default function DynamicFirewallCard({ activated }) { 17 | return ( 18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 | {_("Dynamic Firewall")} 25 |
    26 | 27 | {activated ? _("Activated") : _("Disabled")} 28 | 29 |
    30 |
    31 | 36 | 41 | 42 |
    43 |
    44 |
    45 |
    46 |
    47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /js/src/overview/Cards/NetmetrCard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | .text-to-bottom { 9 | position: absolute; 10 | bottom: 1.25rem; 11 | } 12 | -------------------------------------------------------------------------------- /js/src/overview/Overview.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | .card { 9 | box-shadow: 0 0.75rem 1.5rem rgba(18, 38, 63, 0.03); 10 | } 11 | 12 | .card-header { 13 | background-color: white; 14 | } 15 | 16 | .card-body .row .status { 17 | font-size: 1.7rem; 18 | } 19 | 20 | @media screen and (max-width: 1312px) { 21 | .row-cols-lg-3 > * { 22 | -webkit-box-flex: 0; 23 | -ms-flex: 0 0 50%; 24 | flex: 0 0 50%; 25 | max-width: 50%; 26 | } 27 | } 28 | @media screen and (max-width: 1010px) { 29 | .row-cols-lg-3 > * { 30 | -webkit-box-flex: 0; 31 | -ms-flex: 0 0 100%; 32 | flex: 0 0 100%; 33 | max-width: 100%; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /js/src/overview/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export default function displayCard({ package_lists: packages }, cardName) { 9 | const enabledPackagesNames = []; 10 | packages 11 | .filter((item) => item.enabled) 12 | .map((item) => { 13 | enabledPackagesNames.push(item.name); 14 | item.options 15 | .filter((option) => option.enabled) 16 | .map((option) => { 17 | enabledPackagesNames.push(option.name); 18 | return null; 19 | }); 20 | return null; 21 | }); 22 | return enabledPackagesNames.includes(cardName); 23 | } 24 | -------------------------------------------------------------------------------- /js/src/packageManagement/languages/Languages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import { ForisForm } from "foris"; 10 | 11 | import API_URLs from "common/API"; 12 | import DisableIfUpdaterIsDisabled from "../utils/DisableIfUpdaterIsDisabled"; 13 | import LanguagesForm from "./LanguagesForm"; 14 | 15 | export default function Languages() { 16 | return ( 17 | <> 18 |

    {_("Languages")}

    19 |

    20 | {_( 21 | "If you want to use another language other than English you can select it from the following list:" 22 | )} 23 |

    24 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | function prepDataToSubmit(formData) { 38 | const languages = formData.languages 39 | .filter((language) => language.enabled) 40 | .map((language) => language.code); 41 | 42 | return { languages }; 43 | } 44 | 45 | // Hack to disable submit button 46 | function validator(formData) { 47 | if (!formData.enabled) return { enabled: true }; 48 | return null; 49 | } 50 | -------------------------------------------------------------------------------- /js/src/packageManagement/languages/LanguagesForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import { CheckBox } from "foris"; 12 | 13 | LanguagesForm.propTypes = { 14 | formData: PropTypes.shape({ 15 | languages: PropTypes.arrayOf( 16 | PropTypes.shape({ 17 | code: PropTypes.string.isRequired, 18 | enabled: PropTypes.bool.isRequired, 19 | }) 20 | ).isRequired, 21 | }), 22 | setFormValue: PropTypes.func, 23 | disabled: PropTypes.bool, 24 | }; 25 | 26 | export default function LanguagesForm({ formData, setFormValue, disabled }) { 27 | return ( 28 | <> 29 |

    Languages List

    30 |
    31 |
    32 | {formData.languages.map((language, idx) => ( 33 |
    37 | ({ 42 | languages: { 43 | [idx]: { enabled: { $set: value } }, 44 | }, 45 | }))} 46 | /> 47 |
    48 | ))} 49 |
    50 |
    51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /js/src/packageManagement/languages/__tests__/__fixtures__/languages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | export default function languagesFixture(UpdatesEnabled = true) { 9 | return { 10 | enabled: UpdatesEnabled, 11 | languages: [ 12 | { 13 | code: "cs", 14 | enabled: true, 15 | }, 16 | { 17 | code: "da", 18 | enabled: false, 19 | }, 20 | { 21 | code: "de", 22 | enabled: false, 23 | }, 24 | { 25 | code: "fr", 26 | enabled: false, 27 | }, 28 | { 29 | code: "lt", 30 | enabled: false, 31 | }, 32 | { 33 | code: "pl", 34 | enabled: false, 35 | }, 36 | { 37 | code: "ru", 38 | enabled: false, 39 | }, 40 | { 41 | code: "sk", 42 | enabled: false, 43 | }, 44 | { 45 | code: "hu", 46 | enabled: false, 47 | }, 48 | { 49 | code: "it", 50 | enabled: false, 51 | }, 52 | { 53 | code: "nb", 54 | enabled: false, 55 | }, 56 | ], 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /js/src/packageManagement/packages/Labels/Label.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | .badge { 9 | margin-left: 0.5rem; 10 | } 11 | 12 | .badge-disabled { 13 | opacity: 0.5; 14 | } 15 | 16 | span.badge { 17 | cursor: help; 18 | } 19 | -------------------------------------------------------------------------------- /js/src/packageManagement/packages/Labels/Label.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | 11 | import "./Label.css"; 12 | 13 | Label.propTypes = { 14 | title: PropTypes.string.isRequired, 15 | description: PropTypes.string.isRequired, 16 | severity: PropTypes.oneOf([ 17 | "danger", 18 | "warning", 19 | "info", 20 | "success", 21 | "primary", 22 | "secondary", 23 | "light", 24 | "dark", 25 | ]).isRequired, 26 | disabled: PropTypes.bool, 27 | }; 28 | 29 | export default function Label({ title, description, severity, disabled }) { 30 | return ( 31 | 37 | {title} 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /js/src/packageManagement/packages/Labels/Labels.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/) 3 | * 4 | * This is free software, licensed under the GNU General Public License v3. 5 | * See /LICENSE for more information. 6 | */ 7 | 8 | import React from "react"; 9 | import PropTypes from "prop-types"; 10 | import Label from "./Label"; 11 | 12 | Labels.propTypes = { 13 | labels: PropTypes.arrayOf(PropTypes.object).isRequired, 14 | disabled: PropTypes.bool, 15 | }; 16 | 17 | export default function Labels({ labels, disabled }) { 18 | return labels.map((label) => ( 19 |