Fix xo issues

Signed-off-by: XhmikosR <xhmikosr@gmail.com>
This commit is contained in:
XhmikosR
2020-04-26 12:30:44 +03:00
parent 2c712ae0e5
commit 2ef549cf5a
68 changed files with 263 additions and 268 deletions
+11 -8
View File
@@ -162,10 +162,11 @@ function topDomainList(length, max) {
const numbers = [];
const domains = unique(faker.internet.domainName, length);
for (let i = 0; i < length; i++)
numbers.push(faker.random.number({ max: max }));
for (let i = 0; i < length; i++) numbers.push(faker.random.number({ max }));
numbers.sort((a, b) => (parseInt(a, 10) > parseInt(b, 10) ? -1 : 1));
numbers.sort((a, b) =>
Number.parseInt(a, 10) > Number.parseInt(b, 10) ? -1 : 1
);
for (let i = 0; i < length; i++) {
result.push({
@@ -203,7 +204,9 @@ function topClientList(length, totalQueries) {
for (let i = 0; i < length; i++)
numbers.push(faker.random.number({ max: totalQueries }));
numbers.sort((a, b) => (parseInt(a, 10) > parseInt(b, 10) ? -1 : 1));
numbers.sort((a, b) =>
Number.parseInt(a, 10) > Number.parseInt(b, 10) ? -1 : 1
);
for (let i = 0; i < length; i++) {
top_clients.push({
@@ -251,7 +254,7 @@ function clientsOverTime(range, size) {
return {
over_time: graph,
clients: clients
clients
};
}
@@ -305,7 +308,7 @@ function getVersionInfo() {
"beta",
"test"
]),
hash: faker.internet.color().substring(1) + faker.random.number(9),
hash: faker.internet.color().slice(1) + faker.random.number(9),
tag: "vDev"
},
ftl: {
@@ -316,7 +319,7 @@ function getVersionInfo() {
"beta",
"test"
]),
hash: faker.internet.color().substring(1) + faker.random.number(9),
hash: faker.internet.color().slice(1) + faker.random.number(9),
tag: "vDev"
},
web: {
@@ -327,7 +330,7 @@ function getVersionInfo() {
"beta",
"test"
]),
hash: faker.internet.color().substring(1) + faker.random.number(9),
hash: faker.internet.color().slice(1) + faker.random.number(9),
tag: "vDev"
}
};
+4 -6
View File
@@ -8,8 +8,6 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { Component, FormEvent } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import NavButton from "./NavButton";
@@ -100,9 +98,9 @@ export class EnableDisable extends Component<
)
// Allow new status changes when finished
.then(this.toggleProcessing)
.catch(e => {
.catch(error => {
// Ignore canceled requests
if (e.isCanceled) {
if (error.isCanceled) {
return;
}
@@ -173,7 +171,7 @@ export class EnableDisable extends Component<
type="number"
value={this.state.customTime}
onChange={e =>
this.setState({ customTime: parseInt(e.target.value) })
this.setState({ customTime: Number.parseInt(e.target.value) })
}
/>
<InputGroupAddon addonType="append">
@@ -182,7 +180,7 @@ export class EnableDisable extends Component<
value={this.state.customMultiplier}
onChange={e =>
this.setState({
customMultiplier: parseInt(e.target.value)
customMultiplier: Number.parseInt(e.target.value)
})
}
>
+1 -1
View File
@@ -41,8 +41,8 @@ export default () => (
<header className="app-header navbar">
<button
className="navbar-toggler d-lg-none text-white ml-3"
onClick={mobileSidebarToggle}
type="button"
onClick={mobileSidebarToggle}
>
&#9776;
</button>
+1 -1
View File
@@ -22,11 +22,11 @@ const NavButton = ({ name, icon, onClick }: NavButtonProps) => (
<li className="nav-item">
<a
href="#"
className="nav-link"
onClick={e => {
e.preventDefault();
onClick(e);
}}
className="nav-link"
>
<i className={"nav-icon " + icon} />
{name}
+1 -2
View File
@@ -8,8 +8,6 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { MouseEvent, ReactNode } from "react";
export interface NavDropdownProps {
@@ -23,6 +21,7 @@ export default ({ name, icon, isOpen, children }: NavDropdownProps) => (
<li className={"nav-item nav-dropdown" + (isOpen ? " open" : "")}>
<button
className="nav-link nav-dropdown-toggle"
type="button"
onClick={handleDropdownClick}
>
<i className={"nav-icon " + icon} />
+8 -8
View File
@@ -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, { Fragment, ReactElement, Suspense } from "react";
import React, { ReactElement, Suspense } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import { Nav, NavItem } from "reactstrap";
@@ -42,9 +42,9 @@ export const PiholeNavItem = ({
<NavItem>
<NavLink
to={item.url}
onClick={mobileSidebarHide}
className="nav-link"
activeClassName="active"
onClick={mobileSidebarHide}
>
<i className={"nav-icon " + item.icon} />
{t(item.name)}
@@ -91,11 +91,11 @@ export const NavList = ({
t,
location
}: {
items: Array<RouteData>;
items: RouteData[];
t: TFunction;
location: Location;
}): ReactElement => (
<Fragment>
<>
{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;
@@ -112,20 +112,20 @@ export const NavList = ({
// At this point it's ok to show the item
return (item as RouteGroup).children ? (
<PiholeNavDropdown
item={item as RouteGroup}
key={index}
item={item as RouteGroup}
t={t}
location={location}
/>
) : (
<PiholeNavItem item={item as RouteItem} key={index} t={t} />
<PiholeNavItem key={index} item={item as RouteItem} t={t} />
);
})}
</Fragment>
</>
);
export interface SidebarProps extends WithTranslation {
items: Array<RouteData>;
items: RouteData[];
location: Location;
}
+1 -1
View File
@@ -16,7 +16,7 @@ export interface StatusBadgeProps extends WithTranslation {
status: string;
}
class StatusBadge extends Component<StatusBadgeProps, {}> {
class StatusBadge extends Component<StatusBadgeProps> {
isEnabled = () => this.props.status === "enabled";
render() {
+8 -10
View File
@@ -104,6 +104,7 @@ export class WithAPIData<T> extends Component<
// refresh with data from the API
setTimeout(() => this.loadData(), cancelOptions.interval);
}
return;
}
@@ -140,11 +141,7 @@ export class WithAPIData<T> extends Component<
}
}
componentDidUpdate(
prevProps: Readonly<WithAPIDataProps<T>>,
prevState: Readonly<WithAPIDataState<T>>,
snapshot?: any
): void {
componentDidUpdate(prevProps: Readonly<WithAPIDataProps<T>>): void {
if (prevProps === this.props) {
// Don't do anything if the props didn't change
return;
@@ -153,6 +150,7 @@ export class WithAPIData<T> extends Component<
if (this.props.flushOnUpdate) {
// The props changed, so trigger a full reload of the data. Current data is
// cleared so that loading indicators are shown.
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ apiResult: null });
this.loadData();
}
@@ -165,11 +163,11 @@ export class WithAPIData<T> extends Component<
if (this.state.apiResult.isOk()) {
return this.props.renderOk(this.state.apiResult.unwrap(), this.loadData);
} else {
return this.props.renderErr(
this.state.apiResult.unwrapErr(),
this.loadData
);
}
return this.props.renderErr(
this.state.apiResult.unwrapErr(),
this.loadData
);
}
}
@@ -22,7 +22,7 @@ import NavButton from "../NavButton";
import NavDropdown from "../NavDropdown";
import { Form, Input, Modal, ModalFooter, ModalHeader } from "reactstrap";
const tick = global.tick;
const { tick } = global;
type EnableDisableWrapper = ShallowWrapper<
EnableDisableProps,
@@ -80,7 +80,7 @@ describe("EnableDisable", () => {
const testCall = async (
initialStatus: Status,
buttonIndex: number,
setStatusArgs: Array<any>,
setStatusArgs: any[],
expectedStatus: any
) => {
const setStatus = jest.fn(() =>
@@ -21,7 +21,7 @@ it("renders as null if no update is available", () => {
it("renders a link to the versions page if there is an update", () => {
const wrapper = shallow(
<FooterUpdateStatus t={(text: string) => text} updateAvailable={true} />
<FooterUpdateStatus updateAvailable t={(text: string) => text} />
);
expect(wrapper.find(Link).props().to).toEqual("/settings/versions");
@@ -14,7 +14,7 @@ import NavDropdown from "../NavDropdown";
it("renders as open when isOpen is true", () => {
const wrapper = shallow(
<NavDropdown name="test" icon="test" isOpen={true}>
<NavDropdown isOpen name="test" icon="test">
{}
</NavDropdown>
);
@@ -35,7 +35,7 @@ it("renders as closed when isOpen is false", () => {
it("toggles the dropdown when clicked", () => {
const toggle = jest.fn();
const wrapper = shallow(
<NavDropdown name="test" icon="test" isOpen={true}>
<NavDropdown isOpen name="test" icon="test">
{}
</NavDropdown>
);
@@ -16,7 +16,7 @@ import { RouteCustomItem, RouteGroup } from "../../../routes";
import NavDropdown from "../NavDropdown";
import { NavLink } from "react-router-dom";
const t = global.t;
const { t } = global;
it("expands active drop down items", () => {
const item: RouteGroup = {
@@ -16,7 +16,7 @@ import {
WithAPIDataState
} from "../WithAPIData";
const tick = global.tick;
const { tick } = global;
const emptyRender = () => null;
const emptyAPICall = () => Promise.resolve({});
@@ -182,12 +182,12 @@ it("should clear the data when flushOnUpdate is true and props are changed", asy
const apiCall = jest.fn(() => Promise.resolve("test"));
const wrapper: WithAPIDataWrapper<string> = shallow(
<WithAPIData
flushOnUpdate
renderErr={emptyRender}
renderInitial={renderInitial}
apiCall={apiCall}
renderOk={renderOk}
repeatOptions={{ ignoreCancel: true, interval: 0 }}
flushOnUpdate={true}
/>
);
@@ -210,7 +210,7 @@ it("should clear the data when flushOnUpdate is true and props are changed", asy
});
it("should use the provided data on refresh instead of hitting the API", async () => {
let refreshTest: ((data: any) => void) | undefined = undefined;
let refreshTest: ((data: any) => void) | undefined;
const renderOk = jest.fn((data, refresh) => {
refreshTest = refresh;
return data;
@@ -240,7 +240,7 @@ it("should use the provided data on refresh instead of hitting the API", async (
it("should wait for the interval after refreshing with provided data", async () => {
jest.useFakeTimers();
let refreshTest: ((data: any) => void) | undefined = undefined;
let refreshTest: ((data: any) => void) | undefined;
const renderOk = jest.fn((data, refresh) => {
refreshTest = refresh;
return data;
@@ -293,7 +293,7 @@ it("should pass through repeat options", async () => {
it("should pass through repeat options after refresh", async () => {
jest.useFakeTimers();
let refreshTest: ((data: any) => void) | undefined = undefined;
let refreshTest: ((data: any) => void) | undefined;
const renderOk = jest.fn((data, refresh) => {
refreshTest = refresh;
return data;
@@ -10,7 +10,7 @@
import React from "react";
import { shallow } from "enzyme";
import { GlobalContextProvider } from "../index";
import { GlobalContextProvider } from "..";
it("provides all global contexts", () => {
const wrapper = shallow(
@@ -87,6 +87,7 @@ class ChartTooltip extends Component<ChartTooltipProps, ChartTooltipState> {
colors: tooltip.labelColors[i]
}));
}
data.sort((a: any, b: any) =>
a.data.split(": ")[0].localeCompare(b.data.split(": ")[0])
);
+11 -14
View File
@@ -25,15 +25,14 @@ import {
export interface ClientsGraphProps {
loading: boolean;
labels: Array<string>;
labels: string[];
timeUnit: TimeUnit;
rangeName?: string;
datasets: Array<ChartDataSets>;
datasets: ChartDataSets[];
}
export class ClientsGraph extends Component<
ClientsGraphProps & WithTranslation,
{}
ClientsGraphProps & WithTranslation
> {
private readonly graphRef: RefObject<Line>;
@@ -51,7 +50,7 @@ export class ClientsGraph extends Component<
mode: "x-axis",
callbacks: {
title: tooltipItem => {
const time = moment(tooltipItem[0].xLabel!, "HH:mm");
const time = moment(tooltipItem[0].xLabel, "HH:mm");
const fromTime = time.clone().subtract(5, "minutes");
const toTime = time.clone().add(4, "minutes").add(59, "seconds");
@@ -103,6 +102,7 @@ export class ClientsGraph extends Component<
</div>
<div className="card-body">
<Line
ref={this.graphRef}
width={970}
height={170}
data={{
@@ -110,7 +110,6 @@ export class ClientsGraph extends Component<
datasets: this.props.datasets
}}
options={options}
ref={this.graphRef}
/>
</div>
@@ -179,11 +178,11 @@ export const transformData = (
const labels = overTime.map(step =>
new Date(1000 * step.timestamp).toISOString()
);
const datasets: Array<ChartDataSets> = [];
const datasets: ChartDataSets[] = [];
// Fill in dataset metadata
let i = 0;
for (let client of data.clients) {
for (const client of data.clients) {
datasets.push({
label: client.name.length !== 0 ? client.name : client.ip,
// If we ran out of colors, make a random one
@@ -191,7 +190,7 @@ export const transformData = (
i < colors.length
? colors[i]
: "#" +
parseInt("" + Math.random() * 0xffffff, 10)
Number.parseInt(String(Math.random() * 0xffffff), 10)
.toString(16)
.padStart(6, "0"),
pointRadius: 0,
@@ -205,12 +204,10 @@ export const transformData = (
}
// Fill in data & labels
for (let step of overTime) {
for (let destination in datasets) {
for (const step of overTime) {
for (const destination in datasets) {
if (Object.prototype.hasOwnProperty.call(datasets, destination))
(datasets[destination].data as Array<number>).push(
step.data[destination]
);
(datasets[destination].data as number[]).push(step.data[destination]);
}
}
@@ -16,15 +16,12 @@ import { ChartOptions } from "chart.js";
export interface GenericDoughnutChartProps {
title: string;
loading: boolean;
data: Array<number>;
colors: Array<string>;
labels: Array<string>;
data: number[];
colors: string[];
labels: string[];
}
export class GenericDoughnutChart extends Component<
GenericDoughnutChartProps,
{}
> {
export class GenericDoughnutChart extends Component<GenericDoughnutChartProps> {
private readonly chartRef: RefObject<Doughnut>;
constructor(props: GenericDoughnutChartProps) {
@@ -80,10 +77,10 @@ export class GenericDoughnutChart extends Component<
<div className="card-body">
<div className="float-left" style={{ width: "67%" }}>
<Doughnut
ref={this.chartRef}
width={100}
height={250}
options={options}
ref={this.chartRef}
data={{
datasets: [
{
@@ -152,7 +149,7 @@ export interface ChartItem {
* @param apiData the API data
* @returns GenericDoughnutChartProps
*/
export const transformData = (apiData: Array<ChartItem>) => {
export const transformData = (apiData: ChartItem[]) => {
const colors = [
"#20a8d8",
"#f86c6b",
@@ -168,7 +165,7 @@ export const transformData = (apiData: Array<ChartItem>) => {
// Fill in dataset metadata
let i = 0;
for (let entry of apiData) {
for (const entry of apiData) {
data.push(entry.percent);
labels.push(entry.name.length !== 0 ? entry.name : entry.ip!);
usedColors.push(
@@ -176,7 +173,7 @@ export const transformData = (apiData: Array<ChartItem>) => {
i < colors.length
? colors[i]
: "#" +
parseInt("" + Math.random() * 0xffffff, 10)
Number.parseInt(String(Math.random() * 0xffffff), 10)
.toString(16)
.padStart(6, "0")
);
@@ -210,7 +207,7 @@ export default function <T>({
}: {
apiCall: () => Promise<T>;
title: string;
apiHandler: (data: T) => Array<ChartItem>;
apiHandler: (data: T) => ChartItem[];
}) {
return (
<WithAPIData
+14 -13
View File
@@ -22,14 +22,14 @@ import {
export interface QueriesGraphProps {
loading: boolean;
labels: Array<Date>;
labels: Date[];
timeUnit: TimeUnit;
rangeName?: string;
domains_over_time: Array<number>;
blocked_over_time: Array<number>;
domains_over_time: number[];
blocked_over_time: number[];
}
class QueriesGraph extends Component<QueriesGraphProps & WithTranslation, {}> {
class QueriesGraph extends Component<QueriesGraphProps & WithTranslation> {
render() {
const { t } = this.props;
@@ -72,8 +72,8 @@ class QueriesGraph extends Component<QueriesGraphProps & WithTranslation, {}> {
title: tooltipItem => {
const timeStr = tooltipItem[0].xLabel! as string;
const time = timeStr.match(/(\d?\d):?(\d?\d?)/);
const hour = parseInt(time![1], 10);
const minute = parseInt(time![2], 10) || 0;
const hour = Number.parseInt(time![1], 10);
const minute = Number.parseInt(time![2], 10) || 0;
const from = padNumber(hour) + ":" + padNumber(minute - 5) + ":00";
const to = padNumber(hour) + ":" + padNumber(minute + 4) + ":59";
@@ -99,12 +99,13 @@ class QueriesGraph extends Component<QueriesGraphProps & WithTranslation, {}> {
percentage.toFixed(1) +
"%)"
);
} else
return (
data.datasets![tooltipItems.datasetIndex!].label +
": " +
tooltipItems.yLabel
);
}
return (
data.datasets![tooltipItems.datasetIndex!].label +
": " +
tooltipItems.yLabel
);
}
}
},
@@ -170,7 +171,7 @@ class QueriesGraph extends Component<QueriesGraphProps & WithTranslation, {}> {
* @returns QueriesGraphProps QueriesGraph props
*/
export const transformData = (
data: Array<ApiHistoryGraphItem>,
data: ApiHistoryGraphItem[],
range: TimeRange | null
): QueriesGraphProps => {
let timeUnit: TimeUnit = "hour";
+1 -1
View File
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
const QueryTypesChart = ({
t,
apiCall
}: WithTranslation & { apiCall: () => Promise<Array<ApiQueryType>> }) => (
}: WithTranslation & { apiCall: () => Promise<ApiQueryType[]> }) => (
<GenericDoughnutChart
title={t("Query Types")}
apiCall={apiCall}
+4 -4
View File
@@ -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, { Component, Fragment } from "react";
import React, { Component } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { WithAPIData } from "../common/WithAPIData";
import api from "../../util/api";
@@ -22,12 +22,12 @@ export interface SummaryStatsProps {
uniqueClients: number;
}
class SummaryStats extends Component<SummaryStatsProps & WithTranslation, {}> {
class SummaryStats extends Component<SummaryStatsProps & WithTranslation> {
render() {
const { t } = this.props;
return (
<Fragment>
<>
<div className="col-lg-3 col-xs-12">
<div className="card border-0 bg-success stat-height-lock">
<div className="card-body">
@@ -84,7 +84,7 @@ class SummaryStats extends Component<SummaryStatsProps & WithTranslation, {}> {
</div>
</div>
</div>
</Fragment>
</>
);
}
}
@@ -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, { Fragment, Suspense } from "react";
import React, { Suspense } from "react";
import DateRangePicker from "react-bootstrap-daterangepicker";
import { Button } from "reactstrap";
import {
@@ -58,7 +58,7 @@ const renderLabel = (
}
if (!props.range) {
return t<string>("Last 24 Hours");
return t("Last 24 Hours");
}
if (props.range.name === "Custom Range") {
@@ -89,9 +89,12 @@ export const TimeRangeSelector = (
return (
<DateRangePicker
timePicker
showDropdowns
startDate={range ? range.from : translatedDateRanges[last24Hours][0]}
endDate={range ? range.until : translatedDateRanges[last24Hours][1]}
maxDate={translatedDateRanges[today][1]}
ranges={translatedDateRanges}
onApply={(event, picker) => {
if (
picker.startDate.isSame(translatedDateRanges[last24Hours][0]) &&
@@ -108,17 +111,14 @@ export const TimeRangeSelector = (
});
}
}}
timePicker={true}
showDropdowns={true}
ranges={translatedDateRanges}
>
<Button color="light" size={size}>
<i className="far fa-clock fa-lg" />
{label ? (
<Fragment>
<>
&nbsp; &nbsp;
{label}
</Fragment>
</>
) : null}
</Button>
</DateRangePicker>
@@ -134,10 +134,10 @@ export const TimeRangeSelectorContainer = ({ size }: { size?: string }) => (
{context => (
<Suspense fallback={null}>
<TranslatedTimeRangeSelector
showLabel
range={context.range}
onSelect={context.update}
showLabel={true}
size={size}
onSelect={context.update}
/>
</Suspense>
)}
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
export interface TopBlockedClientsData {
blockedQueries: number;
topClients: Array<ApiClientData>;
topClients: ApiClientData[];
}
/**
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
export interface TopBlockedDomainsData {
totalBlocked: number;
topBlocked: Array<ApiTopDomainItem>;
topBlocked: ApiTopDomainItem[];
}
/**
+1 -1
View File
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
export interface TopClientsData {
totalQueries: number;
topClients: Array<ApiClientData>;
topClients: ApiClientData[];
}
/**
+1 -1
View File
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
export interface TopDomainsData {
totalQueries: number;
topDomains: Array<ApiTopDomainItem>;
topDomains: ApiTopDomainItem[];
}
/**
+4 -4
View File
@@ -15,13 +15,13 @@ export interface TopTableInnerProps<T> {
loading: boolean;
title: string;
data: T;
headers: Array<string>;
headers: string[];
emptyMessage: string;
isEmpty: (data: T) => boolean;
generateRows: (data: T) => ReactNode;
}
export class TopTable<T> extends Component<TopTableInnerProps<T>, {}> {
export class TopTable<T> extends Component<TopTableInnerProps<T>> {
static defaultProps = {
loading: true,
title: "",
@@ -112,13 +112,13 @@ export default function <T, D>({
}}
renderInitial={() => (
<TopTable
loading
title={title}
headers={headers}
emptyMessage={emptyMessage}
isEmpty={isEmpty}
generateRows={generateRows}
data={initialData}
loading={true}
{...props}
/>
)}
@@ -136,13 +136,13 @@ export default function <T, D>({
)}
renderErr={() => (
<TopTable
loading
title={title}
headers={headers}
emptyMessage={emptyMessage}
isEmpty={isEmpty}
generateRows={generateRows}
data={initialData}
loading={true}
{...props}
/>
)}
@@ -49,7 +49,7 @@ const fakeData: ApiClientsGraph = {
]
};
const tick = global.tick;
const { tick } = global;
it("shows loading indicator correctly", () => {
const wrapper = shallow(<TranslatedClientsGraph {...loadingProps} />).dive();
@@ -17,23 +17,21 @@ import {
transformData
} from "../GenericDoughnutChart";
const fakeData: Array<ChartItem> = [
const fakeData: ChartItem[] = [
{ name: "roberta.net", ip: "8.239.48.32", percent: 0.38411761010240625 },
{ name: "", ip: "89.60.252.186", percent: 0.2830935477791041 },
{ name: "christop.net", ip: "181.219.42.222", percent: 0.6249293208519193 }
];
it("shows loading indicator correctly", () => {
const wrapper = shallow(
<GenericDoughnutChart title={""} {...loadingProps} />
);
const wrapper = shallow(<GenericDoughnutChart title="" {...loadingProps} />);
expect(wrapper.children(".card-img-overlay")).toExist();
});
it("hides loading indicator correctly", async () => {
const wrapper = shallow(
<GenericDoughnutChart title={""} {...loadingProps} loading={false} />
<GenericDoughnutChart title="" {...loadingProps} loading={false} />
);
expect(wrapper.children(".card-img-overlay")).not.toExist();
@@ -16,7 +16,7 @@ import {
TranslatedSummaryStats
} from "../SummaryStats";
const tick = global.tick;
const { tick } = global;
const fakeData: ApiSummary = {
active_clients: 2,
@@ -13,7 +13,7 @@ import { shallow } from "enzyme";
import { TopTable } from "../TopTable";
it("shows loading indicator correctly", () => {
const wrapper = shallow(<TopTable loading={true} />);
const wrapper = shallow(<TopTable loading />);
expect(wrapper.children().last()).toHaveClassName("card-img-overlay");
});
+3 -3
View File
@@ -45,7 +45,7 @@ export class DomainInput extends Component<
handleSubmit = (e: FormEvent) => {
e.preventDefault();
const domain = this.state.domain;
const { domain } = this.state;
// Don't do anything for empty inputs
if (domain.length === 0) {
@@ -73,8 +73,8 @@ export class DomainInput extends Component<
className={`form-control ${this.state.isValid ? "" : "is-invalid"}`}
placeholder={placeholder}
value={this.state.domain}
onChange={this.handleChange}
disabled={!api.loggedIn}
onChange={this.handleChange}
/>
<span className="btn-group input-group-append">
{api.loggedIn ? (
@@ -83,9 +83,9 @@ export class DomainInput extends Component<
</button>
) : null}
<button
onClick={onRefresh}
className="btn border-secondary"
type="button"
onClick={onRefresh}
>
<i className="fa fa-sync" />
</button>
+2 -2
View File
@@ -21,7 +21,7 @@ import {
export interface ListPageProps {
title: string;
note?: {} | string;
note?: Record<string, unknown> | string;
placeholder: string;
onAdd: (domain: string) => Promise<any | never>;
onRefresh: () => Promise<any | never>;
@@ -162,9 +162,9 @@ export class ListPage extends Component<
<br />
<DomainInputContainer
placeholder={this.props.placeholder}
isValid={this.props.isValid}
onEnter={this.onEnter}
onRefresh={this.onRefresh}
isValid={this.props.isValid}
onValidationError={this.handleValidationError}
/>
{this.props.note}
@@ -19,8 +19,7 @@ import Alert from "../../common/Alert";
import { DomainInputContainer } from "../DomainInput";
import DomainList from "../DomainList";
const ignoreAPI = global.ignoreAPI;
const tick = global.tick;
const { ignoreAPI, tick } = global;
type ListPageWrapper = ShallowWrapper<
ListPageProps,
+31 -30
View File
@@ -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, { Component, Fragment } from "react";
import React, { Component } from "react";
import ReactTable, {
Filter,
ReactTableFunction,
@@ -34,12 +34,12 @@ import {
} from "../../util/CancelablePromise";
export interface QueryLogState {
history: Array<ApiQuery>;
history: ApiQuery[];
cursor: null | string;
loading: boolean;
atEnd: boolean;
filtersChanged: boolean;
filters: Array<Filter>;
filters: Filter[];
}
/**
@@ -107,10 +107,10 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
color: [1, 4, 5, 6].includes(rowInfo.row.status) ? "red" : "green"
}
};
} else {
// Unknown queries do not get colored
return {};
}
// Unknown queries do not get colored
return {};
};
/**
@@ -119,8 +119,8 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
* @param tableFilters the filters requested by the table
* @return the filters converted for use by the API
*/
parseFilters = (tableFilters: Array<Filter>) => {
let filters: any = {};
parseFilters = (tableFilters: Filter[]) => {
const filters: any = {};
for (const filter of tableFilters) {
switch (filter.id) {
@@ -134,7 +134,7 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
break;
}
filters.query_type = parseInt(filter.value);
filters.query_type = Number.parseInt(filter.value);
break;
case "domain":
if (filter.value.length === 0) {
@@ -167,6 +167,7 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
filters.status = filter.value;
break;
}
break;
case "dnssec":
if (filter.value === "all") {
@@ -243,14 +244,30 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
return (
<ReactTable
showPaginationTop
className="-striped bg-white mb-4"
style={{ lineHeight: 1 }}
columns={columns(t)}
showPaginationTop={true}
sortable={false}
filterable={false}
data={this.state.history}
loading={this.state.loading}
defaultFiltered={[
{
id: "time",
value: getDefaultRange(t)
}
]}
getTrProps={this.getRowProps}
ofText={this.state.atEnd ? "of" : "of at least"}
// Pad empty rows to have the same height as filled rows
PadRowComponent={() => (
<span>
&nbsp;
<br />
&nbsp;
</span>
)}
onFetchData={state => {
if (isEqual(state.filtered, this.state.filters)) {
// If the filters have not changed, do not debounce the fetch.
@@ -277,22 +294,6 @@ class QueryLog extends Component<WithTranslation, QueryLogState> {
history: []
});
}, 300)}
defaultFiltered={[
{
id: "time",
value: getDefaultRange(t)
}
]}
getTrProps={this.getRowProps}
ofText={this.state.atEnd ? "of" : "of at least"}
// Pad empty rows to have the same height as filled rows
PadRowComponent={() => (
<span>
&nbsp;
<br />
&nbsp;
</span>
)}
/>
);
}
@@ -376,9 +377,9 @@ const selectionFilter = (
onChange: ReactTableFunction;
}) => (
<select
onChange={event => onChange(event.target.value)}
style={{ width: "100%" }}
value={filter ? filter.value : "all"}
onChange={event => onChange(event.target.value)}
>
<option value="all">{t("All")}</option>
{extras.map((extra, i) => (
@@ -416,11 +417,11 @@ const columns = (t: TFunction) => [
const second = padNumber(date.getSeconds());
return (
<Fragment>
<>
{month + ", " + dayOfMonth}
<br />
{hour + ":" + minute + ":" + second}
</Fragment>
</>
);
},
filterable: true,
@@ -434,6 +435,7 @@ const columns = (t: TFunction) => [
}) => (
<TranslatedTimeRangeSelector
range={filter ? filter.value : null}
showLabel={false}
onSelect={range => {
if (range) {
onChange(range);
@@ -441,7 +443,6 @@ const columns = (t: TFunction) => [
onChange(getDefaultRange(t));
}
}}
showLabel={false}
/>
)
},
@@ -21,7 +21,7 @@ it("collapses and displays normal if there is no error", () => {
});
it("expands and displays red if there is an error", () => {
const wrapper = shallow(<ForgotPassword error={true} />).dive();
const wrapper = shallow(<ForgotPassword error />).dive();
expect(wrapper.childAt(0)).toHaveClassName("border-danger");
expect(wrapper.childAt(0).childAt(0)).toHaveClassName("bg-danger");
@@ -39,7 +39,7 @@ it("expands and collapses if clicked without error", () => {
});
it("does not collapse if clicked with error", () => {
const wrapper = shallow(<ForgotPassword error={true} />).dive();
const wrapper = shallow(<ForgotPassword error />).dive();
wrapper.find("button").simulate("click");
expect(wrapper.childAt(0).children().last()).not.toHaveClassName("collapse");
@@ -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, { Fragment } from "react";
import React from "react";
import { Col, FormGroup, Input, Label } from "reactstrap";
import { TFunction } from "i18next";
@@ -36,7 +36,7 @@ const ConditionalForwardingSettings = ({
isDomainValid,
t
}: ConditionalForwardingSettingsProps) => (
<Fragment>
<>
<FormGroup check>
<Label check>
<Input
@@ -56,8 +56,8 @@ const ConditionalForwardingSettings = ({
id="routerIP"
disabled={!settings.enabled}
value={settings.ip}
onChange={e => onUpdate({ ...settings, ip: e.target.value })}
invalid={!isRouterIpValid}
onChange={e => onUpdate({ ...settings, ip: e.target.value })}
/>
</Col>
</FormGroup>
@@ -72,7 +72,7 @@ const ConditionalForwardingSettings = ({
value={settings.cidr === -1 ? "" : settings.cidr}
invalid={!isCidrValid}
onChange={e => {
let cidr = parseInt(e.target.value);
let cidr = Number.parseInt(e.target.value);
if (e.target.value.length === 0) {
// Use -1 as an internal representation of the empty string.
@@ -97,12 +97,12 @@ const ConditionalForwardingSettings = ({
id="localDomain"
disabled={!settings.enabled}
value={settings.domain}
onChange={e => onUpdate({ ...settings, domain: e.target.value })}
invalid={!isDomainValid}
onChange={e => onUpdate({ ...settings, domain: e.target.value })}
/>
</Col>
</FormGroup>
</Fragment>
</>
);
export default ConditionalForwardingSettings;
+6 -6
View File
@@ -221,8 +221,8 @@ class DHCPInfo extends Component<WithTranslation, DHCPInfoState> {
id="startIP"
disabled={!this.state.settings.active}
value={this.state.settings.ip_start}
onChange={this.onChange("ip_start", "value")}
invalid={!isIpStartValid}
onChange={this.onChange("ip_start", "value")}
/>
</Col>
</FormGroup>
@@ -235,8 +235,8 @@ class DHCPInfo extends Component<WithTranslation, DHCPInfoState> {
id="endIP"
disabled={!this.state.settings.active}
value={this.state.settings.ip_end}
onChange={this.onChange("ip_end", "value")}
invalid={!isIpEndValid}
onChange={this.onChange("ip_end", "value")}
/>
</Col>
</FormGroup>
@@ -249,8 +249,8 @@ class DHCPInfo extends Component<WithTranslation, DHCPInfoState> {
id="routerIP"
disabled={!this.state.settings.active}
value={this.state.settings.router_ip}
onChange={this.onChange("router_ip", "value")}
invalid={!isRouterIpValid}
onChange={this.onChange("router_ip", "value")}
/>
</Col>
</FormGroup>
@@ -264,15 +264,15 @@ class DHCPInfo extends Component<WithTranslation, DHCPInfoState> {
id="leaseTime"
disabled={!this.state.settings.active}
value={this.state.settings.lease_time}
invalid={!isLeaseTimeValid}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
this.setState(oldState => ({
settings: {
...oldState.settings,
lease_time: parseInt(e.target.value)
lease_time: Number.parseInt(e.target.value)
}
}))
}
invalid={!isLeaseTimeValid}
/>
<InputGroupAddon addonType="append">Hours</InputGroupAddon>
</InputGroup>
@@ -287,8 +287,8 @@ class DHCPInfo extends Component<WithTranslation, DHCPInfoState> {
id="domain"
disabled={!this.state.settings.active}
value={this.state.settings.domain}
onChange={this.onChange("domain", "value")}
invalid={!isDomainValid}
onChange={this.onChange("domain", "value")}
/>
</Col>
</FormGroup>
+4 -4
View File
@@ -36,7 +36,7 @@ export interface DNSInfoState {
alertType: AlertType;
showAlert: boolean;
processing: boolean;
upstreamDns: Array<string>;
upstreamDns: string[];
conditionalForwarding: ConditionalForwardingObject;
options: DnsOptionsObject;
}
@@ -226,26 +226,26 @@ class DNSInfo extends Component<WithTranslation, DNSInfoState> {
<Col sm={6}>
<h3>{t("Upstream DNS Servers")}</h3>
<DnsList
upstreams={this.state.upstreamDns}
onAdd={this.handleUpstreamAdd}
onRemove={this.handleUpstreamRemove}
upstreams={this.state.upstreamDns}
/>
</Col>
<Col sm={6}>
<h3>{t("Conditional Forwarding")}</h3>
<ConditionalForwardingSettings
settings={this.state.conditionalForwarding}
onUpdate={this.handleConditionalForwardingUpdate}
isRouterIpValid={isRouterIpValid}
isCidrValid={isCidrValid}
isDomainValid={isDomainValid}
t={t}
onUpdate={this.handleConditionalForwardingUpdate}
/>
<h3>{t("DNS Options")}</h3>
<DnsOptionSettings
settings={this.state.options}
onUpdate={this.handleDnsOptionsUpdate}
t={t}
onUpdate={this.handleDnsOptionsUpdate}
/>
</Col>
</FormGroup>
+4 -4
View File
@@ -18,7 +18,7 @@ import {
} from "../../util/validate";
export interface DnsListProps {
upstreams: Array<string>;
upstreams: string[];
onAdd: (upstream: string) => void;
onRemove: (upstream: string) => void;
}
@@ -32,7 +32,7 @@ export interface DnsListProps {
*/
export const isAddressValid = (
address: string,
upstreams: Array<string>
upstreams: string[]
): boolean => {
return (
!upstreams.includes(address) &&
@@ -45,14 +45,14 @@ const DnsList = ({ upstreams, onAdd, onRemove }: DnsListProps) => (
{upstreams.map(upstream => (
<DnsListItem
key={upstream}
onRemove={() => onRemove(upstream)}
address={upstream}
onRemove={() => onRemove(upstream)}
/>
))}
<DnsListNewItem
onAdd={onAdd}
isValid={(address: string) => isAddressValid(address, upstreams)}
upstreams={upstreams}
onAdd={onAdd}
/>
</ListGroup>
);
+7 -7
View File
@@ -20,12 +20,12 @@ import {
export interface DnsListNewItemProps {
onAdd: (address: string) => void;
isValid: (address: string) => boolean;
upstreams: Array<string>;
upstreams: string[];
}
export interface DnsListNewItemState {
address: string;
selected: Array<PreconfiguredUpstreamOption>;
selected: PreconfiguredUpstreamOption[];
}
/**
@@ -70,22 +70,23 @@ class DnsListNewItem extends Component<
<ListGroupItem>
<InputGroup>
<Typeahead
ref={this.typeahead}
positionFixed
id="dns-list-typeahead"
onInputChange={address => this.setState({ address })}
onChange={selected => this.setState({ selected })}
options={preconfiguredUpstreamOptions.filter(
upstream => !this.props.upstreams.includes(upstream.address)
)}
selected={this.state.selected}
emptyLabel={t("Detected custom upstream server")}
positionFixed
ref={this.typeahead}
onInputChange={address => this.setState({ address })}
onChange={selected => this.setState({ selected })}
/>
<InputGroupAddon addonType="append">
<Button
color="success"
size="sm"
disabled={!isAddressValid}
onClick={() => {
// Add the server to the list
this.props.onAdd(this.getAddress());
@@ -94,7 +95,6 @@ class DnsListNewItem extends Component<
this.setState({ address: "", selected: [] });
this.typeahead.current.getInstance().clear();
}}
disabled={!isAddressValid}
>
<span className="fa fa-plus" />
</Button>
@@ -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, { Fragment } from "react";
import React from "react";
import { Col, FormGroup, Input, Label } from "reactstrap";
import { TFunction } from "i18next";
@@ -30,7 +30,7 @@ const DnsOptionSettings = ({
onUpdate,
t
}: DnsOptionSettingsProps) => (
<Fragment>
<>
<FormGroup row>
<Label for="listeningBehavior" sm={5}>
{t("Interface listening behavior")}
@@ -82,7 +82,7 @@ const DnsOptionSettings = ({
{t("Use DNSSEC")}
</Label>
</FormGroup>
</Fragment>
</>
);
export default DnsOptionSettings;
@@ -157,14 +157,14 @@ class PreferenceSettings extends Component<
return t(this.state.alertMessage, {
error: t(this.state.error.key, this.state.error.data)
});
} else {
// Check if the message should be translated
if (this.state.translateMessage) {
return t(this.state.alertMessage);
} else {
return this.state.alertMessage;
}
}
// Check if the message should be translated
if (this.state.translateMessage) {
return t(this.state.alertMessage);
}
return this.state.alertMessage;
};
render() {
+1 -1
View File
@@ -14,7 +14,7 @@ import api from "../../util/api";
import VersionCard from "./VersionCard";
import { WithAPIData } from "../common/WithAPIData";
class VersionInfo extends Component<ApiVersions & WithTranslation, {}> {
class VersionInfo extends Component<ApiVersions & WithTranslation> {
render() {
const { t } = this.props;
@@ -14,7 +14,7 @@ import DHCPInfo, { DHCPInfoState } from "../DHCPInfo";
import fetchMock from "fetch-mock";
import { WithTranslation } from "react-i18next";
const tick = global.tick;
const { tick } = global;
const endpoint = "/api/settings/dhcp";
const fakeData = {
@@ -20,7 +20,7 @@ export interface PreconfiguredUpstream {
* A list of preconfigured upstream DNS servers. Each item has primary and
* secondary IPv4 and IPv6 server entries. Some addresses may be empty.
*/
export const preconfiguredUpstreams: Array<PreconfiguredUpstream> = [
export const preconfiguredUpstreams: PreconfiguredUpstream[] = [
{
name: "OpenDNS (ECS)",
primaryIpv4: "208.67.222.222",
@@ -103,7 +103,7 @@ export const preconfiguredUpstreamOptions = preconfiguredUpstreams.flatMap(
address
});
let parsedUpstreams: Array<PreconfiguredUpstreamOption> = [];
const parsedUpstreams: PreconfiguredUpstreamOption[] = [];
if (upstream.primaryIpv4.length > 0) {
parsedUpstreams.push(
+1 -1
View File
@@ -11,7 +11,7 @@
import { Config } from "./config";
const apiUrlBase =
process.env.REACT_APP_CUSTOM_API_URL || process.env.PUBLIC_URL;
process.env.REACT_APP_CUSTOM_API_URL ?? process.env.PUBLIC_URL;
export default {
developmentMode: true,
+1 -1
View File
@@ -60,7 +60,7 @@ const createRoute = (routeData: RouteData): ReactNode => {
return (routeData as RouteGroup).children.map(createRoute);
}
let navItem: RouteItem = routeData as RouteItem;
const navItem: RouteItem = routeData as RouteItem;
return navItem.auth ? (
<AuthRoute
+2 -2
View File
@@ -7,8 +7,8 @@
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import "react-app-polyfill/ie11";
import "react-app-polyfill/stable";
import "react-app-polyfill/ie11"; // eslint-disable-line import/no-unassigned-import
import "react-app-polyfill/stable"; // eslint-disable-line import/no-unassigned-import
import * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch } from "react-router-dom";
+1 -1
View File
@@ -10,7 +10,7 @@
import { createAction } from "@reduxjs/toolkit";
export const preferencesRequest = createAction<void>("PREFERENCES_REQUEST");
export const preferencesRequest = createAction("PREFERENCES_REQUEST");
export const preferencesSuccess = createAction<ApiPreferences>(
"PREFERENCES_SUCCESS"
);
+1 -1
View File
@@ -18,7 +18,7 @@ import i18n from "i18next";
* @param action The action with the language to apply
*/
export function* applyLanguage(action: PayloadAction<ApiPreferences>) {
const language = action.payload.language;
const { language } = action.payload;
// Only change the language if it's different
if (i18n.language !== language) {
+3 -3
View File
@@ -28,14 +28,14 @@ export function* autoLogin() {
try {
// Check if we are logged in
yield call(api.checkAuthStatus);
} catch (e) {
if (e.key === "unauthorized") {
} catch (error) {
if (error.key === "unauthorized") {
// The API requires authentication and we are not already logged in
return;
}
// An unexpected error occurred while checking our logged in state
throw e;
throw error;
}
if (!api.loggedIn) {
+1 -1
View File
@@ -38,7 +38,7 @@ export const loadInitialPreferences = (): ApiPreferences => {
try {
return JSON.parse(cachedPreferencesString);
} catch (e) {
} catch (_) {
return defaultPreferences;
}
};
+2 -2
View File
@@ -59,12 +59,12 @@ export interface RouteGroup {
icon: string;
auth: boolean;
authStrict?: boolean;
children: Array<RouteData>;
children: RouteData[];
}
export type RouteData = RouteItem | RouteGroup | RouteCustomItem;
export const nav: Array<RouteData> = [
export const nav: RouteData[] = [
{
name: "Dashboard",
url: "/dashboard",
+2 -2
View File
@@ -11,10 +11,10 @@
import React from "react";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import "jest-enzyme";
import "jest-enzyme"; // eslint-disable-line import/no-unassigned-import
import api from "./util/api";
import fetchMock from "fetch-mock";
import "jest-localstorage-mock";
import "jest-localstorage-mock"; // eslint-disable-line import/no-unassigned-import
import { TFunction } from "i18next";
import config from "./config";
+9 -9
View File
@@ -21,7 +21,7 @@ interface ApiQuery {
interface ApiHistoryResponse {
cursor: null | string;
history: Array<ApiQuery>;
history: ApiQuery[];
}
interface ApiNetworkSettings {
@@ -44,7 +44,7 @@ interface ApiVersions {
}
interface ApiDnsSettings {
upstream_dns: Array<string>;
upstream_dns: string[];
options: {
fqdn_required: boolean;
bogus_priv: boolean;
@@ -113,13 +113,13 @@ interface ApiStatus {
}
interface ApiClientsGraph {
over_time: Array<ApiClientOverTime>;
clients: Array<ApiClientGraphInfo>;
over_time: ApiClientOverTime[];
clients: ApiClientGraphInfo[];
}
interface ApiClientOverTime {
timestamp: number;
data: Array<number>;
data: number[];
}
interface ApiClientGraphInfo {
@@ -185,22 +185,22 @@ interface ApiTopDomainItem {
}
interface ApiTopBlockedDomains {
top_domains: Array<ApiTopDomainItem>;
top_domains: ApiTopDomainItem[];
blocked_queries: number;
}
interface ApiTopDomains {
top_domains: Array<ApiTopDomainItem>;
top_domains: ApiTopDomainItem[];
total_queries: number;
}
interface ApiTopClients {
top_clients: Array<ApiClientData>;
top_clients: ApiClientData[];
total_queries: number;
}
interface ApiTopBlockedClients {
top_clients: Array<ApiClient>;
top_clients: ApiClient[];
blocked_queries: number;
}
+1
View File
@@ -87,6 +87,7 @@ export function makeCancelable<T>(
if (repeatId !== null) {
clearTimeout(repeatId);
}
hasCanceled = true;
}
};
+2 -2
View File
@@ -22,10 +22,10 @@ it("should return the path from the base element when it exists", () => {
const expectedBasePath = "/admin";
const baseElement = document.createElement("base");
baseElement.href = expectedBasePath;
document.head.appendChild(baseElement);
document.head.append(baseElement);
const actualBasePath = getBasePath();
document.head.removeChild(baseElement);
baseElement.remove();
expect(actualBasePath).toEqual(expectedBasePath);
});
+13 -13
View File
@@ -15,7 +15,7 @@ import { TimeRange } from "../components/common/context/TimeRangeContext";
export class ApiClient {
public loggedIn = false;
constructor(private http: HttpClient) {}
constructor(private readonly http: HttpClient) {}
authenticate = (key: string): Promise<ApiSuccessResponse> => {
return this.http.get("auth", {
@@ -39,14 +39,14 @@ export class ApiClient {
return this.http.get("stats/database/summary?" + timeRangeToParams(range));
};
getHistoryGraph = (): Promise<Array<ApiHistoryGraphItem>> => {
getHistoryGraph = (): Promise<ApiHistoryGraphItem[]> => {
return this.http.get("stats/overTime/history");
};
getHistoryGraphDb = (
range: TimeRange,
interval: number
): Promise<Array<ApiHistoryGraphItem>> => {
): Promise<ApiHistoryGraphItem[]> => {
return this.http.get(
"stats/database/overTime/history?interval=" +
interval +
@@ -71,11 +71,11 @@ export class ApiClient {
);
};
getQueryTypes = (): Promise<Array<ApiQueryType>> => {
getQueryTypes = (): Promise<ApiQueryType[]> => {
return this.http.get("stats/query_types");
};
getQueryTypesDb = (range: TimeRange): Promise<Array<ApiQueryType>> => {
getQueryTypesDb = (range: TimeRange): Promise<ApiQueryType[]> => {
return this.http.get(
"stats/database/query_types?" + timeRangeToParams(range)
);
@@ -161,36 +161,36 @@ export class ApiClient {
return this.http.get("stats/history?" + paramsToString(params));
};
getExactWhitelist = (): Promise<Array<string>> => {
getExactWhitelist = (): Promise<string[]> => {
return this.http.get("dns/whitelist/exact");
};
getExactBlacklist = (): Promise<Array<string>> => {
getExactBlacklist = (): Promise<string[]> => {
return this.http.get("dns/blacklist/exact");
};
getRegexWhitelist = (): Promise<Array<string>> => {
getRegexWhitelist = (): Promise<string[]> => {
return this.http.get("dns/whitelist/regex");
};
getRegexBlacklist = (): Promise<Array<string>> => {
getRegexBlacklist = (): Promise<string[]> => {
return this.http.get("dns/blacklist/regex");
};
addExactWhitelist = (domain: string): Promise<ApiSuccessResponse> => {
return this.http.post("dns/whitelist/exact", { domain: domain });
return this.http.post("dns/whitelist/exact", { domain });
};
addExactBlacklist = (domain: string): Promise<ApiSuccessResponse> => {
return this.http.post("dns/blacklist/exact", { domain: domain });
return this.http.post("dns/blacklist/exact", { domain });
};
addRegexWhitelist = (domain: string): Promise<ApiSuccessResponse> => {
return this.http.post("dns/whitelist/regex", { domain: domain });
return this.http.post("dns/whitelist/regex", { domain });
};
addRegexBlacklist = (domain: string): Promise<ApiSuccessResponse> => {
return this.http.post("dns/blacklist/regex", { domain: domain });
return this.http.post("dns/blacklist/regex", { domain });
};
removeExactWhitelist = (domain: string): Promise<ApiSuccessResponse> => {
+3 -2
View File
@@ -20,7 +20,8 @@ export const getBasePath = (): string => {
if (baseElement) {
return new URL(baseElement.href).pathname;
} else {
return process.env.PUBLIC_URL;
}
// PUBLIC_URL is supplied by CRA, and will never be undefined
return process.env.PUBLIC_URL;
};
+1 -1
View File
@@ -180,7 +180,7 @@ export const paramsToString = (params: {
[key: string]: string | number;
}): string => {
return Object.keys(params)
.map(key => key + "=" + params[key])
.map(key => `${key}=${params[key]}`)
.join("&");
};
+4 -4
View File
@@ -19,7 +19,7 @@ export interface Result<T, E> {
}
export class Ok<T, E> implements Result<T, E> {
constructor(private value: T) {}
constructor(private readonly value: T) {}
isErr(): boolean {
return false;
@@ -34,12 +34,12 @@ export class Ok<T, E> implements Result<T, E> {
}
unwrapErr(): E {
throw Error("unwrapErr on a Result.Ok");
throw new Error("unwrapErr on a Result.Ok");
}
}
export class Err<T, E> implements Result<T, E> {
constructor(private err: E) {}
constructor(private readonly err: E) {}
isErr(): boolean {
return true;
@@ -50,7 +50,7 @@ export class Err<T, E> implements Result<T, E> {
}
unwrap(): T {
throw Error("unwrap on a Result.Err");
throw new Error("unwrap on a Result.Err");
}
unwrapErr(): E {
+8 -7
View File
@@ -23,7 +23,7 @@ export function isValidHostname(hostname: string): boolean {
// If the hostname without periods make a number, deny
if (isPositiveNumber(joined)) return false;
return /^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)+(\.([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*))*$/.test(
return /^([a-zA-Z\d]+(-[a-zA-Z\d]+)*)+(\.([a-zA-Z\d]+(-[a-zA-Z\d]+)*))*$/.test(
hostname
);
}
@@ -39,15 +39,16 @@ export function isValidDomain(domain: string): boolean {
export function isPositiveNumber(input: string): boolean {
// Because parseInt has limitations, e.g. parseInt("15ex") is parsed to 15
// Caution, does not work with negative numbers, replace with /^(\-|\+)?([0-9])$/ if needed
return /^[0-9]+$/.test(input);
return /^\d+$/.test(input);
}
export function isValidRegex(regex: string): boolean {
try {
new RegExp(regex);
} catch (e) {
} catch (_) {
return false;
}
return true;
}
@@ -67,7 +68,7 @@ export function isValidIpv4(address: string): boolean {
// All segments must be numbers (positive)
return segments.every(
segment => isPositiveNumber(segment) && parseInt(segment) < 256
segment => isPositiveNumber(segment) && Number.parseInt(segment) < 256
);
}
@@ -111,7 +112,7 @@ export function isValidIpv4Cidr(cidr: string): boolean {
* @param address
*/
export function isValidIpv6(address: string): boolean {
return /^[a-fA-F0-9:]+$/.test(address);
return /^[a-fA-F\d:]+$/.test(address);
}
/**
@@ -125,7 +126,7 @@ export function isValidIpv6(address: string): boolean {
* @param address The IPv6 address
*/
export function isValidIpv6OptionalPort(address: string): boolean {
return /^(\[[a-fA-F0-9:]+]:\d+|[a-fA-F0-9:]+)$/.test(address);
return /^(\[[a-fA-F\d:]+]:\d+|[a-fA-F\d:]+)$/.test(address);
}
/**
@@ -134,7 +135,7 @@ export function isValidIpv6OptionalPort(address: string): boolean {
* @param cidr The string to check
*/
export function isValidIpv6Cidr(cidr: string): boolean {
const cidrNum = parseInt(cidr);
const cidrNum = Number.parseInt(cidr);
return !isNaN(cidrNum) && cidrNum > 0 && cidrNum <= 128 && cidrNum % 4 === 0;
}
+3 -3
View File
@@ -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, { Fragment } from "react";
import React from "react";
import SummaryStats from "../components/dashboard/SummaryStats";
import QueriesGraph from "../components/dashboard/QueriesGraph";
import { ClientsGraphContainer } from "../components/dashboard/ClientsGraph";
@@ -37,7 +37,7 @@ export default () => (
</div>
</div>
{api.loggedIn ? (
<Fragment>
<>
<div className="row">
<div className="col-md-12">
<ClientsGraphContainer />
@@ -67,7 +67,7 @@ export default () => (
<TopBlockedClients />
</div>
</div>
</Fragment>
</>
) : null}
</div>
);
+2 -2
View File
@@ -21,11 +21,11 @@ const Blacklist: FunctionComponent<WithTranslation> = props => {
<ListPage
title={`${t("Blacklist")} (${t("Exact")})`}
placeholder={t("Add a domain or hostname (example.com or example)")}
isValid={isValidHostname}
validationErrorMsg={t("Not a valid hostname")}
onAdd={api.addExactBlacklist}
onRemove={api.removeExactBlacklist}
onRefresh={api.getExactBlacklist}
isValid={isValidHostname}
validationErrorMsg={t("Not a valid hostname")}
{...props}
/>
);
+2 -2
View File
@@ -21,11 +21,11 @@ const Whitelist: FunctionComponent<WithTranslation> = props => {
<ListPage
title={`${t("Whitelist")} (${t("Exact")})`}
placeholder={t("Add a domain or hostname (example.com or example)")}
isValid={isValidHostname}
validationErrorMsg={t("Not a valid hostname")}
onAdd={api.addExactWhitelist}
onRemove={api.removeExactWhitelist}
onRefresh={api.getExactWhitelist}
isValid={isValidHostname}
validationErrorMsg={t("Not a valid hostname")}
{...props}
/>
);
+7 -7
View File
@@ -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, { ChangeEvent, Component, FormEvent, Fragment } from "react";
import React, { ChangeEvent, Component, FormEvent } from "react";
import { Redirect } from "react-router-dom";
import sha from "sha.js";
import api from "../util/api";
@@ -56,7 +56,7 @@ class Login extends Component<LoginProps, LoginState> {
*/
authenticate = (e?: FormEvent) => {
// Prevent the page from reloading when the user gets redirected
e && e.preventDefault();
e?.preventDefault();
// Hash the password twice before sending to the API
let hashedPassword = sha("sha256")
@@ -79,7 +79,7 @@ class Login extends Component<LoginProps, LoginState> {
}
// Redirect to the page the user was originally going to, or if that doesn't exist, go to home
const locationState = this.props.location.state || {
const locationState = this.props.location.state ?? {
from: { pathname: "/" }
};
this.props.history.push(locationState.from.pathname);
@@ -116,12 +116,12 @@ class Login extends Component<LoginProps, LoginState> {
// tell them they will be redirected once login is successful
this.props.location.state &&
this.props.location.state.from.pathname in routes(t) ? (
<Fragment>
<>
<br />
{t('You will be transferred to the "{{page}}" page', {
page: routes(t)[this.props.location.state.from.pathname]
})}
</Fragment>
</>
) : null
}
{
@@ -152,12 +152,12 @@ class Login extends Component<LoginProps, LoginState> {
}
>
<input
autoFocus
type="password"
className="form-control"
value={this.state.password}
onChange={this.handlePasswordChange}
placeholder={t("Password")}
autoFocus
onChange={this.handlePasswordChange}
/>
</div>
<br />
+2 -2
View File
@@ -21,11 +21,11 @@ const RegexBlacklist: FunctionComponent<WithTranslation> = props => {
<ListPage
title={`${t("Blacklist")} (${t("Regex")})`}
placeholder={t("Input a regular expression")}
isValid={isValidRegex}
validationErrorMsg={t("Not a valid regular expression")}
onAdd={api.addRegexBlacklist}
onRemove={api.removeRegexBlacklist}
onRefresh={api.getRegexBlacklist}
isValid={isValidRegex}
validationErrorMsg={t("Not a valid regular expression")}
{...props}
/>
);
+2 -2
View File
@@ -21,11 +21,11 @@ const RegexWhitelist: FunctionComponent<WithTranslation> = props => {
<ListPage
title={`${t("Whitelist")} (${t("Regex")})`}
placeholder={t("Input a regular expression")}
isValid={isValidRegex}
validationErrorMsg={t("Not a valid regular expression")}
onAdd={api.addRegexWhitelist}
onRemove={api.removeRegexWhitelist}
onRefresh={api.getRegexWhitelist}
isValid={isValidRegex}
validationErrorMsg={t("Not a valid regular expression")}
{...props}
/>
);