Convert settings components to TypeScript

Signed-off-by: Mcat12 <newtoncat12@yahoo.com>
This commit is contained in:
Mcat12
2019-01-23 22:06:38 -08:00
parent dbfd6b6c28
commit 0af3e21ab2
17 changed files with 337 additions and 219 deletions
+10
View File
@@ -1505,6 +1505,16 @@
"@types/react": "*"
}
},
"@types/react-bootstrap-typeahead": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@types/react-bootstrap-typeahead/-/react-bootstrap-typeahead-3.4.0.tgz",
"integrity": "sha512-J2JFcXWoY4234vnBGGh41PdTLMLQT9tZzkn1iBzuf/lRJv+tnGX2BtmbFUmKhwFzGFbjHqKubUvtHnFsYNohRQ==",
"dev": true,
"requires": {
"@types/react": "*",
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
"version": "16.0.11",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.11.tgz",
+1
View File
@@ -14,6 +14,7 @@
"@types/node": "^10.12.18",
"@types/react": "^16.7.20",
"@types/react-bootstrap-daterangepicker": "0.0.26",
"@types/react-bootstrap-typeahead": "^3.4.0",
"@types/react-dom": "^16.0.11",
"@types/react-router-dom": "^4.3.1",
"@types/react-table": "^6.7.21",
@@ -9,8 +9,22 @@
* Please see LICENSE file for your rights under this license. */
import React, { Fragment } from "react";
import PropTypes from "prop-types";
import { Col, FormGroup, Input, Label } from "reactstrap";
import i18next from "i18next";
export interface ConditionalForwardingObject {
enabled: boolean,
routerIp: string,
domain: string
}
export interface ConditionalForwardingSettingsProps {
settings: ConditionalForwardingObject,
onUpdate: (settings: ConditionalForwardingObject) => void,
isRouterIpValid: boolean;
isDomainValid: boolean;
t: i18next.TranslationFunction
}
const ConditionalForwardingSettings = ({
settings,
@@ -18,7 +32,7 @@ const ConditionalForwardingSettings = ({
isRouterIpValid,
isDomainValid,
t
}) => (
}: ConditionalForwardingSettingsProps) => (
<Fragment>
<FormGroup check>
<Label check>
@@ -61,12 +75,4 @@ const ConditionalForwardingSettings = ({
</Fragment>
);
ConditionalForwardingSettings.propTypes = {
settings: PropTypes.object.isRequired,
onUpdate: PropTypes.func.isRequired,
isRouterIpValid: PropTypes.bool.isRequired,
isDomainValid: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired
};
export default ConditionalForwardingSettings;
@@ -8,31 +8,26 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import { withNamespaces } from "react-i18next";
import { ignoreCancel, makeCancelable } from "../../util";
import api from "../../util/api";
import {
Button,
Col,
Form,
FormGroup,
Input,
InputGroup,
InputGroupAddon,
Label
} from "reactstrap";
import {
isPositiveNumber,
isValidHostname,
isValidIpv4
} from "../../util/validate";
import Alert from "../common/Alert";
import React, { ChangeEvent, Component, FormEvent } from "react";
import { WithNamespaces, withNamespaces } from "react-i18next";
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
import api, { ApiDhcpSettings, ApiResultResponse } from "../../util/api";
import { Button, Col, Form, FormGroup, Input, InputGroup, InputGroupAddon, Label } from "reactstrap";
import { isValidHostname, isValidIpv4 } from "../../util/validate";
import Alert, { AlertType } from "../common/Alert";
class DHCPInfo extends Component {
state = {
export interface DHCPInfoState {
alertMessage: string;
alertType: AlertType;
showAlert: boolean;
processing: boolean;
settings: ApiDhcpSettings;
}
class DHCPInfo extends Component<WithNamespaces, DHCPInfoState> {
state: DHCPInfoState = {
alertMessage: "",
alertType: "",
alertType: "info",
showAlert: false,
processing: false,
settings: {
@@ -40,27 +35,20 @@ class DHCPInfo extends Component {
ip_start: "",
ip_end: "",
router_ip: "",
lease_time: "",
lease_time: 0,
domain: "",
ipv6_support: false
}
};
private loadHandler: undefined | CancelablePromise<ApiDhcpSettings>;
private updateHandler: undefined | CancelablePromise<ApiResultResponse>;
loadDHCPInfo = () => {
this.loadHandler = makeCancelable(api.getDHCPInfo());
this.loadHandler.promise
.then(res => {
this.setState({
settings: {
active: res.active,
ip_start: res.ip_start,
ip_end: res.ip_end,
router_ip: res.router_ip,
lease_time: res.lease_time,
domain: res.domain,
ipv6_support: res.ipv6_support
}
});
this.setState({ settings: res });
})
.catch(ignoreCancel);
};
@@ -70,7 +58,9 @@ class DHCPInfo extends Component {
}
componentWillUnmount() {
this.loadHandler.cancel();
if (this.loadHandler) {
this.loadHandler.cancel();
}
if (this.updateHandler) {
this.updateHandler.cancel();
@@ -85,9 +75,10 @@ class DHCPInfo extends Component {
* @param attr {string} the event target attribute to use
* @returns {function(Event)}
*/
onChange = (key, attr) => {
return e => {
const value = e.target[attr];
onChange = (key: string, attr: string) => {
return (e: ChangeEvent) => {
// @ts-ignore
const value: string = e.target[attr];
this.setState(oldState => ({
settings: {
@@ -103,7 +94,7 @@ class DHCPInfo extends Component {
*
* @param e the submit event
*/
saveSettings = e => {
saveSettings = (e: FormEvent) => {
e.preventDefault();
const { t } = this.props;
@@ -155,7 +146,7 @@ class DHCPInfo extends Component {
* @param value the value to check
* @param validator the validation function
*/
isSettingValid = (value, validator) => {
isSettingValid = (value: string, validator: (value: string) => boolean) => {
return (
(!this.state.settings.active && value.length === 0) || validator(value)
);
@@ -180,10 +171,7 @@ class DHCPInfo extends Component {
this.state.settings.router_ip,
isValidIpv4
);
const isLeaseTimeValid = this.isSettingValid(
this.state.settings.lease_time,
isPositiveNumber
);
const isLeaseTimeValid = this.state.settings.lease_time >= 0;
const isDomainValid = this.isSettingValid(
this.state.settings.domain,
isValidHostname
@@ -262,7 +250,14 @@ class DHCPInfo extends Component {
id="leaseTime"
disabled={!this.state.settings.active}
value={this.state.settings.lease_time}
onChange={this.onChange("lease_time", "value")}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
this.setState(oldState => ({
settings: {
...oldState.settings,
lease_time: parseInt(e.target.value)
}
}))
}
invalid={!isLeaseTimeValid}
/>
<InputGroupAddon addonType="append">Hours</InputGroupAddon>
@@ -8,21 +8,31 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import { withNamespaces } from "react-i18next";
import { ignoreCancel, makeCancelable } from "../../util";
import api from "../../util/api";
import React, { Component, FormEvent } from "react";
import { WithNamespaces, withNamespaces } from "react-i18next";
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
import api, { ApiDnsSettings, ApiResultResponse } from "../../util/api";
import DnsList from "./DnsList";
import { Button, Col, Form, FormGroup } from "reactstrap";
import ConditionalForwardingSettings from "./ConditionalForwardingSettings";
import DnsOptionSettings from "./DnsOptionSettings";
import Alert from "../common/Alert";
import ConditionalForwardingSettings, { ConditionalForwardingObject } from "./ConditionalForwardingSettings";
import DnsOptionSettings, { DnsOptionsObject } from "./DnsOptionSettings";
import Alert, { AlertType } from "../common/Alert";
import { isValidHostname, isValidIpv4 } from "../../util/validate";
class DNSInfo extends Component {
state = {
export interface DNSInfoState {
alertMessage: string;
alertType: AlertType;
showAlert: boolean;
processing: boolean;
upstreamDns: Array<string>;
conditionalForwarding: ConditionalForwardingObject;
options: DnsOptionsObject;
}
class DNSInfo extends Component<WithNamespaces, DNSInfoState> {
state: DNSInfoState = {
alertMessage: "",
alertType: "",
alertType: "info",
showAlert: false,
processing: false,
upstreamDns: [],
@@ -39,6 +49,9 @@ class DNSInfo extends Component {
}
};
private loadHandler: undefined | CancelablePromise<ApiDnsSettings>;
private updateHandler: undefined | CancelablePromise<ApiResultResponse>;
loadDNSInfo = () => {
this.loadHandler = makeCancelable(api.getDNSInfo());
this.loadHandler.promise
@@ -70,26 +83,30 @@ class DNSInfo extends Component {
}
componentWillUnmount() {
this.loadHandler.cancel();
if (this.loadHandler) {
this.loadHandler.cancel();
}
}
handleUpstreamAdd = upstream => {
handleUpstreamAdd = (upstream: string) => {
this.setState({
upstreamDns: this.state.upstreamDns.concat(upstream)
});
};
handleUpstreamRemove = upstream => {
handleUpstreamRemove = (upstream: string) => {
this.setState({
upstreamDns: this.state.upstreamDns.filter(item => item !== upstream)
});
};
handleConditionalForwardingUpdate = conditionalForwarding => {
handleConditionalForwardingUpdate = (
conditionalForwarding: ConditionalForwardingObject
) => {
this.setState({ conditionalForwarding });
};
handleDnsOptionsUpdate = options => {
handleDnsOptionsUpdate = (options: DnsOptionsObject) => {
this.setState({ options });
};
@@ -98,7 +115,7 @@ class DNSInfo extends Component {
*
* @param e the submit event
*/
saveSettings = e => {
saveSettings = (e: FormEvent) => {
e.preventDefault();
const { t } = this.props;
@@ -157,7 +174,7 @@ class DNSInfo extends Component {
});
};
isCFSettingValid = (value, validator) => {
isCFSettingValid = (value: string, validator: (value: string) => boolean) => {
return (
(!this.state.conditionalForwarding.enabled && value.length === 0) ||
validator(value)
@@ -9,12 +9,17 @@
* Please see LICENSE file for your rights under this license. */
import React from "react";
import PropTypes from "prop-types";
import { ListGroup } from "reactstrap";
import DnsListItem from "./DnsListItem";
import DnsListNewItem from "./DnsListNewItem";
import { isValidIpv4OptionalPort } from "../../util/validate";
export interface DnsListProps {
upstreams: Array<string>;
onAdd: (upstream: string) => void;
onRemove: (upstream: string) => void;
}
/**
* Check if an upstream address is unique and valid
*
@@ -22,10 +27,10 @@ import { isValidIpv4OptionalPort } from "../../util/validate";
* @param upstreams the list of current upstreams
* @returns {boolean} if the upstream address is unique and valid
*/
export const isAddressValid = (address, upstreams) =>
export const isAddressValid = (address: string, upstreams: Array<string>) =>
!upstreams.includes(address) && isValidIpv4OptionalPort(address);
const DnsList = ({ upstreams, onAdd, onRemove }) => (
const DnsList = ({ upstreams, onAdd, onRemove }: DnsListProps) => (
<ListGroup style={{ overflowY: "scroll", maxHeight: "350px" }}>
{upstreams.map(upstream => (
<DnsListItem
@@ -36,16 +41,10 @@ const DnsList = ({ upstreams, onAdd, onRemove }) => (
))}
<DnsListNewItem
onAdd={onAdd}
isValid={address => isAddressValid(address, upstreams)}
isValid={(address: string) => isAddressValid(address, upstreams)}
upstreams={upstreams}
/>
</ListGroup>
);
DnsList.propTypes = {
upstreams: PropTypes.arrayOf(PropTypes.string).isRequired,
onAdd: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired
};
export default DnsList;
@@ -9,10 +9,14 @@
* Please see LICENSE file for your rights under this license. */
import React from "react";
import PropTypes from "prop-types";
import { Button, ListGroupItem } from "reactstrap";
const DnsListItem = ({ address, onRemove }) => (
export interface DnsListItemProps {
address: string,
onRemove: () => void;
}
const DnsListItem = ({ address, onRemove }: DnsListItemProps) => (
<ListGroupItem>
{address}
<Button color="danger" size="sm" className="pull-right" onClick={onRemove}>
@@ -21,9 +25,4 @@ const DnsListItem = ({ address, onRemove }) => (
</ListGroupItem>
);
DnsListItem.propTypes = {
address: PropTypes.string.isRequired,
onRemove: PropTypes.func.isRequired
};
export default DnsListItem;
@@ -8,30 +8,39 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import PropTypes from "prop-types";
import React, { Component, RefObject } from "react";
import { Button, InputGroup, InputGroupAddon, ListGroupItem } from "reactstrap";
import { Typeahead } from "react-bootstrap-typeahead";
import { withNamespaces } from "react-i18next";
import { preconfiguredUpstreamOptions } from "./preconfiguredUpstreams";
import { WithNamespaces, withNamespaces } from "react-i18next";
import { PreconfiguredUpstreamOption, preconfiguredUpstreamOptions } from "./preconfiguredUpstreams";
export interface DnsListNewItemProps {
onAdd: (address: string) => void;
isValid: (address: string) => boolean;
upstreams: Array<string>;
}
export interface DnsListNewItemState {
address: string;
selected: Array<PreconfiguredUpstreamOption>;
}
/**
* A component to add upstream DNS servers. The servers are either selected from
* a list of preconfigured upstreams, or custom servers can be input.
*/
class DnsListNewItem extends Component {
static propTypes = {
onAdd: PropTypes.func.isRequired,
isValid: PropTypes.func.isRequired,
upstreams: PropTypes.arrayOf(PropTypes.string).isRequired
};
state = {
class DnsListNewItem extends Component<
DnsListNewItemProps & WithNamespaces,
DnsListNewItemState
> {
state: DnsListNewItemState = {
address: "",
selected: []
};
constructor(props) {
private readonly typeahead: RefObject<any>;
constructor(props: DnsListNewItemProps & WithNamespaces) {
super(props);
this.typeahead = React.createRef();
}
@@ -11,8 +11,26 @@
import React, { Fragment } from "react";
import PropTypes from "prop-types";
import { Col, FormGroup, Input, Label } from "reactstrap";
import i18next from "i18next";
const DnsOptionSettings = ({ settings, onUpdate, t }) => (
export interface DnsOptionsObject {
fqdnRequired: boolean;
bogusPriv: boolean;
dnssec: boolean;
listeningType: string;
}
export interface DnsOptionSettingsProps {
settings: DnsOptionsObject;
onUpdate: (settings: DnsOptionsObject) => void;
t: i18next.TranslationFunction;
}
const DnsOptionSettings = ({
settings,
onUpdate,
t
}: DnsOptionSettingsProps) => (
<Fragment>
<FormGroup row>
<Label for="listeningBehavior" sm={5}>
@@ -9,18 +9,17 @@
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withNamespaces } from "react-i18next";
import api from "../../util/api";
import { WithNamespaces, withNamespaces } from "react-i18next";
import api, { ApiFtlDbResponse } from "../../util/api";
import { WithAPIData } from "../common/WithAPIData";
class FTLInfo extends Component {
static propTypes = {
fileSize: PropTypes.number.isRequired,
queries: PropTypes.number.isRequired,
sqliteVersion: PropTypes.string.isRequired
};
export interface FTLInfoProps {
fileSize: number,
queries: number,
sqliteVersion: string
}
class FTLInfo extends Component<FTLInfoProps & WithNamespaces, {}> {
render() {
const { t } = this.props;
@@ -36,7 +35,7 @@ class FTLInfo extends Component {
}
}
export const transformData = data => ({
export const transformData = (data: ApiFtlDbResponse): FTLInfoProps => ({
fileSize: data.filesize,
queries: data.queries,
sqliteVersion: data.sqlite_version
@@ -50,11 +49,12 @@ export const initialData = {
export const TranslatedFTLInfo = withNamespaces(["settings"])(FTLInfo);
export default props => (
export default (props: any) => (
<WithAPIData
apiCall={api.getFTLdb}
repeatOptions={{
interval: 600000
interval: 600000,
ignoreCancel: true
}}
renderInitial={() => <TranslatedFTLInfo {...initialData} {...props} />}
renderOk={data => <TranslatedFTLInfo {...transformData(data)} {...props} />}
@@ -9,20 +9,19 @@
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withNamespaces } from "react-i18next";
import api from "../../util/api";
import { Subtract, WithNamespaces, withNamespaces } from "react-i18next";
import api, { ApiNetworkSettings } from "../../util/api";
import { Col, Form, FormGroup, Input, Label } from "reactstrap";
import { WithAPIData } from "../common/WithAPIData";
class NetworkInfo extends Component {
static propTypes = {
interface: PropTypes.string.isRequired,
ipv4Address: PropTypes.string.isRequired,
ipv6Address: PropTypes.string.isRequired,
hostname: PropTypes.string.isRequired
};
export interface NetworkInfoProps extends WithNamespaces {
interface: string;
ipv4Address: string;
ipv6Address: string;
hostname: string;
}
class NetworkInfo extends Component<NetworkInfoProps, {}> {
render() {
const { t } = this.props;
@@ -85,7 +84,9 @@ class NetworkInfo extends Component {
}
}
export const transformData = data => ({
export const transformData = (
data: ApiNetworkSettings
): Subtract<NetworkInfoProps, WithNamespaces> => ({
interface: data.interface,
ipv4Address: data.ipv4_address,
ipv6Address: data.ipv6_address,
@@ -103,7 +104,7 @@ export const TranslatedNetworkInfo = withNamespaces(["common", "settings"])(
NetworkInfo
);
export default props => (
export default (props: any) => (
<WithAPIData
apiCall={api.getNetworkInfo}
repeatOptions={{
@@ -8,44 +8,50 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withNamespaces } from "react-i18next";
import { ignoreCancel, makeCancelable } from "../../util";
import api from "../../util/api";
import Alert from "../common/Alert";
import React, { ChangeEvent, Component, FormEvent } from "react";
import { WithNamespaces, withNamespaces } from "react-i18next";
import { CancelablePromise, ignoreCancel, makeCancelable } from "../../util";
import api, { ApiPreferences, ApiResultResponse } from "../../util/api";
import Alert, { AlertType } from "../common/Alert";
import { Button, Col, Form, FormGroup, Input, Label } from "reactstrap";
import { PreferencesContext } from "../common/context";
import languages from "../../languages";
import languages from "../../languages.json";
import i18n from "i18next";
class PreferenceSettings extends Component {
static propTypes = {
settings: PropTypes.shape({
layout: PropTypes.string.isRequired
}),
refresh: PropTypes.func.isRequired
};
export interface PreferenceSettingsProps {
settings: ApiPreferences;
refresh: (preferences?: ApiPreferences) => void;
}
state = {
export interface PreferenceSettingsState {
alertMessage: string;
alertType: AlertType;
showAlert: boolean;
processing: boolean;
settings: ApiPreferences;
}
class PreferenceSettings extends Component<
PreferenceSettingsProps & WithNamespaces,
PreferenceSettingsState
> {
state: PreferenceSettingsState = {
alertMessage: "",
alertType: "",
alertType: "info",
showAlert: false,
processing: false,
// Initial value is the current settings
settings: this.props.settings
};
private loadHandler: undefined | CancelablePromise<ApiPreferences>;
private updateHandler: undefined | CancelablePromise<ApiResultResponse>;
loadPreferences = () => {
this.loadHandler = makeCancelable(api.getPreferences());
this.loadHandler.promise
.then(res => {
this.setState({
settings: {
layout: res.layout,
language: res.language
}
});
this.setState({ settings: res });
})
.catch(ignoreCancel);
};
@@ -55,7 +61,9 @@ class PreferenceSettings extends Component {
}
componentWillUnmount() {
this.loadHandler.cancel();
if (this.loadHandler) {
this.loadHandler.cancel();
}
if (this.updateHandler) {
this.updateHandler.cancel();
@@ -70,9 +78,10 @@ class PreferenceSettings extends Component {
* @param attr {string} the event target attribute to use
* @returns {function(Event)}
*/
onChange = (key, attr) => {
return e => {
const value = e.target[attr];
onChange = (key: string, attr: string) => {
return (e: ChangeEvent) => {
// @ts-ignore
const value: string = e.target[attr];
this.setState(oldState => ({
settings: {
@@ -88,7 +97,7 @@ class PreferenceSettings extends Component {
*
* @param e the submit event
*/
saveSettings = e => {
saveSettings = (e: FormEvent) => {
e.preventDefault();
const { t } = this.props;
@@ -9,19 +9,23 @@
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import { withNamespaces } from "react-i18next";
import PropTypes from "prop-types";
import { WithNamespaces, withNamespaces } from "react-i18next";
import { Collapse } from "reactstrap";
class VersionCard extends Component {
static propTypes = {
branch: PropTypes.string.isRequired,
hash: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
tag: PropTypes.string.isRequired
};
export interface VersionCardProps extends WithNamespaces {
branch: string;
hash: string;
name: string;
tag: string;
icon: string;
}
state = {
export interface VersionCardState {
collapsed: boolean
}
class VersionCard extends Component<VersionCardProps, VersionCardState> {
state: VersionCardState = {
collapsed: true
};
@@ -9,26 +9,12 @@
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withNamespaces } from "react-i18next";
import api from "../../util/api";
import { WithNamespaces, withNamespaces } from "react-i18next";
import api, { ApiVersions } from "../../util/api";
import VersionCard from "./VersionCard";
import { WithAPIData } from "../common/WithAPIData";
class VersionInfo extends Component {
static versionPropType = PropTypes.shape({
branch: PropTypes.string.isRequired,
hash: PropTypes.string.isRequired,
tag: PropTypes.string.isRequired
}).isRequired;
static propTypes = {
api: VersionInfo.versionPropType,
core: VersionInfo.versionPropType,
ftl: VersionInfo.versionPropType,
web: VersionInfo.versionPropType
};
class VersionInfo extends Component<ApiVersions & WithNamespaces, {}> {
render() {
const { t } = this.props;
@@ -75,7 +61,7 @@ class VersionInfo extends Component {
}
}
export const initialData = {
export const initialData: ApiVersions = {
api: {
branch: "unknown",
hash: "unknown",
@@ -100,10 +86,10 @@ export const initialData = {
export const TranslatedVersionInfo = withNamespaces(["common"])(VersionInfo);
export default props => (
export default (props: any) => (
<WithAPIData
apiCall={api.getVersion}
repeatOptions={{ interval: 600000 }}
repeatOptions={{ interval: 600000, ignoreCancel: true }}
renderInitial={() => <TranslatedVersionInfo {...initialData} {...props} />}
renderOk={data => <TranslatedVersionInfo {...data} {...props} />}
renderErr={() => <TranslatedVersionInfo {...initialData} {...props} />}
@@ -9,9 +9,12 @@
* Please see LICENSE file for your rights under this license. */
import React from "react";
import { shallow } from "enzyme";
import DHCPInfo from "../DHCPInfo";
import { shallow, ShallowWrapper } from "enzyme";
import DHCPInfo, { DHCPInfoState } from "../DHCPInfo";
import fetchMock from "fetch-mock";
import { WithNamespaces } from "react-i18next";
const tick = global.tick;
const endpoint = "/admin/api/settings/dhcp";
const fakeData = {
@@ -51,7 +54,9 @@ it("disables the apply button if an input is invalid", async () => {
it("disables inputs only when DHCP is not enabled", async () => {
fetchMock.mock(endpoint, { body: fakeData });
const wrapper = shallow(<DHCPInfo />);
const wrapper: ShallowWrapper<WithNamespaces, DHCPInfoState> = shallow(
<DHCPInfo />
);
await tick();
wrapper.update();
@@ -69,7 +74,9 @@ it("disables the apply button when processing setting update", async () => {
fetchMock.get(endpoint, { body: fakeData });
fetchMock.put(endpoint, { body: { status: "success" } });
const wrapper = shallow(<DHCPInfo />);
const wrapper: ShallowWrapper<WithNamespaces, DHCPInfoState> = shallow(
<DHCPInfo />
);
await tick();
@@ -86,7 +93,9 @@ it("sends the correct data to the API when apply is clicked", async () => {
fetchMock.get(endpoint, { body: fakeData });
fetchMock.put(endpoint, { body: { status: "success" } });
const wrapper = shallow(<DHCPInfo />);
const wrapper: ShallowWrapper<WithNamespaces, DHCPInfoState> = shallow(
<DHCPInfo />
);
await tick();
@@ -99,9 +108,9 @@ it("sends the correct data to the API when apply is clicked", async () => {
expect(wrapper.find("Button")).toBeDisabled();
// The output of lastCall is [url, request]
const updateRequest = fetchMock.lastCall(endpoint)[1];
const updateRequest = fetchMock.lastCall(endpoint)![1];
expect(JSON.parse(updateRequest.body)).toEqual(fakeData);
expect(JSON.parse(updateRequest!.body as string)).toEqual(fakeData);
});
it("shows a success message after successfully saving settings", async () => {
@@ -8,11 +8,19 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
export interface PreconfiguredUpstream {
name: string;
primaryIpv4: string;
secondaryIpv4: string;
primaryIpv6: string;
secondaryIpv6: string;
}
/**
* 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 = [
export const preconfiguredUpstreams: Array<PreconfiguredUpstream> = [
{
name: "OpenDNS (ECS)",
primaryIpv4: "208.67.222.222",
@@ -78,6 +86,11 @@ export const preconfiguredUpstreams = [
}
];
export interface PreconfiguredUpstreamOption {
label: string;
address: string;
}
/**
* The preconfigured servers, but with an entry for each address. This is used
* when displaying the servers in a list.
@@ -85,12 +98,12 @@ export const preconfiguredUpstreams = [
export const preconfiguredUpstreamOptions = preconfiguredUpstreams.flatMap(
upstream => {
// Parse the upstream address information into the new format
const parseUpstream = (address, order, type) => ({
const parseUpstream = (address: string, order: string, type: string) => ({
label: upstream.name + " " + order + " " + type + " (" + address + ")",
address
});
let parsedUpstreams = [];
let parsedUpstreams: Array<PreconfiguredUpstreamOption> = [];
if (upstream.primaryIpv4.length > 0) {
parsedUpstreams.push(
+66 -24
View File
@@ -24,46 +24,88 @@ export interface ApiQuery {
export interface ApiHistoryResponse {
cursor: null | string;
history: Array<ApiQuery>
history: Array<ApiQuery>;
}
export interface ApiNetworkSettings {
interface: string;
ipv4_address: string;
ipv6_address: string;
hostname: string;
}
export interface ApiVersion {
branch: string;
hash: string;
tag: string;
}
export interface ApiVersions {
api: ApiVersion;
core: ApiVersion;
ftl: ApiVersion;
web: ApiVersion;
}
export interface ApiDnsSettings {
upstream_dns: Array<string>,
upstream_dns: Array<string>;
options: {
fqdn_required: boolean,
bogus_priv: boolean,
dnssec: boolean,
listening_type: string
},
fqdn_required: boolean;
bogus_priv: boolean;
dnssec: boolean;
listening_type: string;
};
conditional_forwarding: {
enabled: boolean,
router_ip: string,
domain: string
}
enabled: boolean;
router_ip: string;
domain: string;
};
}
export interface ApiDhcpSettings {
active: boolean,
ip_start: string,
ip_end: string,
router_ip: string,
lease_time: number,
domain: string,
ipv6_support: boolean
active: boolean;
ip_start: string;
ip_end: string;
router_ip: string;
lease_time: number;
domain: string;
ipv6_support: boolean;
}
export interface ApiFtlDbResponse {
filesize: number;
queries: number;
sqlite_version: string;
}
export interface ApiSuccessResponse {
status: "success";
}
export interface ApiError {
key: string;
message: string;
data: any;
}
export interface ApiErrorResponse {
error: ApiError;
}
export type ApiResultResponse = ApiSuccessResponse | ApiErrorResponse;
export type WebLayout = "boxed" | "traditional";
export interface ApiPreferences {
layout: WebLayout,
language: string
layout: WebLayout;
language: string;
}
export type Status = "enabled" | "disabled" | "unknown";
export type StatusAction = "enable" | "disable";
export interface ApiStatus {
status: Status
status: Status;
}
export default {
@@ -142,13 +184,13 @@ export default {
setStatus(action: StatusAction, time: number | null = null) {
return http.post("dns/status", { action, time });
},
getNetworkInfo() {
getNetworkInfo(): Promise<ApiNetworkSettings> {
return http.get("settings/network");
},
getVersion() {
getVersion(): Promise<ApiVersions> {
return http.get("version");
},
getFTLdb() {
getFTLdb(): Promise<ApiFtlDbResponse> {
return http.get("settings/ftldb");
},
getDNSInfo(): Promise<ApiDnsSettings> {