mirror of
https://github.com/gosticks/web.git
synced 2026-08-12 12:10:20 +00:00
@@ -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"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
☰
|
||||
</button>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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])
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
<>
|
||||
|
||||
{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[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
|
||||
|
||||
export interface TopClientsData {
|
||||
totalQueries: number;
|
||||
topClients: Array<ApiClientData>;
|
||||
topClients: ApiClientData[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TimeRangeContext } from "../common/context/TimeRangeContext";
|
||||
|
||||
export interface TopDomainsData {
|
||||
totalQueries: number;
|
||||
topDomains: Array<ApiTopDomainItem>;
|
||||
topDomains: ApiTopDomainItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
<br />
|
||||
|
||||
</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>
|
||||
|
||||
<br />
|
||||
|
||||
</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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -38,7 +38,7 @@ export const loadInitialPreferences = (): ApiPreferences => {
|
||||
|
||||
try {
|
||||
return JSON.parse(cachedPreferencesString);
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return defaultPreferences;
|
||||
}
|
||||
};
|
||||
|
||||
+2
-2
@@ -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
@@ -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";
|
||||
|
||||
|
||||
Vendored
+9
-9
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ export function makeCancelable<T>(
|
||||
if (repeatId !== null) {
|
||||
clearTimeout(repeatId);
|
||||
}
|
||||
|
||||
hasCanceled = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
@@ -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> => {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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 />
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user