Convert the query log to TypeScript

Signed-off-by: Mcat12 <newtoncat12@yahoo.com>
This commit is contained in:
Mcat12
2019-01-23 20:59:13 -08:00
parent f9270074a4
commit 35e0c3af7e
4 changed files with 145 additions and 34 deletions
+65
View File
@@ -1384,6 +1384,16 @@
"integrity": "sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==",
"dev": true
},
"@types/daterangepicker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/daterangepicker/-/daterangepicker-3.0.0.tgz",
"integrity": "sha512-jxPTuNceIF1RoVLcjVzG0L7zM2gB/Zdc5WmiI7gdiPo7Jf9oWAUTQUI49/jmepRdxIBo/ThCw6K+0xZc2Tynvw==",
"dev": true,
"requires": {
"@types/jquery": "*",
"moment": ">=2.14.0"
}
},
"@types/enzyme": {
"version": "3.1.15",
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.1.15.tgz",
@@ -1415,6 +1425,12 @@
"integrity": "sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q==",
"dev": true
},
"@types/i18next-browser-languagedetector": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.0.1.tgz",
"integrity": "sha512-9TwP0tw0PSXUtBtM12PFUjBsl9sP5bNHd54qdSXBrrZ4HJmtVdqbKvmXLH4Qd3KQzIS2g/Z7HhLhygL2LUSb8w==",
"dev": true
},
"@types/i18next-xhr-backend": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@types/i18next-xhr-backend/-/i18next-xhr-backend-1.4.1.tgz",
@@ -1427,6 +1443,30 @@
"integrity": "sha512-ePl4l+7dLLmCucIwgQHAgjiepY++qcI6nb8eAwGNkB6OxmTe3Z9rQU3rSpomqu42PCCnlThZbOoxsf+qylJsLA==",
"dev": true
},
"@types/jquery": {
"version": "3.3.29",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz",
"integrity": "sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/lodash": {
"version": "4.14.120",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.120.tgz",
"integrity": "sha512-jQ21kQ120mo+IrDs1nFNVm/AsdFxIx2+vZ347DbogHJPd/JzKNMOqU6HCYin1W6v8l5R9XSO2/e9cxmn7HAnVw==",
"dev": true
},
"@types/lodash.debounce": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.4.tgz",
"integrity": "sha512-W3oJCQXSCmOE9uIqOdrUWT08YNSXyqXed8JhxJKCe4SH40yxz5HSdtStN1ZQYkvT7S/tae8PA34Y0TO5C7Z8Ng==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/node": {
"version": "10.12.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
@@ -1455,6 +1495,16 @@
"csstype": "^2.2.0"
}
},
"@types/react-bootstrap-daterangepicker": {
"version": "0.0.26",
"resolved": "https://registry.npmjs.org/@types/react-bootstrap-daterangepicker/-/react-bootstrap-daterangepicker-0.0.26.tgz",
"integrity": "sha512-Y5XfjMtejmG3sH6HL/8sxSh5wXeODGd2P9A6SuPbJjVWrGGvuANLc/4tzff6LMH8dbqc/HGU6FCSkdts1lP6fw==",
"dev": true,
"requires": {
"@types/daterangepicker": "*",
"@types/react": "*"
}
},
"@types/react-dom": {
"version": "16.0.11",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.11.tgz",
@@ -1485,6 +1535,15 @@
"@types/react-router": "*"
}
},
"@types/react-table": {
"version": "6.7.21",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.7.21.tgz",
"integrity": "sha512-XiYCcn/CBajrj18vLA3kO79AHr5yZTCJe2kl87ZNTRxLO14y9D0IGeGZ3xLsqhfYrJSkkVzAJV8v+bQ4nuKCRQ==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/reactstrap": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/@types/reactstrap/-/reactstrap-6.4.4.tgz",
@@ -1504,6 +1563,12 @@
"@types/node": "*"
}
},
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
"dev": true
},
"@types/tapable": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.2.tgz",
+3
View File
@@ -10,10 +10,13 @@
"@types/i18next-browser-languagedetector": "^2.0.1",
"@types/i18next-xhr-backend": "^1.4.1",
"@types/jest": "^23.3.13",
"@types/lodash.debounce": "^4.0.4",
"@types/node": "^10.12.18",
"@types/react": "^16.7.20",
"@types/react-bootstrap-daterangepicker": "0.0.26",
"@types/react-dom": "^16.0.11",
"@types/react-router-dom": "^4.3.1",
"@types/react-table": "^6.7.21",
"@types/reactstrap": "^6.4.4",
"@types/sha.js": "^2.4.0",
"enzyme": "^3.8.0",
@@ -9,21 +9,32 @@
* Please see LICENSE file for your rights under this license. */
import React, { Component, Fragment } from "react";
import ReactTable from "react-table";
import ReactTable, { Filter, ReactTableFunction, RowInfo, RowRenderProps } from "react-table";
import DateRangePicker from "react-bootstrap-daterangepicker";
import { Button } from "reactstrap";
import i18n from "i18next";
import { withNamespaces } from "react-i18next";
import i18next from "i18next";
import { WithNamespaces, withNamespaces } from "react-i18next";
import debounce from "lodash.debounce";
import moment from "moment";
import { ignoreCancel, makeCancelable, padNumber } from "../../util";
import api from "../../util/api";
import moment, { Moment } from "moment";
import { CancelablePromise, ignoreCancel, makeCancelable, padNumber } from "../../util";
import api, { ApiHistoryResponse, ApiQuery } from "../../util/api";
import "react-table/react-table.css";
import "bootstrap-daterangepicker/daterangepicker.css";
class QueryLog extends Component {
updateHandler = null;
state = {
export interface QueryLogState {
history: Array<ApiQuery>;
cursor: null | string;
loading: boolean;
atEnd: boolean;
filtersChanged: boolean;
filters: Array<Filter>;
}
class QueryLog extends Component<WithNamespaces, QueryLogState> {
private updateHandler: null | CancelablePromise<ApiHistoryResponse> = null;
state: QueryLogState = {
history: [],
cursor: null,
loading: false,
@@ -32,7 +43,7 @@ class QueryLog extends Component {
filters: []
};
constructor(props) {
constructor(props: WithNamespaces) {
super(props);
// This happens in the constructor to avoid using dateRanges before it's
@@ -61,7 +72,7 @@ class QueryLog extends Component {
* @param rowInfo the row information
* @returns {*} props for the row
*/
getRowProps = (state, rowInfo) => {
getRowProps = (state: any, rowInfo: RowInfo | undefined) => {
// Check if the row is known to be blocked or allowed (not unknown)
if (rowInfo && rowInfo.row.status !== 0) {
// Blocked queries are red, allowed queries are green
@@ -82,8 +93,8 @@ class QueryLog extends Component {
* @param tableFilters the filters requested by the table
* @return the filters converted for use by the API
*/
parseFilters = tableFilters => {
let filters = {};
parseFilters = (tableFilters: Array<Filter>) => {
let filters: any = {};
for (const filter of tableFilters) {
switch (filter.id) {
@@ -171,7 +182,7 @@ class QueryLog extends Component {
* @param page The page of the query log
* @param pageSize The number of queries in the page
*/
fetchQueries = ({ page, pageSize }) => {
fetchQueries = ({ page, pageSize }: { page: number; pageSize: number }) => {
// Check if we've reached the end of the queries, or are still waiting for
// the last fetch to finish
if (this.state.atEnd || this.state.loading) {
@@ -255,7 +266,7 @@ class QueryLog extends Component {
* Convert a status code to a status message. The messages are translated, so
* you must pass in the translation function before using the message array.
*/
const status = t => [
const status = (t: i18next.TranslationFunction) => [
t("Unknown"),
t("Blocked (gravity)"),
t("Allowed (forwarded)"),
@@ -269,7 +280,7 @@ const status = t => [
* Convert a DNSSEC code to a DNSSEC message. The messages are translated, so
* you must pass in the translation function before using the message array.
*/
const dnssec = t => [
const dnssec = (t: i18next.TranslationFunction) => [
"N/A", // Unspecified, which means DNSSEC is off, so nothing should be shown
t("Secure"),
t("Insecure"),
@@ -291,7 +302,7 @@ const dnssecColor = [
* Convert a reply type code to a reply type. The unknown type is translated, so
* you must pass in the translation function before using the message array.
*/
const replyTypes = t => [
const replyTypes = (t: i18next.TranslationFunction) => [
t("Unknown"),
"NODATA",
"NXDOMAIN",
@@ -316,8 +327,18 @@ const queryTypes = ["A", "AAAA", "ANY", "SRV", "SOA", "PTR", "TXT"];
* @returns {function({filter: *, onChange: *}): *} A select component with the
* filter data
*/
const selectionFilter = (items, t, extras = []) => {
return ({ filter, onChange }) => (
const selectionFilter = (
items: string[],
t: i18next.TranslationFunction,
extras: Array<{ name: string; value: any }> = []
) => {
return ({
filter,
onChange
}: {
filter: Filter;
onChange: ReactTableFunction;
}) => (
<select
onChange={event => onChange(event.target.value)}
style={{ width: "100%" }}
@@ -341,7 +362,7 @@ const selectionFilter = (items, t, extras = []) => {
/**
* Preconfigured date ranges listed in the date range picker
*/
const dateRanges = {
const dateRanges: { [name: string]: [Moment, Moment] } = {
Today: [moment().startOf("day"), moment()],
Yesterday: [
moment()
@@ -370,13 +391,13 @@ const dateRanges = {
* The columns of the Query Log. Some pieces are translated, so you must pass in
* the translation function before using the columns.
*/
const columns = t => [
const columns = (t: i18next.TranslationFunction) => [
{
Header: t("Time"),
id: "time",
accessor: r => r.timestamp,
accessor: (r: ApiQuery) => r.timestamp,
width: 70,
Cell: row => {
Cell: (row: RowRenderProps) => {
const date = new Date(row.value * 1000);
const month = date.toLocaleDateString(i18n.language, {
month: "short"
@@ -396,7 +417,13 @@ const columns = t => [
},
filterable: true,
filterMethod: () => true, // Don't filter client side
Filter: ({ filter, onChange }) => (
Filter: ({
filter,
onChange
}: {
filter: Filter;
onChange: ReactTableFunction;
}) => (
<DateRangePicker
startDate={filter ? filter.value.start : dateRanges.Today[0]}
endDate={filter ? filter.value.end : dateRanges.Today[1]}
@@ -417,7 +444,7 @@ const columns = t => [
{
Header: t("Type"),
id: "queryType",
accessor: r => queryTypes[r.type - 1],
accessor: (r: ApiQuery) => queryTypes[r.type - 1],
width: 50,
filterable: true,
filterMethod: () => true, // Don't filter client side
@@ -426,7 +453,7 @@ const columns = t => [
{
Header: t("Domain"),
id: "domain",
accessor: r => r.domain,
accessor: (r: ApiQuery) => r.domain,
minWidth: 150,
className: "horizontal-scroll",
filterable: true,
@@ -435,7 +462,7 @@ const columns = t => [
{
Header: t("Client"),
id: "client",
accessor: r => r.client,
accessor: (r: ApiQuery) => r.client,
minWidth: 120,
className: "horizontal-scroll",
filterable: true,
@@ -444,9 +471,9 @@ const columns = t => [
{
Header: t("Status"),
id: "status",
accessor: r => r.status,
accessor: (r: ApiQuery) => r.status,
width: 140,
Cell: row => status(t)[row.value],
Cell: (row: RowRenderProps) => status(t)[row.value],
filterable: true,
filterMethod: () => true, // Don't filter client side
Filter: selectionFilter(status(t), t, [
@@ -457,9 +484,9 @@ const columns = t => [
{
Header: "DNSSEC",
id: "dnssec",
accessor: r => r.dnssec,
accessor: (r: ApiQuery) => r.dnssec,
width: 90,
Cell: row => (
Cell: (row: RowRenderProps) => (
<div style={{ color: dnssecColor[row.value] }}>
{dnssec(t)[row.value]}
</div>
@@ -471,9 +498,9 @@ const columns = t => [
{
Header: t("Reply"),
id: "reply",
accessor: r => ({ type: r.reply, time: r.response_time }),
accessor: (r: ApiQuery) => ({ type: r.reply, time: r.response_time }),
width: 90,
Cell: row => (
Cell: (row: RowRenderProps) => (
<div style={{ color: "black" }}>
{replyTypes(t)[row.value.type]}
<br />
@@ -488,7 +515,7 @@ const columns = t => [
Header: t("Action"),
width: 100,
filterable: false,
Cell: data => {
Cell: (data: { row: any }) => {
// Blocked, but can whitelist
if ([1, 4, 5].includes(data.row.status)) {
return (
+17 -1
View File
@@ -11,6 +11,22 @@
import http, { paramsToString } from "./http";
import config from "../config";
export interface ApiQuery {
timestamp: number;
type: number;
status: number;
domain: string;
client: string;
dnssec: number;
reply: number;
response_time: number;
}
export interface ApiHistoryResponse {
cursor: null | string;
history: Array<ApiQuery>
}
export interface ApiDnsSettings {
upstream_dns: Array<string>,
options: {
@@ -90,7 +106,7 @@ export default {
getTopClients() {
return http.get("stats/top_clients");
},
getHistory(params: any) {
getHistory(params: any): Promise<ApiHistoryResponse> {
return http.get("stats/history?" + paramsToString(params));
},
getWhitelist() {