diff --git a/.circleci/config.yml b/.circleci/config.yml index ee2764a..54900e3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,6 +29,10 @@ jobs: - v1-build-{{ checksum "package-lock.json" }} - v1-build- - *install_packages + - run: + name: "Check Formatting" + command: | + npm run check-format - *test - run: name: "Build" diff --git a/package-lock.json b/package-lock.json index b2469c4..9536876 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6523,14 +6523,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6550,8 +6548,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -6699,7 +6696,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -11724,7 +11720,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true } diff --git a/package.json b/package.json index 2d0e930..a7755e1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "node-fetch": "^2.1.2", "node-sass-chokidar": "^1.3.0", "npm-run-all": "^4.1.3", + "prettier": "^1.14.3", "react-scripts": "^2.0.5", "react-test-renderer": "^16.4.1" }, @@ -53,7 +54,9 @@ "watch-css": "node-sass-chokidar --include-path ./node_modules ./src/scss -o ./src/scss --watch --recursive", "update-translations": "node update-translations.js", "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "format": "prettier --write \"src/**/*.js\"", + "check-format": "prettier --list-different \"src/**/*.js\"" }, "browserslist": [ ">0.2%", diff --git a/src/components/common/Alert.js b/src/components/common/Alert.js index 514dd1a..c39ae93 100644 --- a/src/components/common/Alert.js +++ b/src/components/common/Alert.js @@ -8,12 +8,14 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import PropTypes from "prop-types"; -const Alert = (props) => { +const Alert = props => { return ( -
+
diff --git a/src/components/common/Alert.test.js b/src/components/common/Alert.test.js index 5b2c9bf..5f33193 100644 --- a/src/components/common/Alert.test.js +++ b/src/components/common/Alert.test.js @@ -8,23 +8,27 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { shallow } from 'enzyme'; -import Alert from './Alert'; +import React from "react"; +import { shallow } from "enzyme"; +import Alert from "./Alert"; -it('renders an alert correctly', () => { +it("renders an alert correctly", () => { const message = "test message"; const onClick = jest.fn(); - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper).toHaveClassName("alert-info"); expect(wrapper).toIncludeText(message); }); -it('calls onClick when clicked', () => { +it("calls onClick when clicked", () => { const message = "test message"; const onClick = jest.fn(); - const wrapper = shallow(); + const wrapper = shallow( + + ); wrapper.find("button").simulate("click"); expect(onClick).toBeCalled(); diff --git a/src/components/common/BoxedLayoutButton.js b/src/components/common/BoxedLayoutButton.js index 4618ec7..252002f 100755 --- a/src/components/common/BoxedLayoutButton.js +++ b/src/components/common/BoxedLayoutButton.js @@ -8,24 +8,24 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; +import React from "react"; const boxedLayoutToggle = () => { - document.body.classList.toggle('boxcontainer'); - document.body.classList.toggle('background-image'); + document.body.classList.toggle("boxcontainer"); + document.body.classList.toggle("background-image"); }; export default () => { return (
-
- ) -} - - - + ); +}; diff --git a/src/components/common/BoxedLayoutButton.test.js b/src/components/common/BoxedLayoutButton.test.js index 26c8f6a..8785a9f 100644 --- a/src/components/common/BoxedLayoutButton.test.js +++ b/src/components/common/BoxedLayoutButton.test.js @@ -8,16 +8,16 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { shallow } from 'enzyme'; -import BoxedLayoutButton from './BoxedLayoutButton'; +import React from "react"; +import { shallow } from "enzyme"; +import BoxedLayoutButton from "./BoxedLayoutButton"; it("renders without crashing", () => { - shallow(); + shallow(); }); it("toggles boxed layout", () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(document.body.classList).not.toContain("boxcontainer"); expect(document.body.classList).not.toContain("background-image"); diff --git a/src/components/common/Footer.js b/src/components/common/Footer.js index 4930b1e..fc0f7b9 100644 --- a/src/components/common/Footer.js +++ b/src/components/common/Footer.js @@ -8,7 +8,7 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; +import React from "react"; import { translate } from "react-i18next"; const Footer = props => { @@ -17,12 +17,14 @@ const Footer = props => { return (
- + -  {t("Donate")} +   + {t("Donate")} - {t("if you found this useful")} + {" "} + {t("if you found this useful")}
{t("Core Version")}: diff --git a/src/components/common/Footer.test.js b/src/components/common/Footer.test.js index 5a9bfc1..405b9e4 100644 --- a/src/components/common/Footer.test.js +++ b/src/components/common/Footer.test.js @@ -8,10 +8,10 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { shallow } from 'enzyme'; +import React from "react"; +import { shallow } from "enzyme"; import Footer from "./Footer"; -it('renders without crashing', () => { - shallow(
); +it("renders without crashing", () => { + shallow(
); }); diff --git a/src/components/common/Header.js b/src/components/common/Header.js index 1e76799..61c15ce 100644 --- a/src/components/common/Header.js +++ b/src/components/common/Header.js @@ -8,7 +8,7 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; +import React from "react"; import { Link } from "react-router-dom"; import BoxedLayoutButton from "./BoxedLayoutButton"; @@ -19,16 +19,16 @@ import BoxedLayoutButton from "./BoxedLayoutButton"; const sidebarMinimize = e => { e.preventDefault(); - document.body.classList.toggle('sidebar-minimized'); - document.body.classList.toggle('brand-minimized'); + document.body.classList.toggle("sidebar-minimized"); + document.body.classList.toggle("brand-minimized"); }; export const mobileSidebarToggle = () => { - document.body.classList.toggle('sidebar-show'); + document.body.classList.toggle("sidebar-show"); }; export const mobileSidebarHide = () => { - document.body.classList.remove('sidebar-show'); + document.body.classList.remove("sidebar-show"); }; //const asideToggle = e => { @@ -38,20 +38,36 @@ export const mobileSidebarHide = () => { export default () => (
- - - - Pi-hole - Ph + + + + Pi- + hole + + + Ph +
  • -
  • diff --git a/src/components/common/Sidebar.js b/src/components/common/Sidebar.js index cd66fc1..4c29eb4 100644 --- a/src/components/common/Sidebar.js +++ b/src/components/common/Sidebar.js @@ -17,18 +17,26 @@ import { mobileSidebarHide } from "./Header"; import { api } from "../../utils"; import StatusBadge from "./StatusBadge"; -const handleClick = (e) => { +const handleClick = e => { e.preventDefault(); e.target.parentElement.classList.toggle("open"); }; export const dropDownClassList = (routeName, props) => - props.location.pathname.startsWith(routeName) ? "nav-item nav-dropdown open" : "nav-item nav-dropdown"; + props.location.pathname.startsWith(routeName) + ? "nav-item nav-dropdown open" + : "nav-item nav-dropdown"; export const navItem = (item, key, props) => ( - - {props.t(item.name)} + + + {props.t(item.name)} ); @@ -36,26 +44,25 @@ export const navItem = (item, key, props) => ( export const navDropdown = (item, key, props) => (
  • -
      - {navList(item.children, props)} -
    +
      {navList(item.children, props)}
  • ); export const navList = (items, props) => items.map((item, index) => { // Don't show an item if it requires auth and we're not logged in - if(item.auth && !api.loggedIn) - return null; + if (item.auth && !api.loggedIn) return null; // Some items (login page) should only be shown when logged in or logged out, not both - if(item.authStrict && item.auth !== api.loggedIn) - return null; + if (item.authStrict && item.auth !== api.loggedIn) return null; // At this point it's ok to show the item - return item.children ? navDropdown(item, index, props) : navItem(item, index, props); + return item.children + ? navDropdown(item, index, props) + : navItem(item, index, props); }); const Sidebar = ({ items, ...props }) => { @@ -64,17 +71,28 @@ const Sidebar = ({ items, ...props }) => {

{this.state.totalQueries}

-

- {t("Total Queries ({{count}} clients)", { count: this.state.uniqueClients })} +

+ {t("Total Queries ({{count}} clients)", { + count: this.state.uniqueClients + })}

@@ -85,9 +91,7 @@ class SummaryStats extends Component {

{this.state.blockedQueries}

-

- {t("Queries Blocked")} -

+

{t("Queries Blocked")}

@@ -100,9 +104,7 @@ class SummaryStats extends Component {

{this.state.percentBlocked}

-

- {t("Percent Blocked")} -

+

{t("Percent Blocked")}

@@ -110,14 +112,12 @@ class SummaryStats extends Component {
- +

{this.state.gravityDomains}

-

- {t("Domains On Blocklist")} -

+

{t("Domains On Blocklist")}

@@ -126,4 +126,4 @@ class SummaryStats extends Component { } } -export default translate(['common', 'dashboard'])(SummaryStats); +export default translate(["common", "dashboard"])(SummaryStats); diff --git a/src/components/dashboard/SummaryStats.test.js b/src/components/dashboard/SummaryStats.test.js index e5cf035..e939a93 100644 --- a/src/components/dashboard/SummaryStats.test.js +++ b/src/components/dashboard/SummaryStats.test.js @@ -30,22 +30,30 @@ const fakeData = { it("displays summary stats correctly", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow(); + const wrapper = shallow(); await tick(); wrapper.update(); - expect(wrapper.childAt(0).find("h3")).toHaveText(fakeData.total_queries.toLocaleString()); + expect(wrapper.childAt(0).find("h3")).toHaveText( + fakeData.total_queries.toLocaleString() + ); expect(wrapper.state().uniqueClients).toEqual(fakeData.unique_clients); - expect(wrapper.childAt(1).find("h3")).toHaveText(fakeData.blocked_queries.toLocaleString()); - expect(wrapper.childAt(2).find("h3")).toHaveText(fakeData.percent_blocked.toFixed(2).toLocaleString() + "%"); - expect(wrapper.childAt(3).find("h3")).toHaveText(fakeData.domains_blocked.toLocaleString()); + expect(wrapper.childAt(1).find("h3")).toHaveText( + fakeData.blocked_queries.toLocaleString() + ); + expect(wrapper.childAt(2).find("h3")).toHaveText( + fakeData.percent_blocked.toFixed(2).toLocaleString() + "%" + ); + expect(wrapper.childAt(3).find("h3")).toHaveText( + fakeData.domains_blocked.toLocaleString() + ); }); it("displays an error message on error", async () => { fetchMock.mock(endpoint, { error: {} }); - const wrapper = shallow(); + const wrapper = shallow(); await tick(); wrapper.update(); diff --git a/src/components/dashboard/TopBlocked.js b/src/components/dashboard/TopBlocked.js index ce92206..8d9902a 100644 --- a/src/components/dashboard/TopBlocked.js +++ b/src/components/dashboard/TopBlocked.js @@ -21,11 +21,7 @@ const TopBlocked = ({ t, ...props }) => ( total_blocked: 0, top_blocked: [] }} - headers={[ - t("Domain"), - t("Hits"), - t("Frequency") - ]} + headers={[t("Domain"), t("Hits"), t("Frequency")]} emptyMessage={t("No Domains Found")} isEmpty={state => state.top_blocked.length === 0} apiCall={api.getTopBlocked} @@ -38,31 +34,31 @@ const TopBlocked = ({ t, ...props }) => ( }} generateRows={state => { return state.top_blocked.map(item => { - const percentage = item.count / state.total_blocked * 100; + const percentage = (item.count / state.total_blocked) * 100; return ( - - {item.domain} - - - {item.count.toLocaleString()} - - -
-
+ {item.domain} + {item.count.toLocaleString()} + +
+
); }); - }}/> + }} + /> ); export default translate(["common", "dashboard"])(TopBlocked); diff --git a/src/components/dashboard/TopBlocked.test.js b/src/components/dashboard/TopBlocked.test.js index 9cb23f2..c4ca5ae 100644 --- a/src/components/dashboard/TopBlocked.test.js +++ b/src/components/dashboard/TopBlocked.test.js @@ -33,7 +33,7 @@ const fakeData = { it("loads the API data into state correctly", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow().dive(); + const wrapper = shallow().dive(); await tick(); wrapper.update(); @@ -45,11 +45,13 @@ it("loads the API data into state correctly", async () => { it("creates an appropriately sized table", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow().dive(); + const wrapper = shallow().dive(); await tick(); wrapper.update(); // Add one to the expected length to account for the table header - expect(wrapper.find("tbody").children("tr")).toHaveLength(fakeData.top_blocked.length + 1); + expect(wrapper.find("tbody").children("tr")).toHaveLength( + fakeData.top_blocked.length + 1 + ); }); diff --git a/src/components/dashboard/TopClients.js b/src/components/dashboard/TopClients.js index 493c487..a1c342d 100644 --- a/src/components/dashboard/TopClients.js +++ b/src/components/dashboard/TopClients.js @@ -21,11 +21,7 @@ const TopClients = ({ t, ...props }) => ( total_queries: 0, top_clients: [] }} - headers={[ - t("Client"), - t("Requests"), - t("Frequency") - ]} + headers={[t("Client"), t("Requests"), t("Frequency")]} emptyMessage={t("No Clients Found")} isEmpty={state => state.top_clients.length === 0} apiCall={api.getTopClients} @@ -38,31 +34,31 @@ const TopClients = ({ t, ...props }) => ( }} generateRows={state => { return state.top_clients.map(item => { - const percentage = item.count / state.total_queries * 100; + const percentage = (item.count / state.total_queries) * 100; return ( - - {item.name !== "" ? item.name : item.ip} - - - {item.count.toLocaleString()} - - -
-
+ {item.name !== "" ? item.name : item.ip} + {item.count.toLocaleString()} + +
+
); }); - }}/> + }} + /> ); export default translate(["common", "dashboard"])(TopClients); diff --git a/src/components/dashboard/TopClients.test.js b/src/components/dashboard/TopClients.test.js index 2bdc491..a15070e 100644 --- a/src/components/dashboard/TopClients.test.js +++ b/src/components/dashboard/TopClients.test.js @@ -33,7 +33,7 @@ const fakeData = { it("loads the API data into state correctly", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow().dive(); + const wrapper = shallow().dive(); await tick(); wrapper.update(); @@ -45,11 +45,13 @@ it("loads the API data into state correctly", async () => { it("creates an appropriately sized table", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow().dive(); + const wrapper = shallow().dive(); await tick(); wrapper.update(); // Add one to the expected length to account for the table header - expect(wrapper.find("tbody").children("tr")).toHaveLength(fakeData.top_clients.length + 1); + expect(wrapper.find("tbody").children("tr")).toHaveLength( + fakeData.top_clients.length + 1 + ); }); diff --git a/src/components/dashboard/TopDomains.js b/src/components/dashboard/TopDomains.js index db6dc06..92d6072 100644 --- a/src/components/dashboard/TopDomains.js +++ b/src/components/dashboard/TopDomains.js @@ -21,11 +21,7 @@ const TopDomains = ({ t, ...props }) => ( total_queries: 0, top_domains: [] }} - headers={[ - t("Domain"), - t("Hits"), - t("Frequency") - ]} + headers={[t("Domain"), t("Hits"), t("Frequency")]} emptyMessage={t("No Domains Found")} isEmpty={state => state.top_domains.length === 0} apiCall={api.getTopDomains} @@ -38,31 +34,31 @@ const TopDomains = ({ t, ...props }) => ( }} generateRows={state => { return state.top_domains.map(item => { - const percentage = item.count / state.total_queries * 100; + const percentage = (item.count / state.total_queries) * 100; return ( - - {item.domain} - - - {item.count.toLocaleString()} - - -
-
+ {item.domain} + {item.count.toLocaleString()} + +
+
); }); - }}/> + }} + /> ); export default translate(["common", "dashboard"])(TopDomains); diff --git a/src/components/dashboard/TopDomains.test.js b/src/components/dashboard/TopDomains.test.js index 467b73a..f90185c 100644 --- a/src/components/dashboard/TopDomains.test.js +++ b/src/components/dashboard/TopDomains.test.js @@ -33,7 +33,7 @@ const fakeData = { it("loads the API data into state correctly", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow().dive(); + const wrapper = shallow().dive(); await tick(); wrapper.update(); @@ -45,11 +45,13 @@ it("loads the API data into state correctly", async () => { it("creates an appropriately sized table", async () => { fetchMock.mock(endpoint, fakeData); - const wrapper = shallow().dive(); + const wrapper = shallow().dive(); await tick(); wrapper.update(); // Add one to the expected length to account for the table header - expect(wrapper.find("tbody").children("tr")).toHaveLength(fakeData.top_domains.length + 1); + expect(wrapper.find("tbody").children("tr")).toHaveLength( + fakeData.top_domains.length + 1 + ); }); diff --git a/src/components/dashboard/TopTable.js b/src/components/dashboard/TopTable.js index 1bda899..f117698 100644 --- a/src/components/dashboard/TopTable.js +++ b/src/components/dashboard/TopTable.js @@ -19,27 +19,29 @@ class TopTable extends Component { }; updateChart = () => { - this.updateHandler = makeCancelable( - this.props.apiCall(), - { repeat: this.updateChart, interval: 10 * 60 * 1000 } - ); + this.updateHandler = makeCancelable(this.props.apiCall(), { + repeat: this.updateChart, + interval: 10 * 60 * 1000 + }); this.updateHandler.promise .then(res => this.props.apiHandler(this, res)) .catch(ignoreCancel); }; generateTable = () => { - if(this.props.isEmpty(this.state)) { + if (this.props.isEmpty(this.state)) { return this.props.emptyMessage; } return ( - - {this.props.headers.map((header, i) => )} - - {this.props.generateRows(this.state)} + + {this.props.headers.map((header, i) => ( + + ))} + + {this.props.generateRows(this.state)}
{header}
{header}
); @@ -56,24 +58,26 @@ class TopTable extends Component { render() { return (
-
- {this.props.title} -
+
{this.props.title}
-
- {this.generateTable()} -
+
{this.generateTable()}
- { - this.state.loading - ? -
- -
- : - null - } + {this.state.loading ? ( +
+ +
+ ) : null}
); } diff --git a/src/components/dashboard/TopTable.test.js b/src/components/dashboard/TopTable.test.js index 815c395..5d7bda8 100644 --- a/src/components/dashboard/TopTable.test.js +++ b/src/components/dashboard/TopTable.test.js @@ -21,7 +21,8 @@ it("shows loading indicator before first load", () => { isEmpty={() => true} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); expect(wrapper.state().loading).toBeTruthy(); @@ -37,7 +38,8 @@ it("hides loading indicator after first load", async () => { isEmpty={() => true} apiCall={() => Promise.resolve()} apiHandler={self => self.setState({ loading: false })} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); await tick(); @@ -59,7 +61,8 @@ it("sets the initial state", () => { isEmpty={() => true} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); expect(wrapper.state().testInitialState).toEqual(true); @@ -75,18 +78,15 @@ it("sets the title", () => { isEmpty={() => true} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); expect(wrapper.find(".card-header")).toHaveText(title); }); it("adds the headers", () => { - const headers = [ - "header1", - "header2", - "header3" - ]; + const headers = ["header1", "header2", "header3"]; const wrapper = shallow( { isEmpty={() => false} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); expect( @@ -119,7 +120,8 @@ it("shows the empty message when empty", () => { isEmpty={() => true} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); expect(wrapper.find(".card-body").childAt(0)).toHaveText(emptyMessage); @@ -137,7 +139,8 @@ it("calls apiCall and apiHandler to retrieve data", async () => { isEmpty={() => true} apiCall={apiCall} apiHandler={apiHandler} - generateRows={jest.fn()}/> + generateRows={jest.fn()} + /> ); await tick(); @@ -157,7 +160,8 @@ it("does not call generateRows when empty", () => { isEmpty={() => true} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={generateRows}/> + generateRows={generateRows} + /> ); expect(generateRows).not.toHaveBeenCalled(); @@ -175,7 +179,8 @@ it("calls generateRows with state when not empty", () => { isEmpty={() => false} apiCall={ignoreAPI} apiHandler={jest.fn()} - generateRows={generateRows}/> + generateRows={generateRows} + /> ); expect(generateRows).toHaveBeenCalledWith(state); diff --git a/src/components/list/DomainInput.js b/src/components/list/DomainInput.js index badf46f..c6c1d9b 100644 --- a/src/components/list/DomainInput.js +++ b/src/components/list/DomainInput.js @@ -8,9 +8,9 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { translate } from 'react-i18next'; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { translate } from "react-i18next"; import { api } from "../../utils"; class DomainInput extends Component { @@ -19,10 +19,9 @@ class DomainInput extends Component { isValid: true }; - handleChange = (e) => - this.setState({ domain: e.target.value }); + handleChange = e => this.setState({ domain: e.target.value }); - handleSubmit = (e) => { + handleSubmit = e => { e.preventDefault(); const domain = this.state.domain; @@ -51,15 +50,17 @@ class DomainInput extends Component { disabled={!api.loggedIn} /> - { - api.loggedIn ? - - : null - } - + ) : null} + @@ -76,7 +77,7 @@ DomainInput.propTypes = { }; DomainInput.defaultProps = { - placeholder: '' + placeholder: "" }; export default translate(["common", "lists"])(DomainInput); diff --git a/src/components/list/DomainInput.test.js b/src/components/list/DomainInput.test.js index 4729312..57a90b3 100644 --- a/src/components/list/DomainInput.test.js +++ b/src/components/list/DomainInput.test.js @@ -8,107 +8,199 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { shallow } from 'enzyme'; -import DomainInput from './DomainInput'; +import React from "react"; +import { shallow } from "enzyme"; +import DomainInput from "./DomainInput"; import { api } from "../../utils"; import { isValidDomain } from "../../validate"; -it('has a placeholder', () => { - const placeholder = 'placeholder'; - const wrapper = shallow(); +it("has a placeholder", () => { + const placeholder = "placeholder"; + const wrapper = shallow( + + ); - expect(wrapper.find('input')).toHaveProp('placeholder', placeholder); + expect(wrapper.find("input")).toHaveProp("placeholder", placeholder); }); -it('sets state to input', () => { +it("sets state to input", () => { const domain = "domain"; - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('input').simulate('change', { target: { value: domain } }); + wrapper.find("input").simulate("change", { target: { value: domain } }); expect(wrapper.state().domain).toEqual(domain); }); -it('only has one button when not logged in', () => { - const wrapper = shallow(); +it("only has one button when not logged in", () => { + const wrapper = shallow( + + ); - expect(wrapper.find('button')).toHaveLength(1); + expect(wrapper.find("button")).toHaveLength(1); }); -it('disables input when not logged in', () => { - const wrapper = shallow(); +it("disables input when not logged in", () => { + const wrapper = shallow( + + ); - expect(wrapper.find('input')).toBeDisabled(); + expect(wrapper.find("input")).toBeDisabled(); }); -it('enables input when logged in', () => { +it("enables input when logged in", () => { api.loggedIn = true; - const wrapper = shallow(); + const wrapper = shallow( + + ); - expect(wrapper.find('input')).not.toBeDisabled(); + expect(wrapper.find("input")).not.toBeDisabled(); }); -it('calls onRefresh when the refresh button is clicked', () => { +it("calls onRefresh when the refresh button is clicked", () => { const onRefresh = jest.fn(); - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('button').last().simulate('click'); + wrapper + .find("button") + .last() + .simulate("click"); expect(onRefresh).toHaveBeenCalled(); }); -it('has two buttons when logged in', () => { +it("has two buttons when logged in", () => { api.loggedIn = true; - const wrapper = shallow(); + const wrapper = shallow( + + ); - expect(wrapper.find('button')).toHaveLength(2); + expect(wrapper.find("button")).toHaveLength(2); }); -it('does not call onEnter when input is empty', () => { +it("does not call onEnter when input is empty", () => { api.loggedIn = true; const onEnter = jest.fn(); - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('button').first().simulate('click'); + wrapper + .find("button") + .first() + .simulate("click"); expect(onEnter).not.toHaveBeenCalled(); }); -it('calls onEnter when input is not empty', () => { +it("calls onEnter when input is not empty", () => { api.loggedIn = true; - const domain = 'domain.com'; + const domain = "domain.com"; const onEnter = jest.fn(); - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('input').simulate('change', { target: { value: domain } }); - wrapper.find('form').first().simulate('submit', { preventDefault: jest.fn() }); + wrapper.find("input").simulate("change", { target: { value: domain } }); + wrapper + .find("form") + .first() + .simulate("submit", { preventDefault: jest.fn() }); expect(onEnter).toHaveBeenCalledWith(domain); }); -it('clears input after clicking add button', () => { +it("clears input after clicking add button", () => { api.loggedIn = true; - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('input').simulate('change', { target: { value: 'domain.com' } }); - wrapper.find('form').first().simulate('submit', { preventDefault: jest.fn() }); + wrapper.find("input").simulate("change", { target: { value: "domain.com" } }); + wrapper + .find("form") + .first() + .simulate("submit", { preventDefault: jest.fn() }); - expect(wrapper.state().domain).toEqual(''); + expect(wrapper.state().domain).toEqual(""); }); // Tests to check the validation functionality it("sets state.isValid to true when domain is properly formatted", () => { api.loggedIn = true; - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('input').simulate('change', { target: { value: "valid.domain" }}); + wrapper + .find("input") + .simulate("change", { target: { value: "valid.domain" } }); expect(wrapper.state().domain).toEqual("valid.domain"); expect(wrapper.state().isValid).toEqual(true); }); @@ -116,21 +208,45 @@ it("sets state.isValid to true when domain is properly formatted", () => { it("sets state.isValid to false when domain is not properly formatted", () => { api.loggedIn = true; - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('input').simulate('change', { target: { value: "invalid.domain." }}); + wrapper + .find("input") + .simulate("change", { target: { value: "invalid.domain." } }); expect(wrapper.state().domain).toEqual("invalid.domain."); - wrapper.find('form').first().simulate('submit', { preventDefault: jest.fn() }); + wrapper + .find("form") + .first() + .simulate("submit", { preventDefault: jest.fn() }); expect(wrapper.state().isValid).toEqual(false); }); it("sets is-invalid class to the input when domain is not properly formatted", () => { api.loggedIn = true; - const wrapper = shallow(); + const wrapper = shallow( + + ); - wrapper.find('input').simulate('change', { target: { value: "invalid.domain." }}); - wrapper.find('form').first().simulate('submit', { preventDefault: jest.fn() }); - expect(wrapper.find('input').hasClass('is-valid')).toBe(false); - expect(wrapper.find('input').hasClass('is-invalid')).toBe(true); + wrapper + .find("input") + .simulate("change", { target: { value: "invalid.domain." } }); + wrapper + .find("form") + .first() + .simulate("submit", { preventDefault: jest.fn() }); + expect(wrapper.find("input").hasClass("is-valid")).toBe(false); + expect(wrapper.find("input").hasClass("is-invalid")).toBe(true); }); diff --git a/src/components/list/DomainList.js b/src/components/list/DomainList.js index a6260ff..ccf6fc6 100644 --- a/src/components/list/DomainList.js +++ b/src/components/list/DomainList.js @@ -8,14 +8,14 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { translate } from 'react-i18next'; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { translate } from "react-i18next"; import { api, ignoreCancel, makeCancelable } from "../../utils"; class DomainList extends Component { onRemove(domain) { - if(this.props.domains.includes(domain)) { + if (this.props.domains.includes(domain)) { const prevDomains = this.props.domains.slice(); this.removeHandler = makeCancelable(this.props.apiCall(domain)); @@ -32,29 +32,35 @@ class DomainList extends Component { return (
    - { - this.props.domains.length > 0 - ? - this.props.domains.map(item => ( -
  • - { - api.loggedIn ? - - : null - } - - {item} - -
  • - )) - : -
    - {t("There are no domains in this list")} -
    - } + {this.props.domains.length > 0 ? ( + this.props.domains.map(item => ( +
  • + {api.loggedIn ? ( + + ) : null} + + {item} + +
  • + )) + ) : ( +
    + {t("There are no domains in this list")} +
    + )}
); } @@ -71,4 +77,4 @@ DomainList.defaultProps = { domains: [] }; -export default translate(["common", "lists"])(DomainList); \ No newline at end of file +export default translate(["common", "lists"])(DomainList); diff --git a/src/components/list/DomainList.test.js b/src/components/list/DomainList.test.js index 75e5b34..ab04e30 100644 --- a/src/components/list/DomainList.test.js +++ b/src/components/list/DomainList.test.js @@ -8,81 +8,125 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { shallow } from 'enzyme'; -import DomainList from './DomainList'; +import React from "react"; +import { shallow } from "enzyme"; +import DomainList from "./DomainList"; import { api } from "../../utils"; -const domains = [ - 'domain1.com', - 'domain2.com', - 'domain3.com' -]; +const domains = ["domain1.com", "domain2.com", "domain3.com"]; -it('shows a list of domains', () => { +it("shows a list of domains", () => { const wrapper = shallow( - + ); - expect(wrapper.find('li')).toHaveLength(domains.length); + expect(wrapper.find("li")).toHaveLength(domains.length); }); -it('shows an alert if there are no domains', () => { +it("shows an alert if there are no domains", () => { const wrapper = shallow( - + ); - expect(wrapper.find('li')).toHaveLength(0); - expect(wrapper.find('ul').childAt(0)).toHaveClassName('alert-info'); - expect(wrapper).toIncludeText('There are no domains in this list'); + expect(wrapper.find("li")).toHaveLength(0); + expect(wrapper.find("ul").childAt(0)).toHaveClassName("alert-info"); + expect(wrapper).toIncludeText("There are no domains in this list"); }); -it('does not have a delete button when not logged in', () => { +it("does not have a delete button when not logged in", () => { const wrapper = shallow( - + ); - expect(wrapper.find("ul").childAt(0).find("button")).not.toExist(); + expect( + wrapper + .find("ul") + .childAt(0) + .find("button") + ).not.toExist(); }); -it('has a delete button when logged in', () => { +it("has a delete button when logged in", () => { api.loggedIn = true; const wrapper = shallow( - + ); - expect(wrapper.find("ul").childAt(0).find("button")).toExist(); + expect( + wrapper + .find("ul") + .childAt(0) + .find("button") + ).toExist(); }); -it('calls onRemoved and API callback when a domain is removed', () => { +it("calls onRemoved and API callback when a domain is removed", () => { api.loggedIn = true; const onRemoved = jest.fn(); const onFailed = jest.fn(); const apiCall = jest.fn(); const wrapper = shallow( - + ); - wrapper.find("ul").childAt(0).find("button").simulate("click"); + wrapper + .find("ul") + .childAt(0) + .find("button") + .simulate("click"); expect(onRemoved).toHaveBeenCalled(); expect(apiCall).toHaveBeenCalled(); expect(onFailed).not.toHaveBeenCalled(); }); -it('calls all callbacks when a domain is removed and the API call fails', async () => { +it("calls all callbacks when a domain is removed and the API call fails", async () => { api.loggedIn = true; const onRemoved = jest.fn(); const onFailed = jest.fn(); const apiCall = jest.fn(() => Promise.reject(new Error())); const wrapper = shallow( - + ); - wrapper.find("ul").childAt(0).find("button").simulate("click"); + wrapper + .find("ul") + .childAt(0) + .find("button") + .simulate("click"); await tick(); diff --git a/src/components/list/ListPage.js b/src/components/list/ListPage.js index 3d4bb29..8c99ca0 100644 --- a/src/components/list/ListPage.js +++ b/src/components/list/ListPage.js @@ -8,9 +8,9 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { translate } from 'react-i18next'; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { translate } from "react-i18next"; import DomainInput from "./DomainInput"; import Alert from "../common/Alert"; import DomainList from "./DomainList"; @@ -26,7 +26,7 @@ class ListPage extends Component { onEnter = domain => { // Check if the domain was already added - if(this.state.domains.includes(domain)) { + if (this.state.domains.includes(domain)) { this.onAlreadyAdded(domain); } else { // Store the domains before adding the new domain, for a possible rollback @@ -34,9 +34,10 @@ class ListPage extends Component { // Try to add the domain this.addHandler = makeCancelable(this.props.add(domain)); - this.addHandler.promise.then(() => { - this.onAdded(domain); - }) + this.addHandler.promise + .then(() => { + this.onAdded(domain); + }) .catch(ignoreCancel) .catch(() => { this.onAddFailed(domain, prevDomains); @@ -92,9 +93,10 @@ class ListPage extends Component { onRefresh = () => { this.refreshHandler = makeCancelable(this.props.refresh()); - this.refreshHandler.promise.then(data => { - this.setState({ domains: data }); - }) + this.refreshHandler.promise + .then(data => { + this.setState({ domains: data }); + }) .catch(ignoreCancel); }; @@ -109,48 +111,53 @@ class ListPage extends Component { } componentWillUnmount() { - if(this.addHandler) - this.addHandler.cancel(); + if (this.addHandler) this.addHandler.cancel(); - if(this.removeHandler) - this.removeHandler.cancel(); + if (this.removeHandler) this.removeHandler.cancel(); - if(this.refreshHandler) - this.refreshHandler.cancel(); + if (this.refreshHandler) this.refreshHandler.cancel(); } render() { return (

{this.props.title}

-
+
- { this.props.note } - { - this.state.infoMsg - ? this.setState({ infoMsg: "" })}/> - : null - } - { - this.state.successMsg - ? this.setState({ successMsg: "" })}/> - : null - } - { - this.state.errorMsg - ? this.setState({ errorMsg: "" })}/> - : null - } + onValidationError={this.handleValidationError} + /> + {this.props.note} + {this.state.infoMsg ? ( + this.setState({ infoMsg: "" })} + /> + ) : null} + {this.state.successMsg ? ( + this.setState({ successMsg: "" })} + /> + ) : null} + {this.state.errorMsg ? ( + this.setState({ errorMsg: "" })} + /> + ) : null} + onFailed={this.onRemoveFailed} + />
); } diff --git a/src/components/list/ListPage.test.js b/src/components/list/ListPage.test.js index 79daa45..112a264 100644 --- a/src/components/list/ListPage.test.js +++ b/src/components/list/ListPage.test.js @@ -23,7 +23,8 @@ it("shows the title", () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); expect(wrapper.find("h2")).toHaveText(title); @@ -40,7 +41,8 @@ it("shows the placeholder", () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); expect(wrapper.find("DomainInput")).toHaveProp("placeholder", placeholder); @@ -57,7 +59,8 @@ it("shows the note", () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); expect(wrapper).toIncludeText(note); @@ -73,7 +76,8 @@ it("starts with no alerts shown", () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); expect(wrapper.find("Alert")).toHaveLength(0); @@ -90,7 +94,8 @@ it("loads domains after mounting", async () => { refresh={() => Promise.resolve(domains)} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); await tick(); @@ -111,7 +116,8 @@ it("checks if the domain was already added", async () => { refresh={() => Promise.resolve(domains)} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); // Setup with domains (wait for promise to resolve) and mock function @@ -137,7 +143,8 @@ it("calls the add prop when adding a domain", () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onEnter(domain); @@ -157,7 +164,8 @@ it("calls onAdding when adding a domain", () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onAdding = onAdding; @@ -179,7 +187,8 @@ it("calls onAdded after API request succeeds", async () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onAdded = onAdded; @@ -202,7 +211,8 @@ it("calls onAddFailed after API request fails", async () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onAddFailed = onAddFailed; @@ -224,7 +234,8 @@ it("adds the domain in onAdded", async () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onEnter(domain); @@ -245,7 +256,8 @@ it("resets the domains when adding failed", async () => { refresh={ignoreAPI} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onEnter(domain); @@ -267,7 +279,8 @@ it("removes the domain when onRemoved is called", async () => { refresh={() => Promise.resolve(domains)} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onRemoved(domain); @@ -288,7 +301,8 @@ it("resets the domains when removal failed", () => { refresh={() => Promise.resolve(domains)} remove={ignoreAPI} isValid={jest.fn()} - validationErrorMsg=""/> + validationErrorMsg="" + /> ); wrapper.instance().onRemoveFailed(domain, domains); wrapper.update(); diff --git a/src/components/log/QueryLog.js b/src/components/log/QueryLog.js index 723be74..20ef6c3 100644 --- a/src/components/log/QueryLog.js +++ b/src/components/log/QueryLog.js @@ -8,11 +8,11 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Component } from 'react'; -import ReactTable from 'react-table'; -import { translate } from 'react-i18next'; -import { api, ignoreCancel, makeCancelable, padNumber } from '../../utils'; -import 'react-table/react-table.css'; +import React, { Component } from "react"; +import ReactTable from "react-table"; +import { translate } from "react-i18next"; +import { api, ignoreCancel, makeCancelable, padNumber } from "../../utils"; +import "react-table/react-table.css"; class QueryLog extends Component { updateHandler = null; @@ -28,12 +28,14 @@ class QueryLog extends Component { updateTable() { this.updateHandler = makeCancelable(api.getHistory()); - this.updateHandler.promise.then(data => { - this.setState({ - history: data, - loading: false - }); - }).catch(ignoreCancel); + this.updateHandler.promise + .then(data => { + this.setState({ + history: data, + loading: false + }); + }) + .catch(ignoreCancel); } componentDidMount() { @@ -57,19 +59,21 @@ class QueryLog extends Component { data={this.state.history} loading={this.state.loading} getTrProps={(state, rowInfo) => { - if(rowInfo && rowInfo.row.status !== 0) + if (rowInfo && rowInfo.row.status !== 0) return { style: { - color: [1, 4, 5].includes(rowInfo.row.status) ? 'red' : 'green' + color: [1, 4, 5].includes(rowInfo.row.status) ? "red" : "green" } }; - else - return {}; + else return {}; }} - defaultSorted={[{ - id: "time", - desc: true - }]} /> + defaultSorted={[ + { + id: "time", + desc: true + } + ]} + /> ); } } @@ -91,7 +95,9 @@ const columns = t => [ Cell: row => { const date = new Date(row.value * 1000); - return `${padNumber(date.getHours())}-${padNumber(date.getMinutes())}-${padNumber(date.getSeconds())}`; + return `${padNumber(date.getHours())}-${padNumber( + date.getMinutes() + )}-${padNumber(date.getSeconds())}`; } }, { @@ -120,27 +126,33 @@ const columns = t => [ accessor: r => r[4], width: 140, Cell: row => status(t)[row.value], - filterMethod: (filter, row) => - status(t)[row[filter.id]] - .toLowerCase() - .includes( - filter.value.toLowerCase() - ) + filterMethod: (filter, row) => { + const rowStatus = status(t)[row[filter.id]].toLowerCase(); + return rowStatus.includes(filter.value.toLowerCase()); + } }, { Header: t("Action"), width: 100, filterable: false, Cell: data => { - if([1, 4, 5].includes(data.row.status)) + if ([1, 4, 5].includes(data.row.status)) return ( - ); - if([2, 3].includes(data.row.status)) + if ([2, 3].includes(data.row.status)) return ( - ); diff --git a/src/components/login/ForgotPassword.js b/src/components/login/ForgotPassword.js index 89f92d3..85f7e8b 100644 --- a/src/components/login/ForgotPassword.js +++ b/src/components/login/ForgotPassword.js @@ -8,9 +8,9 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { translate } from 'react-i18next'; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { translate } from "react-i18next"; class ForgotPassword extends Component { state = { @@ -33,23 +33,47 @@ class ForgotPassword extends Component { const { t } = this.props; return ( -
-
-
-

+
+
+
+

{t("Forgot Password")}

-
-
+
{t("forgot_password_description")} -
sudo pihole -a -p
+
sudo pihole -a -p
@@ -61,4 +85,4 @@ ForgotPassword.propTypes = { error: PropTypes.bool.isRequired }; -export default translate("login")(ForgotPassword); \ No newline at end of file +export default translate("login")(ForgotPassword); diff --git a/src/components/login/ForgotPassword.test.js b/src/components/login/ForgotPassword.test.js index c43350b..602fdab 100644 --- a/src/components/login/ForgotPassword.test.js +++ b/src/components/login/ForgotPassword.test.js @@ -8,39 +8,64 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { shallow } from 'enzyme'; +import React from "react"; +import { shallow } from "enzyme"; import ForgotPassword from "./ForgotPassword"; -it('collapses and displays normal if there is no error', () => { - const wrapper = shallow(); +it("collapses and displays normal if there is no error", () => { + const wrapper = shallow(); - expect(wrapper.childAt(0)).toHaveClassName('border-primary'); - expect(wrapper.childAt(0).childAt(0)).toHaveClassName('bg-primary'); - expect(wrapper.childAt(0).children().last()).toHaveClassName('collapse'); + expect(wrapper.childAt(0)).toHaveClassName("border-primary"); + expect(wrapper.childAt(0).childAt(0)).toHaveClassName("bg-primary"); + expect( + wrapper + .childAt(0) + .children() + .last() + ).toHaveClassName("collapse"); }); -it('expands and displays red if there is an error', () => { - const wrapper = shallow(); +it("expands and displays red if there is an error", () => { + const wrapper = shallow(); - expect(wrapper.childAt(0)).toHaveClassName('border-danger'); - expect(wrapper.childAt(0).childAt(0)).toHaveClassName('bg-danger'); - expect(wrapper.childAt(0).children().last()).not.toHaveClassName('collapse'); + expect(wrapper.childAt(0)).toHaveClassName("border-danger"); + expect(wrapper.childAt(0).childAt(0)).toHaveClassName("bg-danger"); + expect( + wrapper + .childAt(0) + .children() + .last() + ).not.toHaveClassName("collapse"); }); -it('expands and collapses if clicked without error', () => { - const wrapper = shallow(); +it("expands and collapses if clicked without error", () => { + const wrapper = shallow(); - wrapper.find('button').simulate('click'); - expect(wrapper.childAt(0).children().last()).not.toHaveClassName('collapse'); + wrapper.find("button").simulate("click"); + expect( + wrapper + .childAt(0) + .children() + .last() + ).not.toHaveClassName("collapse"); - wrapper.find('button').simulate('click'); - expect(wrapper.childAt(0).children().last()).toHaveClassName('collapse'); + wrapper.find("button").simulate("click"); + expect( + wrapper + .childAt(0) + .children() + .last() + ).toHaveClassName("collapse"); }); -it('does not collapse if clicked with error', () => { - const wrapper = shallow(); +it("does not collapse if clicked with error", () => { + const wrapper = shallow(); - wrapper.find('button').simulate('click'); - expect(wrapper.childAt(0).children().last()).not.toHaveClassName('collapse'); + wrapper.find("button").simulate("click"); + expect( + wrapper + .childAt(0) + .children() + .last() + ).not.toHaveClassName("collapse"); }); diff --git a/src/components/settings/DHCPInfo.js b/src/components/settings/DHCPInfo.js index cdb2f68..ddc9d4e 100644 --- a/src/components/settings/DHCPInfo.js +++ b/src/components/settings/DHCPInfo.js @@ -26,21 +26,25 @@ class DHCPInfo extends Component { constructor(props) { super(props); this.updateDHCPInfo = this.updateDHCPInfo.bind(this); - }; + } updateDHCPInfo() { - this.updateHandler = makeCancelable(api.getDHCPInfo(), { repeat: this.updateDHCPInfo, interval: 600000 }); - this.updateHandler.promise.then(res => { - this.setState({ - active: res.active, - ip_start: res.ip_start, - ip_end: res.ip_end, - router_ip: res.router_ip, - lease_time: res.lease_time, - domain: res.domain, - ipv6_support: res.ipv6_support - }); - }) + this.updateHandler = makeCancelable(api.getDHCPInfo(), { + repeat: this.updateDHCPInfo, + interval: 600000 + }); + this.updateHandler.promise + .then(res => { + this.setState({ + active: res.active, + ip_start: res.ip_start, + ip_end: res.ip_end, + router_ip: res.router_ip, + lease_time: res.lease_time, + domain: res.domain, + ipv6_support: res.ipv6_support + }); + }) .catch(ignoreCancel) .catch(() => { this.setState({ @@ -68,13 +72,19 @@ class DHCPInfo extends Component { return (
-        {t("DHCP Active")}: {this.state.active.toString()}
- {t("Start IP")}: {this.state.ip_start}
- {t("End IP")}: {this.state.ip_end}
- {t("Router IP")}: {this.state.router_ip}
- {t("Lease Time")}: {this.state.lease_time} h
- {t("Domain")}: {this.state.domain}
- {t("IPv6 Support")}: {this.state.ipv6_support.toString()}
+ {t("DHCP Active")}: {this.state.active.toString()} +
+ {t("Start IP")}: {this.state.ip_start} +
+ {t("End IP")}: {this.state.ip_end} +
+ {t("Router IP")}: {this.state.router_ip} +
+ {t("Lease Time")}: {this.state.lease_time} h
+ {t("Domain")}: {this.state.domain} +
+ {t("IPv6 Support")}: {this.state.ipv6_support.toString()} +
); } diff --git a/src/components/settings/DNSInfo.js b/src/components/settings/DNSInfo.js index a977ca9..8a6d9b3 100644 --- a/src/components/settings/DNSInfo.js +++ b/src/components/settings/DNSInfo.js @@ -14,17 +14,17 @@ import { api, ignoreCancel, makeCancelable } from "../../utils"; class DNSInfo extends Component { state = { - "upstream_dns": ["---"], - "conditional_forwarding": { - "enabled": "---", - "router_ip": "---", - "domain": "---" + upstream_dns: ["---"], + conditional_forwarding: { + enabled: "---", + router_ip: "---", + domain: "---" }, - "options": { - "fqdn_required": "---", - "bogus_priv": "---", - "dnssec": "---", - "listening_type": "---" + options: { + fqdn_required: "---", + bogus_priv: "---", + dnssec: "---", + listening_type: "---" } }; @@ -34,24 +34,28 @@ class DNSInfo extends Component { } updateDNSInfo() { - this.updateHandler = makeCancelable(api.getDNSInfo(), { repeat: this.updateDNSInfo, interval: 600000 }); - this.updateHandler.promise.then(res => { - this.setState(res); - }) + this.updateHandler = makeCancelable(api.getDNSInfo(), { + repeat: this.updateDNSInfo, + interval: 600000 + }); + this.updateHandler.promise + .then(res => { + this.setState(res); + }) .catch(ignoreCancel) .catch(() => { this.setState({ - "upstream_dns": ["-!-"], - "options": { - "fqdn_required": "-!-", - "bogus_priv": "-!-", - "dnssec": "-!-", - "listening_type": "-!-" + upstream_dns: ["-!-"], + options: { + fqdn_required: "-!-", + bogus_priv: "-!-", + dnssec: "-!-", + listening_type: "-!-" }, - "conditional_forwarding": { - "enabled": "-!-", - "router_ip": "-!-", - "domain": "-!-" + conditional_forwarding: { + enabled: "-!-", + router_ip: "-!-", + domain: "-!-" } }); }); @@ -72,27 +76,41 @@ class DNSInfo extends Component {
-            
- {t("Upstream DNS Servers")}:
- {this.state.upstream_dns.map(item => item + "\n")}
+
+ {t("Upstream DNS Servers")}:
+ {this.state.upstream_dns.map(item => item + "\n")} +
-            
- {t("Interface listening behavior")}: {this.state.options.listening_type}
- {t("Forward FQDNs only")}: {this.state.options.fqdn_required.toString()}
- {t("Only forward public reverse lookups")}:{this.state.options.bogus_priv.toString()}
+
+ {t("Interface listening behavior")}:{" "} + {this.state.options.listening_type} +
+ {t("Forward FQDNs only")}:{" "} + {this.state.options.fqdn_required.toString()} +
+ {t("Only forward public reverse lookups")}: + {this.state.options.bogus_priv.toString()} +
{t("Use DNSSEC")}: {this.state.options.dnssec.toString()}
-            
- {t("Conditional Forwarding")}
- {t("Enabled")}: {this.state.conditional_forwarding.enabled.toString()}
- {t("Router IP")}: {this.state.conditional_forwarding.router_ip.toString()}
- {t("Local Domain Name")}: {this.state.conditional_forwarding.domain.toString()}
+
+ {t("Conditional Forwarding")} +
+ {t("Enabled")}:{" "} + {this.state.conditional_forwarding.enabled.toString()} +
+ {t("Router IP")}:{" "} + {this.state.conditional_forwarding.router_ip.toString()} +
+ {t("Local Domain Name")}:{" "} + {this.state.conditional_forwarding.domain.toString()} +
diff --git a/src/components/settings/FTLInfo.js b/src/components/settings/FTLInfo.js index a332817..d87d00c 100644 --- a/src/components/settings/FTLInfo.js +++ b/src/components/settings/FTLInfo.js @@ -25,23 +25,26 @@ class FTLInfo extends Component { } updateFTLInfo() { - this.updateHandler = makeCancelable(api.getFTLdb(), { repeat: this.updateFTLInfo, interval: 600000 }); - this.updateHandler.promise.then(res => { - this.setState({ - queries: res.queries.toLocaleString(), - filesize: res.filesize.toLocaleString(), - sqlite_version: res.sqlite_version - }); - }) + this.updateHandler = makeCancelable(api.getFTLdb(), { + repeat: this.updateFTLInfo, + interval: 600000 + }); + this.updateHandler.promise + .then(res => { + this.setState({ + queries: res.queries.toLocaleString(), + filesize: res.filesize.toLocaleString(), + sqlite_version: res.sqlite_version + }); + }) .catch(ignoreCancel) .catch(() => { - this.setState({ - queries: "-!-", - filesize: "-!-", - sqlite_version: "-!-" - }); - } - ); + this.setState({ + queries: "-!-", + filesize: "-!-", + sqlite_version: "-!-" + }); + }); } componentDidMount() { @@ -57,9 +60,11 @@ class FTLInfo extends Component { return (
-        {t("Queries")}: {this.state.queries}
- {t("Filesize")}: {this.state.filesize} B
- {t("SQLite version")}: {this.state.sqlite_version}
+ {t("Queries")}: {this.state.queries} +
+ {t("Filesize")}: {this.state.filesize} B
+ {t("SQLite version")}: {this.state.sqlite_version} +
); } diff --git a/src/components/settings/NetworkInfo.js b/src/components/settings/NetworkInfo.js index 7f78b58..d4c7ce2 100644 --- a/src/components/settings/NetworkInfo.js +++ b/src/components/settings/NetworkInfo.js @@ -27,15 +27,19 @@ class NetworkInfo extends Component { } updateNetInfo() { - this.updateHandler = makeCancelable(api.getNetworkInfo(), { repeat: this.updateNetInfo, interval: 600000 }); - this.updateHandler.promise.then(res => { - this.setState({ - interface: res.interface, - ipv4_address: res.ipv4_address, - ipv6_address: res.ipv6_address, - hostname: res.hostname - }); - }) + this.updateHandler = makeCancelable(api.getNetworkInfo(), { + repeat: this.updateNetInfo, + interval: 600000 + }); + this.updateHandler.promise + .then(res => { + this.setState({ + interface: res.interface, + ipv4_address: res.ipv4_address, + ipv6_address: res.ipv6_address, + hostname: res.hostname + }); + }) .catch(ignoreCancel) .catch(() => { this.setState({ @@ -62,27 +66,43 @@ class NetworkInfo extends Component {
- + - {this.state.interface} + + {this.state.interface} + - + - {this.state.ipv4_address} + + {this.state.ipv4_address} + - + - {this.state.ipv6_address} + + {this.state.ipv6_address} + - + - {this.state.hostname} + + {this.state.hostname} +
diff --git a/src/components/settings/VersionCard.js b/src/components/settings/VersionCard.js index 213c70b..66fb7d2 100644 --- a/src/components/settings/VersionCard.js +++ b/src/components/settings/VersionCard.js @@ -25,22 +25,27 @@ class VersionCard extends Component {
- +

{this.props.name}

- {t("Version")}: {this.props.branch === "master" ? this.props.tag : "vDev"} + {t("Version")}:{" "} + {this.props.branch === "master" ? this.props.tag : "vDev"}

- - +
diff --git a/src/components/settings/VersionInfo.js b/src/components/settings/VersionInfo.js index cf27e51..5c6b59b 100644 --- a/src/components/settings/VersionInfo.js +++ b/src/components/settings/VersionInfo.js @@ -43,10 +43,14 @@ class VersionInfo extends Component { } updateVersionInfo() { - this.updateHandler = makeCancelable(api.getVersion(), { repeat: this.updateVersionInfo, interval: 600000 }); - this.updateHandler.promise.then(res => { - this.setState(res); - }) + this.updateHandler = makeCancelable(api.getVersion(), { + repeat: this.updateVersionInfo, + interval: 600000 + }); + this.updateHandler.promise + .then(res => { + this.setState(res); + }) .catch(ignoreCancel) .catch(() => { this.setState({ @@ -88,20 +92,44 @@ class VersionInfo extends Component { return (
- +
- +
- +
- +
); diff --git a/src/config.development.js b/src/config.development.js index f7fc5d4..534a6ef 100644 --- a/src/config.development.js +++ b/src/config.development.js @@ -11,4 +11,4 @@ export default { developmentMode: true, fakeAPI: false -}; \ No newline at end of file +}; diff --git a/src/config.js b/src/config.js index 8f502e9..d28c7ef 100644 --- a/src/config.js +++ b/src/config.js @@ -8,16 +8,13 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import developmentConfig from './config.development'; -import productionConfig from './config.production'; +import developmentConfig from "./config.development"; +import productionConfig from "./config.production"; let config; -if(process.env.NODE_ENV === "development") - config = developmentConfig; -else - config = productionConfig; +if (process.env.NODE_ENV === "development") config = developmentConfig; +else config = productionConfig; -if(process.env.REACT_APP_FAKE_API) - config.fakeAPI = true; +if (process.env.REACT_APP_FAKE_API) config.fakeAPI = true; -export default config; \ No newline at end of file +export default config; diff --git a/src/config.production.js b/src/config.production.js index c02ae07..11036ab 100644 --- a/src/config.production.js +++ b/src/config.production.js @@ -11,4 +11,4 @@ export default { developmentMode: false, fakeAPI: false -}; \ No newline at end of file +}; diff --git a/src/containers/Full.js b/src/containers/Full.js index 3b96f8c..ead2fcf 100644 --- a/src/containers/Full.js +++ b/src/containers/Full.js @@ -8,29 +8,29 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { Switch, Route, Redirect } from 'react-router-dom' -import Header, { mobileSidebarHide } from '../components/common/Header'; -import Sidebar from '../components/common/Sidebar'; -import Footer from '../components/common/Footer'; +import React from "react"; +import { Switch, Route, Redirect } from "react-router-dom"; +import Header, { mobileSidebarHide } from "../components/common/Header"; +import Sidebar from "../components/common/Sidebar"; +import Footer from "../components/common/Footer"; import { api } from "../utils"; import { nav } from "../routes"; export default props => (
-
+
- +
-
+
- + {nav.map(createRoute)}
-
+
); @@ -41,13 +41,23 @@ export default props => ( * @param routeData the route data (see routes.js) */ const createRoute = routeData => { - if(routeData.children) { + if (routeData.children) { return routeData.children.map(createRoute); } - return routeData.auth - ? - : ; + return routeData.auth ? ( + + ) : ( + + ); }; /** @@ -76,4 +86,3 @@ const AuthRoute = ({ component: Component, ...rest }) => ( } /> ); - diff --git a/src/i18n.js b/src/i18n.js index 7069032..26b4e54 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -1,8 +1,8 @@ -import i18n from 'i18next'; -import XHR from 'i18next-xhr-backend'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import { reactI18nextModule } from 'react-i18next'; -import config from './config'; +import i18n from "i18next"; +import XHR from "i18next-xhr-backend"; +import LanguageDetector from "i18next-browser-languagedetector"; +import { reactI18nextModule } from "react-i18next"; +import config from "./config"; export function setupI18n() { i18n @@ -11,9 +11,17 @@ export function setupI18n() { .use(reactI18nextModule) .init({ fallbackLng: "en", - ns: ['common'], + ns: ["common"], defaultNS: "common", - fallbackNS: ["dashboard", "footer", "lists", "location", "login", "query-log", "settings"], + fallbackNS: [ + "dashboard", + "footer", + "lists", + "location", + "login", + "query-log", + "settings" + ], nsSeparator: false, keySeparator: false, debug: config.developmentMode, diff --git a/src/index.js b/src/index.js index 01ff80a..78fd6b3 100644 --- a/src/index.js +++ b/src/index.js @@ -8,16 +8,16 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { BrowserRouter, Route, Switch } from 'react-router-dom' +import React from "react"; +import ReactDOM from "react-dom"; +import { BrowserRouter, Route, Switch } from "react-router-dom"; import "ionicons/dist/css/ionicons.min.css"; import "font-awesome/css/font-awesome.min.css"; import "simple-line-icons/css/simple-line-icons.css"; -import './scss/style.css'; -import Full from './containers/Full'; +import "./scss/style.css"; +import Full from "./containers/Full"; import { api } from "./utils"; -import { setupI18n } from './i18n'; +import { setupI18n } from "./i18n"; // Before rendering anything, check if there is a session cookie. // Note: the user could have an old session, so the first API call @@ -27,12 +27,10 @@ api.loggedIn = document.cookie.includes("user_id="); setupI18n(); ReactDOM.render( - ( - - - - - - ), - document.getElementById('root') + + + + + , + document.getElementById("root") ); diff --git a/src/routes.js b/src/routes.js index 9165897..ec02a41 100644 --- a/src/routes.js +++ b/src/routes.js @@ -19,96 +19,96 @@ import Login from "./views/Login"; import Logout from "./views/Logout"; export const routes = t => ({ - '/dashboard': t('Dashboard'), - '/query-log': t('Query Log'), - '/whitelist': t('Whitelist'), - '/blacklist/exact': `${t('Blacklist')} (${t('Exact')})`, - '/blacklist/regex': `${t('Blacklist')} (${t('Regex')})`, - '/settings/versions': t('Versions'), - '/settings/networking': t('Networking'), - '/login': t('Login'), - '/logout': t('Logout') + "/dashboard": t("Dashboard"), + "/query-log": t("Query Log"), + "/whitelist": t("Whitelist"), + "/blacklist/exact": `${t("Blacklist")} (${t("Exact")})`, + "/blacklist/regex": `${t("Blacklist")} (${t("Regex")})`, + "/settings/versions": t("Versions"), + "/settings/networking": t("Networking"), + "/login": t("Login"), + "/logout": t("Logout") }); export const nav = [ { - name: 'Dashboard', - url: '/dashboard', + name: "Dashboard", + url: "/dashboard", component: Dashboard, - icon: 'fa fa-dashboard', + icon: "fa fa-dashboard", auth: false }, { - name: 'Query Log', - url: '/query-log', + name: "Query Log", + url: "/query-log", component: QueryLog, - icon: 'fa fa-database', + icon: "fa fa-database", auth: true }, { - name: 'Whitelist', - url: '/whitelist', + name: "Whitelist", + url: "/whitelist", component: Whitelist, - icon: 'fa fa-check-circle-o', + icon: "fa fa-check-circle-o", auth: false }, { - name: 'Blacklist', + name: "Blacklist", url: "/blacklist", - icon: 'fa fa-ban', + icon: "fa fa-ban", auth: false, children: [ { - name: 'Exact', - url: '/blacklist/exact', + name: "Exact", + url: "/blacklist/exact", component: Blacklist, - icon: 'fa fa-ban', + icon: "fa fa-ban", auth: false }, { - name: 'Regex', - url: '/blacklist/regex', + name: "Regex", + url: "/blacklist/regex", component: Regexlist, - icon: 'fa fa-ban', + icon: "fa fa-ban", auth: false } ] }, { - name: 'Settings', + name: "Settings", url: "/settings", - icon: 'fa fa-wrench', + icon: "fa fa-wrench", auth: true, children: [ - { - name: 'Versions', - url: '/settings/versions', + { + name: "Versions", + url: "/settings/versions", component: Versions, - icon: 'fa fa-download', + icon: "fa fa-download", auth: true }, { - name: 'Networking', - url: '/settings/networking', + name: "Networking", + url: "/settings/networking", component: Networking, - icon: 'fa fa-sitemap', - auth: true, + icon: "fa fa-sitemap", + auth: true } ] }, { - name: 'Login', - url: '/login', + name: "Login", + url: "/login", component: Login, - icon: 'fa fa-user', + icon: "fa fa-user", auth: false, authStrict: true }, { - name: 'Logout', - url: '/logout', + name: "Logout", + url: "/logout", component: Logout, - icon: 'fa fa-user-times', + icon: "fa fa-user-times", auth: true, authStrict: true } diff --git a/src/setupTests.js b/src/setupTests.js index 19d9443..6dba3f4 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -8,9 +8,9 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import { configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import 'jest-enzyme'; +import { configure } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import "jest-enzyme"; import { api } from "./utils"; import fetchMock from "fetch-mock"; @@ -18,12 +18,12 @@ import fetchMock from "fetch-mock"; configure({ adapter: new Adapter() }); // Mock out react-i18next -jest.mock('react-i18next', () => ({ +jest.mock("react-i18next", () => ({ // This mock makes sure any components using the translate HoC receive the t function as a prop translate: () => component => { component.defaultProps = { ...component.defaultProps, t: key => key }; return component; - }, + } })); // Temporary fix to reset logged in state for each test. diff --git a/src/utils.js b/src/utils.js index 40ff7fa..d53e52c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -8,27 +8,26 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import config from './config'; +import config from "./config"; -export const padNumber = (num) => { - return ("00" + num).substr(-2,2); +export const padNumber = num => { + return ("00" + num).substr(-2, 2); }; -export const makeCancelable = (promise, { repeat = null, interval = 0 } = {}) => { +export const makeCancelable = ( + promise, + { repeat = null, interval = 0 } = {} +) => { let hasCanceled = false; let repeatId = null; const handle = (resolve, reject, val, isError) => { - if(hasCanceled) - reject({ isCanceled: true }); + if (hasCanceled) reject({ isCanceled: true }); else { - if(isError) - reject(val); - else - resolve(val); + if (isError) reject(val); + else resolve(val); - if(repeat) - repeatId = setTimeout(repeat, interval); + if (repeat) repeatId = setTimeout(repeat, interval); } }; @@ -44,13 +43,12 @@ export const makeCancelable = (promise, { repeat = null, interval = 0 } = {}) => cancel() { clearTimeout(repeatId); hasCanceled = true; - }, + } }; }; export const ignoreCancel = err => { - if(!err.isCanceled) - throw err; + if (!err.isCanceled) throw err; }; export const api = { @@ -73,13 +71,13 @@ export const api = { return api.get("stats/overTime/history"); }, getClientsGraph() { - return api.get("stats/overTime/clients") + return api.get("stats/overTime/clients"); }, getQueryTypes() { - return api.get("stats/query_types") + return api.get("stats/query_types"); }, getForwardDestinations() { - return api.get("stats/forward_destinations") + return api.get("stats/forward_destinations"); }, getTopDomains() { return api.get("stats/top_domains"); @@ -103,13 +101,13 @@ export const api = { return api.get("dns/regexlist"); }, addWhitelist(domain) { - return api.post("dns/whitelist", { "domain": domain }); + return api.post("dns/whitelist", { domain: domain }); }, addBlacklist(domain) { - return api.post("dns/blacklist", { "domain": domain }); + return api.post("dns/blacklist", { domain: domain }); }, addRegexlist(domain) { - return api.post("dns/regexlist", { "domain": domain }); + return api.post("dns/regexlist", { domain: domain }); }, removeWhitelist(domain) { return api.delete("dns/whitelist/" + domain); @@ -121,7 +119,7 @@ export const api = { return api.delete("dns/regexlist/" + encodeURIComponent(domain)); }, getStatus() { - return api.get("dns/status") + return api.get("dns/status"); }, get(url) { return fetch(api.urlFor(url), { @@ -152,42 +150,41 @@ export const api = { .then(api.checkForErrors); }, getNetworkInfo() { - return api.get("settings/network") + return api.get("settings/network"); }, getVersion() { - return api.get("version") + return api.get("version"); }, getFTLdb() { - return api.get("settings/ftldb") + return api.get("settings/ftldb"); }, getDNSInfo() { - return api.get("settings/dns") + return api.get("settings/dns"); }, getDHCPInfo() { - return api.get("settings/dhcp") + return api.get("settings/dhcp"); }, /** * If the user is logged in, check if the user's session has lapsed. * If so, log them out and refresh the page. */ checkIfLoggedOut(response) { - if(api.loggedIn && response.status === 401) { + if (api.loggedIn && response.status === 401) { // Clear the user's old session and refresh the page - document.cookie = 'user_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + document.cookie = + "user_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; window.location.reload(); - return Promise.reject({ isCanceled: true }) + return Promise.reject({ isCanceled: true }); } return Promise.resolve(response); }, async convertJSON(data) { - if(!data.ok) - return Promise.reject({ data, json: await data.json() }); - else - return data.json(); + if (!data.ok) return Promise.reject({ data, json: await data.json() }); + else return data.json(); }, checkForErrors(data) { - if(data.error) { + if (data.error) { return Promise.reject(data.error); } return Promise.resolve(data); @@ -195,10 +192,8 @@ export const api = { urlFor(endpoint) { let apiLocation; - if(config.fakeAPI) - apiLocation = process.env.PUBLIC_URL + "/fakeAPI"; - else - apiLocation = "/admin/api"; + if (config.fakeAPI) apiLocation = process.env.PUBLIC_URL + "/fakeAPI"; + else apiLocation = "/admin/api"; return apiLocation + "/" + endpoint; }, diff --git a/src/utils.test.js b/src/utils.test.js index 7e83d20..4d2355c 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -12,13 +12,12 @@ import { api } from "./utils"; // This is a dumb test used to set up the next test, // which checks that the logged in state is reset before each test -it('sets logged in to true', () => { +it("sets logged in to true", () => { api.loggedIn = true; expect(api.loggedIn).toBeTruthy(); }); -it('resets the logged in state for each test', () => { +it("resets the logged in state for each test", () => { expect(api.loggedIn).toBeFalsy(); }); - diff --git a/src/views/Blacklist.js b/src/views/Blacklist.js index 64706b5..ede888d 100644 --- a/src/views/Blacklist.js +++ b/src/views/Blacklist.js @@ -8,8 +8,8 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { translate } from 'react-i18next'; +import React from "react"; +import { translate } from "react-i18next"; import { api } from "../utils"; import ListPage from "../components/list/ListPage"; import { isValidDomain } from "../validate"; @@ -28,7 +28,7 @@ const Blacklist = props => { validationErrorMsg={t("Not a valid domain")} {...props} /> - ) + ); }; export default translate("location")(Blacklist); diff --git a/src/views/Dashboard.js b/src/views/Dashboard.js index 5a625b7..88f1150 100644 --- a/src/views/Dashboard.js +++ b/src/views/Dashboard.js @@ -8,56 +8,54 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Fragment } from 'react'; -import SummaryStats from '../components/dashboard/SummaryStats'; -import QueriesGraph from '../components/dashboard/QueriesGraph'; -import ClientsGraph from '../components/dashboard/ClientsGraph'; +import React, { Fragment } from "react"; +import SummaryStats from "../components/dashboard/SummaryStats"; +import QueriesGraph from "../components/dashboard/QueriesGraph"; +import ClientsGraph from "../components/dashboard/ClientsGraph"; import QueryTypesChart from "../components/dashboard/QueryTypesChart"; import ForwardDestinationsChart from "../components/dashboard/ForwardDestinationsChart"; -import TopDomains from '../components/dashboard/TopDomains'; -import TopBlocked from '../components/dashboard/TopBlocked'; -import TopClients from '../components/dashboard/TopClients'; +import TopDomains from "../components/dashboard/TopDomains"; +import TopBlocked from "../components/dashboard/TopBlocked"; +import TopClients from "../components/dashboard/TopClients"; import { api } from "../utils"; export default () => (
- +
- +
- { - api.loggedIn ? - -
-
- -
+ {api.loggedIn ? ( + +
+
+
-
-
- -
-
- -
+
+
+
+
-
-
- -
-
- -
-
- -
+
+
- - : null - } +
+
+
+ +
+
+ +
+
+ +
+
+ + ) : null}
); diff --git a/src/views/Login.js b/src/views/Login.js index c00666d..aa055f8 100644 --- a/src/views/Login.js +++ b/src/views/Login.js @@ -27,8 +27,7 @@ class Login extends Component { componentWillMount() { // Check if cookies are enabled - if(navigator.cookieEnabled) - this.setState({ cookiesEnabled: true }); + if (navigator.cookieEnabled) this.setState({ cookiesEnabled: true }); } /** @@ -39,10 +38,8 @@ class Login extends Component { * @param e the event */ handlePasswordChange = e => { - if(e.keyCode === 13) - this.authenticate(); - else - this.setState({ password: e.target.value }); + if (e.keyCode === 13) this.authenticate(); + else this.setState({ password: e.target.value }); }; /** @@ -53,17 +50,22 @@ class Login extends Component { e.preventDefault(); // Hash the password twice before sending to the API - let hashedPassword = sha("sha256").update(this.state.password).digest("hex"); - hashedPassword = sha("sha256").update(hashedPassword).digest("hex"); + let hashedPassword = sha("sha256") + .update(this.state.password) + .digest("hex"); + hashedPassword = sha("sha256") + .update(hashedPassword) + .digest("hex"); // Clear the state this.setState({ password: "", error: false }); // Send the password to the API to authenticate the user - api.authenticate(hashedPassword) + api + .authenticate(hashedPassword) .then(data => { // Verify status - if(data.status !== "success") { + if (data.status !== "success") { console.log("Failed to log in:"); console.log(data); return; @@ -71,7 +73,7 @@ class Login extends Component { api.loggedIn = true; - if(config.fakeAPI) { + if (config.fakeAPI) { // When using the fake API, set the cookie ourselves document.cookie = "user_id=;"; } @@ -86,76 +88,86 @@ class Login extends Component { render() { // If the user is already logged in, then go to the home page - if(api.loggedIn) - return ; + if (api.loggedIn) return ; const { t } = this.props; return ( -
+
-
- Logo +
+ Logo
-
+
-
- - Pi-hole +
+ + Pi- + hole
{t("Sign in to start your session")} - { - // If the user tried to go to a protected page and is not logged in, - // tell them they will be redirected once login is successful - this.props.location.state && this.props.location.state.from.pathname in routes(t) ? - ( - -
- {t( - "You will be transferred to the \"{{page}}\" page", - { page: routes(t)[this.props.location.state.from.pathname] } - )} -
- ) : null - } - { - // If cookies are not enabled (or detected), show a warning - !this.state.cookiesEnabled ? -
- {t("Verify that cookies are allowed for {{host}}", { host: window.location.host })} -
- : null - } -
- { - this.state.error - ? -
- + {// If the user tried to go to a protected page and is not logged in, + // tell them they will be redirected once login is successful + this.props.location.state && + this.props.location.state.from.pathname in routes(t) ? ( + +
+ {t('You will be transferred to the "{{page}}" page', { + page: routes(t)[this.props.location.state.from.pathname] + })} +
+ ) : null} + {// If cookies are not enabled (or detected), show a warning + !this.state.cookiesEnabled ? ( +
+ {t("Verify that cookies are allowed for {{host}}", { + host: window.location.host + })}
- : null - } + ) : null} +
+ {this.state.error ? ( +
+ +
+ ) : null}
-
- +
+
-
-
- +
+
@@ -164,4 +176,4 @@ class Login extends Component { } } -export default translate(["login", "location"])(Login); \ No newline at end of file +export default translate(["login", "location"])(Login); diff --git a/src/views/Logout.js b/src/views/Logout.js index eddda78..8760779 100644 --- a/src/views/Logout.js +++ b/src/views/Logout.js @@ -8,8 +8,8 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React, { Component } from 'react'; -import { Redirect } from 'react-router-dom'; +import React, { Component } from "react"; +import { Redirect } from "react-router-dom"; import { api } from "../utils"; import config from "../config"; @@ -17,16 +17,17 @@ export default class Logout extends Component { componentWillMount() { api.loggedIn = false; - if(config.fakeAPI) { + if (config.fakeAPI) { // When using the fake API, don't try deleting the resource // (it results in an error). Instead, delete the cookie. - document.cookie = 'user_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + document.cookie = + "user_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; } else { api.logout(); } } render() { - return ; + return ; } } diff --git a/src/views/Networking.js b/src/views/Networking.js index 80547ac..5402e4d 100644 --- a/src/views/Networking.js +++ b/src/views/Networking.js @@ -27,7 +27,7 @@ class Networking extends Component { * @param tab the tab ID to switch to */ setTab = tab => { - if(this.state.activeTab !== tab) { + if (this.state.activeTab !== tab) { this.setState({ activeTab: tab }); @@ -43,7 +43,10 @@ class Networking extends Component { */ tab = (id, name) => ( - this.setTab(id)}> + this.setTab(id)} + > {name} @@ -56,9 +59,7 @@ class Networking extends Component { * @param component the component to render in the tab * @returns {TabPane} the tab content component */ - tabContent = (id, component) => ( - {component} - ); + tabContent = (id, component) => {component}; render() { const { t } = this.props; @@ -72,10 +73,10 @@ class Networking extends Component { {this.tab("ftl", t("FTL"))} - {this.tabContent("network", )} - {this.tabContent("dhcp", )} - {this.tabContent("dns", )} - {this.tabContent("ftl", )} + {this.tabContent("network", )} + {this.tabContent("dhcp", )} + {this.tabContent("dns", )} + {this.tabContent("ftl", )}
); diff --git a/src/views/Regexlist.js b/src/views/Regexlist.js index e254450..9852580 100644 --- a/src/views/Regexlist.js +++ b/src/views/Regexlist.js @@ -8,8 +8,8 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { translate } from 'react-i18next'; +import React from "react"; +import { translate } from "react-i18next"; import ListPage from "../components/list/ListPage"; import { api } from "../utils"; diff --git a/src/views/Versions.js b/src/views/Versions.js index 1f2e993..0436879 100644 --- a/src/views/Versions.js +++ b/src/views/Versions.js @@ -8,13 +8,13 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import VersionInfo from '../components/settings/VersionInfo'; +import React from "react"; +import VersionInfo from "../components/settings/VersionInfo"; export default () => (
- +
); diff --git a/src/views/Whitelist.js b/src/views/Whitelist.js index f0ccf24..5f641c0 100644 --- a/src/views/Whitelist.js +++ b/src/views/Whitelist.js @@ -8,8 +8,8 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -import React from 'react'; -import { translate } from 'react-i18next'; +import React from "react"; +import { translate } from "react-i18next"; import ListPage from "../components/list/ListPage"; import { api } from "../utils"; import { isValidDomain } from "../validate"; @@ -28,7 +28,7 @@ const Whitelist = props => { validationErrorMsg={t("Not a valid domain")} {...props} /> - ) + ); }; export default translate(["location", "lists"])(Whitelist);
{t("Branch")}