Merge branch 'development' into optimize-image

This commit is contained in:
Mark Drobnak
2018-10-17 17:19:32 -04:00
committed by GitHub
76 changed files with 478 additions and 253 deletions
+3 -3
View File
@@ -14,10 +14,9 @@ version: 2
run:
name: "Store Version"
command: |
BRANCH=$([ -z "$CI_COMMIT_TAG" ] && echo "$CIRCLE_BRANCH" || echo "master")
BRANCH=$([ -z "$CIRCLE_TAG" ] && echo "$CIRCLE_BRANCH" || echo "master")
echo "$CIRCLE_TAG $BRANCH ${CIRCLE_SHA1::7}" > build/VERSION
cat build/VERSION
echo "export BRANCH=$BRANCH" >> $BASH_ENV
jobs:
build:
@@ -41,10 +40,11 @@ jobs:
name: "Upload"
command: |
[ -z "$FTL_SECRET" ] && exit 0
FOLDER=$([ -z "$CIRCLE_TAG" ] && echo "$CIRCLE_BRANCH" || echo "$CIRCLE_TAG")
tar -czvf pihole-web.tar.gz -C build .
wget https://ftl.pi-hole.net:8080/FTL-client
chmod +x ./FTL-client
./FTL-client "$BRANCH" pihole-web.tar.gz "$FTL_SECRET"
./FTL-client "$FOLDER" pihole-web.tar.gz "$FTL_SECRET"
rm ./FTL-client
- save_cache:
key: v1-build-{{ checksum "package-lock.json" }}
+1
View File
@@ -1,4 +1,5 @@
{
"Details": "Детайли / детайли",
"Status": "Статус / статус",
"DNS": "DNS / ДНС",
"Donate": "Дарят",
+1 -1
View File
@@ -5,6 +5,6 @@
"Forgot Password": "Забравена парола",
"forgot_password_description": "забравена парола описание",
"Wrong Password!": "Грешна парола!",
"You will be transferred to the {{page}}": "Ще бъдете прехвърлени на {{страницата}}",
"You will be transferred to the \"{{page}}\" page": "Ще бъдете прехвърлени на {{страницата}}",
"Verify that cookies are allowed for {{host}}": "Уверете се, че бисквитките са разрешени за {{хост}}"
}
+23
View File
@@ -0,0 +1,23 @@
{
"Details": "Detalls",
"Status": "Estat",
"DNS": "DNS",
"Donate": "Donatius",
"Domain": "Domini",
"Hits": "Consultes",
"Frequency": "freqüència",
"Client": "Client",
"Requests": "Consultes",
"Add": "Afegir",
"Time": "Tiempo",
"Type": "Tipus",
"Network": "Xarxa",
"Action": "Acció",
"Enabled": "Activat",
"Disabled": "Desactivat",
"DHCP": "DHCP",
"Core": "Core",
"FTL": "FTL",
"API": "API",
"Web": "Web"
}
+23
View File
@@ -0,0 +1,23 @@
{
"Domains On Blocklist": "Dominis bloquejats",
"Query Types": "Tipus de consultes",
"Forward Destinations": "DNS Remots",
"Top Permitted Domains": "Dominis principales",
"Top Blocked Domains": "Anunciants principals",
"Top Clients": "Clients principals",
"Total Queries ({{count}} clients)": "Consultes Totals ({{clients}} client)",
"Total Queries ({{count}} clients)_plural": "Consultes Totals ({{clients}} clients)",
"Queries Blocked": "Consultes Bloquejades",
"Percent Blocked": "Percentatge Bloquejat",
"Queries Over Last 24 Hours": "Consultes últimes 24 hores",
"Clients Over Last 24 Hours": "Clients últimes 24 hores",
"Queries Answered By": "Consultes contestades per",
"Queries from {{from}} to {{to}}": "Consultes de {{from}} a {{to}}",
"Total Queries": "Consultes totals",
"Blocked Queries": "Consultes bloquejades",
"Client activity from {{from}} to {{to}}": "Activitat de clients de {{from}} a {{to}}",
"{{percent}}% of {{total}}": "\n{{percent}}% de {{total}}\n",
"No Clients Found": "No s'ha trobat cap client",
"No Domains Found": "No s'ha trobat cap domini",
"Queries Answered By Destination": "Respostes per destí"
}
+5
View File
@@ -0,0 +1,5 @@
{
"if you found this useful": "si et sembla útil",
"Core Version": "Versió de Pi-hole",
"Web Version": "Versió del interface web"
}
+12
View File
@@ -0,0 +1,12 @@
{
"Adding {{domain}}...": "Afegint a",
"Successfully added {{domain}}": "¡Correcte! La llista se actualitzarà.",
"Failed to add {{domain}}": "¡S' ha produït un error!",
"Note: Whitelisting a subdomain of a wildcard blocked domain is not possible.": "Nota: Afegir a la llista blanca un domini bloquejat per comodí no es possible",
"Note: Only the domain and subdomains of the blocked domain will be blocked.": "Nota: Només el domini i subdominis del domini bloquejat seran bloquejats",
"Add a domain (example.com or sub.example.com)": "Afegir un domini ( example.com o sub.example.com)",
"{{domain}} is already added": "{{domain}} ja està afegit\n",
"Failed to remove {{domain}}": "Error en esborrar {{domain}}",
"There are no domains in this list": "No hi ha dominis en aquesta llista",
"Input a regular expression": "Escriu una expressió regular"
}
+11
View File
@@ -0,0 +1,11 @@
{
"Query Log": "Log de consultes",
"Whitelist": "Llista blanca",
"Blacklist": "Llista negra",
"Dashboard": "Panell de Control",
"Exact": "Exacte",
"Wildcard": "Comodí",
"Login": "Entrar",
"Logout": "Sortir",
"Regex": "Regex"
}
+10
View File
@@ -0,0 +1,10 @@
{
"Password": "Clau",
"Sign in to start your session": "Identificat per iniciar sessió",
"Log in": "Entrar",
"Forgot Password": "He oblidat la clau",
"forgot_password_description": "Després de Instal·lar Pi-hole per primer cop, es genera una clau i es mostra al usuari.\nLa clau no pot eser recuperada mes tard, però es pot canviar (o esborrar-la explícitament establint una clau buida) utilitzant la comanda:",
"Wrong Password!": "Clau incorrecta",
"You will be transferred to the \"{{page}}\" page": "Serás redirigit a {{page}}",
"Verify that cookies are allowed for {{host}}": "Verifica que s'han permés les cookies per {{host}}"
}
+7
View File
@@ -0,0 +1,7 @@
{
"Blocked": "Bloquejat",
"Allowed (forwarded)": "Permès (reenviat)",
"Allowed (cached)": "Permès (al cau)",
"Blocked (regex)": "Bloquejat (comodí)",
"Blocked (blacklist)": "Bloquejat (Llista negra)"
}
+26
View File
@@ -0,0 +1,26 @@
{
"DHCP Active": "DHCP Actiu",
"Start IP": "IP inicial",
"End IP": "IP final",
"Router IP": "IP del router",
"Lease Time": "Temps de concessió",
"IPv6 Support": "Suport IPv6",
"Upstream DNS Servers": "Servidors DNS externs",
"Interface listening behavior": "Comportament del interface",
"Forward FQDNs only": "Reenviar només FQDN",
"Use DNSSEC": "Usar DNSSEC",
"Conditional Forwarding": "Reenviament condicional",
"Local Domain Name": "Domini local",
"FTL Database": "Base de dades FTL",
"Queries": "Consultes",
"Filesize": "Tamany",
"SQLite version": "Versió SQLite",
"Interface": "Interface",
"IPv4 address": "Adreça IPv4",
"IPv6 address": "Adreça IPv6",
"Hostname": "Hostname",
"Branch": "Branca",
"Hash": "Hash",
"Tag": "Tag",
"Only forward public reverse lookups": "Reenviar només les cerques inverses públiques"
}
+4 -2
View File
@@ -1,4 +1,5 @@
{
"Details": "Details",
"Status": "Status",
"DNS": "DNS",
"Donate": "Donate",
@@ -10,7 +11,7 @@
"Add": "Add",
"Time": "Time",
"Type": "Type",
"Network": "Network\n",
"Network": "Network",
"Action": "Action",
"Enabled": "Enabled",
"Disabled": "Disabled",
@@ -18,5 +19,6 @@
"Core": "Core",
"FTL": "FTL",
"API": "API",
"Web": "Web"
"Web": "Web",
"Version": "Version"
}
+4 -1
View File
@@ -7,5 +7,8 @@
"Wildcard": "Wildcard",
"Login": "Login",
"Logout": "Logout",
"Regex": "Regex"
"Regex": "Regex",
"Settings": "Settings",
"Networking": "Networking",
"Versions": "Versions"
}
+1 -1
View File
@@ -5,6 +5,6 @@
"Forgot Password": "Forgot Password",
"forgot_password_description": "After installing Pi-hole for the first time, a password is generated and displayed to the user. The password cannot be retrieved later on, but it is possible to set a new password (or explicitly disable the password by setting an empty password) using the command:",
"Wrong Password!": "Wrong Password!",
"You will be transferred to the {{page}}": "You will be transferred to the {{page}}",
"You will be transferred to the \"{{page}}\" page": "You will be transferred to the \"{{page}}\" page",
"Verify that cookies are allowed for {{host}}": "Verify that cookies are allowed for {{host}}"
}
+1
View File
@@ -1,4 +1,5 @@
{
"Details": "Detalles",
"Status": "Estado",
"DNS": "DNS",
"Donate": "Donar",
+1 -1
View File
@@ -5,6 +5,6 @@
"Forgot Password": "Olvidé la contraseña",
"forgot_password_description": "Frase para recordar la contraseña",
"Wrong Password!": "Contraseña incorrecta",
"You will be transferred to the {{page}}": "Será trasferido a la página: {{page}}",
"You will be transferred to the \"{{page}}\" page": "Será trasferido a la página: {{page}}",
"Verify that cookies are allowed for {{host}}": "Verifique que {{host}} admite cookies."
}
+1
View File
@@ -1,4 +1,5 @@
{
"Details": "Detalhes",
"Status": "Status",
"DNS": "DNS",
"Donate": "Doar",
+1 -1
View File
@@ -5,6 +5,6 @@
"Forgot Password": "Esqueceu a palavra passe",
"forgot_password_description": "Esqueceu a palavra passe descrição",
"Wrong Password!": "Palavra passe incorreta!",
"You will be transferred to the {{page}}": "Você será transferido para a {{página}}",
"You will be transferred to the \"{{page}}\" page": "Você será transferido para a {{página}}",
"Verify that cookies are allowed for {{host}}": "Verifique se os cookies são permitidos para {{host}}"
}
-41
View File
@@ -1,41 +0,0 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* Web Interface
* Settings :: Version - Card component
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React from 'react';
import { translate } from 'react-i18next';
import PropTypes from 'prop-types';
const VersionCard = props => (
<div className="card border-0 bg-primary stat-mid-height-lock">
<div className="card-block">
<div className="card-icon">
<i className={props.icon}/>
</div>
</div>
<div className="card-img-overlay">
<h3>{props.name}</h3>
<pre>
<br/>
{props.t("Branch")}: {props.branch}<br/>
{props.t("Hash")}: {props.hash}<br/>
{props.t("Tag")}: {props.tag}<br/>
</pre>
</div>
</div>
);
VersionCard.propTypes = {
branch: PropTypes.string.isRequired,
hash: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
tag: PropTypes.string.isRequired
};
export default translate(['settings'])(VersionCard);
@@ -12,9 +12,9 @@ import React from "react";
import { translate } from "react-i18next";
import { NavLink } from "react-router-dom";
import { Nav, NavItem } from "reactstrap";
import logo from "../img/logo.svg";
import logo from "../../img/logo.svg";
import { mobileSidebarHide } from "./Header";
import { api } from "../utils";
import { api } from "../../utils";
import StatusBadge from "./StatusBadge";
const handleClick = (e) => {
@@ -70,7 +70,7 @@ const Sidebar = ({ items, ...props }) => {
paddingLeft: "15px", textTransform: "initial", fontSize: "14px", marginBottom: "initial",
lineHeight: "14px", color: "white"
}}>
Pi-hole
{props.t("Status")}
</p>
<br/>
<span style={{ textTransform: "initial", paddingLeft: "15px" }}>
@@ -84,4 +84,4 @@ const Sidebar = ({ items, ...props }) => {
);
};
export default translate("location")(Sidebar);
export default translate(["common", "location"])(Sidebar);
@@ -11,7 +11,7 @@
import React from "react";
import { shallow } from "enzyme";
import { dropDownClassList, navDropdown, navItem, navList } from "./Sidebar";
import { api } from "../utils";
import { api } from "../../utils";
it("expands active drop down items", () => {
const classList = dropDownClassList("/testRoute", { location: { pathname: "/testRoute/page" } });
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { translate } from 'react-i18next';
import { api, makeCancelable, ignoreCancel } from '../utils';
import { api, makeCancelable, ignoreCancel } from '../../utils';
class StatusBadge extends Component {
state = {
@@ -12,7 +12,7 @@ import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Line } from 'react-chartjs-2';
import { translate } from 'react-i18next';
import { padNumber, api, makeCancelable, ignoreCancel } from '../utils';
import { padNumber, api, makeCancelable, ignoreCancel } from '../../utils';
import ChartTooltip from "./ChartTooltip";
class ClientsGraph extends Component {
@@ -10,7 +10,7 @@
import React from "react";
import { translate } from "react-i18next";
import { api } from "../utils";
import { api } from "../../utils";
import GenericDoughnutChart from "./GenericDoughnutChart";
const ForwardDestinationsChart = ({ t }) => (
@@ -11,7 +11,7 @@
import React, { Component } from "react";
import { Doughnut } from "react-chartjs-2";
import PropTypes from "prop-types";
import { makeCancelable, ignoreCancel } from "../utils";
import { makeCancelable, ignoreCancel } from "../../utils";
class GenericDoughnutChart extends Component {
state = {
@@ -11,7 +11,7 @@
import React, { Component } from 'react';
import { Line } from 'react-chartjs-2';
import { translate } from 'react-i18next';
import { padNumber, api, makeCancelable, ignoreCancel } from '../utils';
import { padNumber, api, makeCancelable, ignoreCancel } from '../../utils';
class QueriesGraph extends Component {
state = {
@@ -10,7 +10,7 @@
import React from 'react';
import { translate } from 'react-i18next';
import { api } from "../utils";
import { api } from "../../utils";
import GenericDoughnutChart from "./GenericDoughnutChart";
const QueryTypesChart = ({ t }) => (
@@ -10,7 +10,7 @@
import React, { Component, Fragment } from 'react';
import { translate } from 'react-i18next';
import { api, ignoreCancel, makeCancelable } from "../utils";
import { api, ignoreCancel, makeCancelable } from "../../utils";
class SummaryStats extends Component {
state = {
@@ -10,7 +10,7 @@
import React from "react";
import { translate } from "react-i18next";
import { api } from "../utils";
import { api } from "../../utils";
import TopTable from "./TopTable";
const TopBlocked = ({ t, ...props }) => (
@@ -10,7 +10,7 @@
import React from "react";
import { translate } from "react-i18next";
import { api } from "../utils";
import { api } from "../../utils";
import TopTable from "./TopTable";
const TopClients = ({ t, ...props }) => (
@@ -10,7 +10,7 @@
import React from "react";
import { translate } from "react-i18next";
import { api } from "../utils";
import { api } from "../../utils";
import TopTable from "./TopTable";
const TopDomains = ({ t, ...props }) => (
@@ -10,7 +10,7 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { ignoreCancel, makeCancelable } from "../utils";
import { ignoreCancel, makeCancelable } from "../../utils";
class TopTable extends Component {
state = {
@@ -11,7 +11,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { api } from "../utils";
import { api } from "../../utils";
class DomainInput extends Component {
state = {
@@ -11,7 +11,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import DomainInput from './DomainInput';
import { api } from "../utils";
import { api } from "../../utils";
it('has a placeholder', () => {
const placeholder = 'placeholder';
@@ -11,7 +11,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { api, ignoreCancel, makeCancelable } from "../utils";
import { api, ignoreCancel, makeCancelable } from "../../utils";
class DomainList extends Component {
onRemove(domain) {
@@ -11,7 +11,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import DomainList from './DomainList';
import { api } from "../utils";
import { api } from "../../utils";
const domains = [
'domain1.com',
@@ -12,9 +12,9 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import DomainInput from "./DomainInput";
import Alert from "./Alert";
import Alert from "../common/Alert";
import DomainList from "./DomainList";
import { ignoreCancel, makeCancelable } from "../utils";
import { ignoreCancel, makeCancelable } from "../../utils";
class ListPage extends Component {
state = {
@@ -150,7 +150,7 @@ class ListPage extends Component {
ListPage.propTypes = {
title: PropTypes.string.isRequired,
note: PropTypes.object,
note: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
placeholder: PropTypes.string.isRequired,
add: PropTypes.func.isRequired,
refresh: PropTypes.func.isRequired,
@@ -11,7 +11,7 @@
import React, { Component } from 'react';
import ReactTable from 'react-table';
import { translate } from 'react-i18next';
import { api, ignoreCancel, makeCancelable, padNumber } from '../utils';
import { api, ignoreCancel, makeCancelable, padNumber } from '../../utils';
import 'react-table/react-table.css';
class QueryLog extends Component {
@@ -10,7 +10,7 @@
import React, { Component } from "react";
import { translate } from "react-i18next";
import { api, ignoreCancel, makeCancelable } from "../utils";
import { api, ignoreCancel, makeCancelable } from "../../utils";
class DHCPInfo extends Component {
state = {
@@ -67,25 +67,15 @@ class DHCPInfo extends Component {
const { t } = this.props;
return (
<div className="card border-0 bg-success stat-dbl-height-lock">
<div className="card-body">
<div className="card-icon">
<i className="fa fa-cogs fa-2x"/>
</div>
</div>
<div className="card-img-overlay">
<h3>{t("DHCP")}</h3>
<pre>
{t("DHCP Active")}: {this.state.active.toString()}<br/>
{t("Start IP")}: {this.state.ip_start}<br/>
{t("End IP")}: {this.state.ip_end}<br/>
{t("Router IP")}: {this.state.router_ip}<br/>
{t("Lease Time")}: {this.state.lease_time} h<br/>
{t("Domain")}: {this.state.domain}<br/>
{t("IPv6 Support")}: {this.state.ipv6_support.toString()}<br/>
</pre>
</div>
</div>
<pre>
{t("DHCP Active")}: {this.state.active.toString()}<br/>
{t("Start IP")}: {this.state.ip_start}<br/>
{t("End IP")}: {this.state.ip_end}<br/>
{t("Router IP")}: {this.state.router_ip}<br/>
{t("Lease Time")}: {this.state.lease_time} h<br/>
{t("Domain")}: {this.state.domain}<br/>
{t("IPv6 Support")}: {this.state.ipv6_support.toString()}<br/>
</pre>
);
}
}
@@ -10,7 +10,7 @@
import React, { Component } from "react";
import { translate } from "react-i18next";
import { api, ignoreCancel, makeCancelable } from "../utils";
import { api, ignoreCancel, makeCancelable } from "../../utils";
class DNSInfo extends Component {
state = {
@@ -69,41 +69,31 @@ class DNSInfo extends Component {
const { t } = this.props;
return (
<div className="card border-0 bg-success stat-dbl-height-lock">
<div className="card-body">
<div className="card-icon">
<i className="fa fa-binoculars fa-2x"/>
</div>
<div className="row">
<div className="col-lg-4 col-sm-4 col-xs-4">
<pre>
<br/>
{t("Upstream DNS Servers")}:<br/>
{this.state.upstream_dns.map(item => item + "\n")}<br/>
</pre>
</div>
<div className="card-img-overlay">
<h3>{t("DNS")}</h3>
<div className="row">
<div className="col-lg-4 col-sm-4 col-xs-4">
<pre>
<br/>
{t("Upstream DNS Servers")}:<br/>
{this.state.upstream_dns.map(item => item + "\n")}<br/>
</pre>
</div>
<div className="col-lg-4 col-sm-4 col-xs-4">
<pre>
<br/>
{t("Interface listening behaviour")}: {this.state.options.listening_type}<br/>
{t("Forward FQDNs only")}: {this.state.options.fqdn_required.toString()}<br/>
{t("Only forward public reverse lookups")}:{this.state.options.bogus_priv.toString()}<br/>
{t("Use DNSSEC")}: {this.state.options.dnssec.toString()}
</pre>
</div>
<div className="col-lg-4 col-sm-4 col-xs-4">
<pre>
<br/>
{t("Conditional Forwarding")}<br/>
{t("Enabled")}: {this.state.conditional_forwarding.enabled.toString()}<br/>
{t("Router IP")}: {this.state.conditional_forwarding.router_ip.toString()}<br/>
{t("Local Domain Name")}: {this.state.conditional_forwarding.domain.toString()}<br/>
</pre>
</div>
</div>
<div className="col-lg-4 col-sm-4 col-xs-4">
<pre>
<br/>
{t("Interface listening behavior")}: {this.state.options.listening_type}<br/>
{t("Forward FQDNs only")}: {this.state.options.fqdn_required.toString()}<br/>
{t("Only forward public reverse lookups")}:{this.state.options.bogus_priv.toString()}<br/>
{t("Use DNSSEC")}: {this.state.options.dnssec.toString()}
</pre>
</div>
<div className="col-lg-4 col-sm-4 col-xs-4">
<pre>
<br/>
{t("Conditional Forwarding")}<br/>
{t("Enabled")}: {this.state.conditional_forwarding.enabled.toString()}<br/>
{t("Router IP")}: {this.state.conditional_forwarding.router_ip.toString()}<br/>
{t("Local Domain Name")}: {this.state.conditional_forwarding.domain.toString()}<br/>
</pre>
</div>
</div>
);
@@ -10,7 +10,7 @@
import React, { Component } from "react";
import { translate } from "react-i18next";
import { api, ignoreCancel, makeCancelable } from "../utils";
import { api, ignoreCancel, makeCancelable } from "../../utils";
class FTLInfo extends Component {
state = {
@@ -56,23 +56,11 @@ class FTLInfo extends Component {
const { t } = this.props;
return (
<div className="card border-0 bg-success stat-dbl-height-lock">
<div className="card-block">
<div className="card-icon">
<i className="fa fa-database fa-2x"/>
</div>
</div>
<div className="card-img-overlay">
<h3>
{t("FTL Database")}
</h3>
<pre>
{t("Queries")}: {this.state.queries}<br/>
{t("Filesize")}: {this.state.filesize} B<br/>
{t("SQLite version")}: {this.state.sqlite_version}<br/>
</pre>
</div>
</div>
<pre>
{t("Queries")}: {this.state.queries}<br/>
{t("Filesize")}: {this.state.filesize} B<br/>
{t("SQLite version")}: {this.state.sqlite_version}<br/>
</pre>
);
}
}
@@ -8,9 +8,10 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React, { Component } from "react";
import React, { Component, Fragment } from "react";
import { translate } from "react-i18next";
import { api, ignoreCancel, makeCancelable } from "../utils";
import { api, ignoreCancel, makeCancelable } from "../../utils";
import { Col, Form, FormGroup, Input, Label } from "reactstrap";
class NetworkInfo extends Component {
state = {
@@ -58,22 +59,34 @@ class NetworkInfo extends Component {
const { t } = this.props;
return (
<div className="card border-0 bg-success stat-dbl-height-lock">
<div className="card-block">
<div className="card-icon">
<i className="fa fa-sitemap fa-2x"/>
</div>
</div>
<div className="card-img-overlay">
<h3>{t("Network")}</h3>
<pre>
{t("Interface")}: {this.state.interface}<br/>
{t("IPv4 address")}: {this.state.ipv4_address}<br/>
{t("IPv6 address")}: {this.state.ipv6_address}<br/>
{t("Hostname")}: {this.state.hostname}
</pre>
</div>
</div>
<Fragment>
<Form>
<FormGroup row>
<Label className="bold" for="interface" sm={4}>{t("Interface")}</Label>
<Col sm={8}>
<Input plaintext id="interface">{this.state.interface}</Input>
</Col>
</FormGroup>
<FormGroup row>
<Label className="bold" for="ipv4_address" sm={4}>{t("IPv4 address")}</Label>
<Col sm={8}>
<Input plaintext id="ipv4_address">{this.state.ipv4_address}</Input>
</Col>
</FormGroup>
<FormGroup row>
<Label className="bold" for="ipv6_address" sm={4}>{t("IPv6 address")}</Label>
<Col sm={8}>
<Input plaintext id="ipv6_address">{this.state.ipv6_address}</Input>
</Col>
</FormGroup>
<FormGroup row>
<Label className="bold" for="hostname" sm={4}>{t("Hostname")}</Label>
<Col sm={8}>
<Input plaintext id="hostname">{this.state.hostname}</Input>
</Col>
</FormGroup>
</Form>
</Fragment>
);
}
}
+73
View File
@@ -0,0 +1,73 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* Web Interface
* Settings :: Version - Card component
*
* 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 { translate } from "react-i18next";
import PropTypes from "prop-types";
import { Collapse } from "reactstrap";
class VersionCard extends Component {
state = {
collapsed: true
};
render() {
const { t } = this.props;
return (
<div className="card border-0 bg-primary">
<div className="card-block">
<div className="card-icon">
<i className={this.props.icon}/>
</div>
</div>
<div className="card-body">
<h3>{this.props.name}</h3>
<p>
{t("Version")}: {this.props.branch === "master" ? this.props.tag : "vDev"}
</p>
<button className="btn btn-secondary"
type="button"
onClick={() => this.setState({ collapsed: !this.state.collapsed })}>
{t("Details")}
</button>
<Collapse isOpen={!this.state.collapsed}>
<table className="table table-borderless table-sm table-condensed"
style={{ marginTop: "1rem", marginBottom: "0" }}>
<tbody>
<tr>
<td>{t("Branch")}</td>
<td>{this.props.branch}</td>
</tr>
<tr>
<td>{t("Tag")}</td>
<td>{this.props.tag}</td>
</tr>
<tr>
<td>{t("Hash")}</td>
<td>{this.props.hash}</td>
</tr>
</tbody>
</table>
</Collapse>
</div>
</div>
);
}
}
VersionCard.propTypes = {
branch: PropTypes.string.isRequired,
hash: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
tag: PropTypes.string.isRequired
};
export default translate(["common", "settings"])(VersionCard);
@@ -10,8 +10,8 @@
import React, { Component } from "react";
import { translate } from "react-i18next";
import { api, ignoreCancel, makeCancelable } from "../utils";
import VersionCard from "../components/VersionCard";
import { api, ignoreCancel, makeCancelable } from "../../utils";
import VersionCard from "./VersionCard";
class VersionInfo extends Component {
state = {
@@ -87,19 +87,19 @@ class VersionInfo extends Component {
return (
<div className="row">
<div className="col-lg-3 col-xs-12">
<div className="col-xl-3 col-md-6 col-xs-12">
<VersionCard name={t("Core")} icon="fa fa-dot-circle-o fa-2x"
branch={this.state.core.branch} hash={this.state.core.hash} tag={this.state.core.tag} t={t}/>
</div>
<div className="col-lg-3 col-xs-12">
<div className="col-xl-3 col-md-6 col-xs-12">
<VersionCard name={t("FTL")} icon="fa fa-industry fa-2x"
branch={this.state.ftl.branch} hash={this.state.ftl.hash} tag={this.state.ftl.tag} t={t}/>
</div>
<div className="col-lg-3 col-xs-12">
<div className="col-xl-3 col-md-6 col-xs-12">
<VersionCard name={t("API")} icon="fa fa-bullseye fa-2x"
branch={this.state.api.branch} hash={this.state.api.hash} tag={this.state.api.tag} t={t}/>
</div>
<div className="col-lg-3 col-xs-12">
<div className="col-xl-3 col-md-6 col-xs-12">
<VersionCard name={t("Web")} icon="fa fa-list-alt fa-2x"
branch={this.state.web.branch} hash={this.state.web.hash} tag={this.state.web.tag} t={t}/>
</div>
+20 -21
View File
@@ -10,18 +10,9 @@
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom'
import Header, { mobileSidebarHide } from '../components/Header';
import Sidebar from '../components/Sidebar';
import Footer from '../components/Footer';
import Dashboard from '../views/Dashboard';
import QueryLog from '../components/QueryLog';
import Whitelist from "../views/Whitelist";
import Blacklist from "../views/Blacklist";
import Regexlist from "../views/Regexlist";
import Versions from "../views/Versions";
import Networking from "../views/Networking";
import Login from "../views/Login";
import Logout from "../views/Logout";
import Header, { mobileSidebarHide } from '../components/common/Header';
import Sidebar from '../components/common/Sidebar';
import Footer from '../components/common/Footer';
import { api } from "../utils";
import { nav } from "../routes";
@@ -33,16 +24,8 @@ export default props => (
<main className="main" onClick={mobileSidebarHide}>
<div className="container-fluid" style={{"marginTop": "1.5rem"}}>
<Switch>
<Route path="/dashboard" name="Dashboard" component={Dashboard}/>
<Redirect exact from="/" to="/dashboard"/>
<AuthRoute path="/query-log" name="Query Log" component={QueryLog}/>
<Route path="/whitelist" name="Whitelist" component={Whitelist}/>
<Route path="/blacklist/exact" name="Blacklist" component={Blacklist}/>
<Route path="/blacklist/regex" name="Regexlist" component={Regexlist}/>
<Route path="/settings/versions" name="Versions" component={Versions}/>
<Route path="/settings/networking" name="Networking" component={Networking}/>
<Route path="/login" name="Login" component={Login}/>
<AuthRoute path="/logout" name="Logout" component={Logout}/>
{nav.map(createRoute)}
</Switch>
</div>
</main>
@@ -51,6 +34,22 @@ export default props => (
</div>
);
/**
* Create a route from the route data.
* If the route has children, an array of routes will be returned.
*
* @param routeData the route data (see routes.js)
*/
const createRoute = routeData => {
if(routeData.children) {
return routeData.children.map(createRoute);
}
return routeData.auth
? <AuthRoute key={routeData.url} path={routeData.url} component={routeData.component}/>
: <Route key={routeData.url} path={routeData.url} component={routeData.component}/>;
};
/**
* Create a route which requires authentication.
* If the user is unauthenticated, they will be redirected to the login page.
+1 -1
View File
@@ -13,7 +13,7 @@ export function setupI18n() {
fallbackLng: "en",
ns: ['common'],
defaultNS: "common",
fallbackNS: ['dashboard', 'footer', 'lists', 'location', 'login', 'query-log'],
fallbackNS: ["dashboard", "footer", "lists", "location", "login", "query-log", "settings"],
nsSeparator: false,
keySeparator: false,
debug: config.developmentMode,
+3 -3
View File
@@ -10,7 +10,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route, Switch } from 'react-router-dom'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import "ionicons/dist/css/ionicons.min.css";
import "font-awesome/css/font-awesome.min.css";
import "simple-line-icons/css/simple-line-icons.css";
@@ -28,11 +28,11 @@ setupI18n();
ReactDOM.render(
(
<HashRouter>
<BrowserRouter>
<Switch>
<Route path="/" name="Home" component={Full}/>
</Switch>
</HashRouter>
</BrowserRouter>
),
document.getElementById('root')
);
+19
View File
@@ -8,6 +8,16 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import Dashboard from "./views/Dashboard";
import QueryLog from "./components/log/QueryLog";
import Whitelist from "./views/Whitelist";
import Blacklist from "./views/Blacklist";
import Regexlist from "./views/Regexlist";
import Versions from "./views/Versions";
import Networking from "./views/Networking";
import Login from "./views/Login";
import Logout from "./views/Logout";
export const routes = t => ({
'/dashboard': t('Dashboard'),
'/query-log': t('Query Log'),
@@ -24,18 +34,21 @@ export const nav = [
{
name: 'Dashboard',
url: '/dashboard',
component: Dashboard,
icon: 'fa fa-dashboard',
auth: false
},
{
name: 'Query Log',
url: '/query-log',
component: QueryLog,
icon: 'fa fa-database',
auth: true
},
{
name: 'Whitelist',
url: '/whitelist',
component: Whitelist,
icon: 'fa fa-check-circle-o',
auth: false
},
@@ -48,12 +61,14 @@ export const nav = [
{
name: 'Exact',
url: '/blacklist/exact',
component: Blacklist,
icon: 'fa fa-ban',
auth: false
},
{
name: 'Regex',
url: '/blacklist/regex',
component: Regexlist,
icon: 'fa fa-ban',
auth: false
}
@@ -68,12 +83,14 @@ export const nav = [
{
name: 'Versions',
url: '/settings/versions',
component: Versions,
icon: 'fa fa-download',
auth: true
},
{
name: 'Networking',
url: '/settings/networking',
component: Networking,
icon: 'fa fa-sitemap',
auth: true,
}
@@ -82,6 +99,7 @@ export const nav = [
{
name: 'Login',
url: '/login',
component: Login,
icon: 'fa fa-user',
auth: false,
authStrict: true
@@ -89,6 +107,7 @@ export const nav = [
{
name: 'Logout',
url: '/logout',
component: Logout,
icon: 'fa fa-user-times',
auth: true,
authStrict: true
+4
View File
@@ -113,6 +113,10 @@
text-decoration: line-through !important;
}
.bold {
font-weight: bold;
}
.background-image {
background: url("../img/backgroundLight.png") repeat fixed;
}
+1 -1
View File
@@ -11,7 +11,7 @@
import React from 'react';
import { translate } from 'react-i18next';
import { api } from "../utils";
import ListPage from "../components/ListPage";
import ListPage from "../components/list/ListPage";
const Blacklist = props => {
const { t } = props;
+8 -8
View File
@@ -9,14 +9,14 @@
* Please see LICENSE file for your rights under this license. */
import React, { Fragment } from 'react';
import SummaryStats from '../components/SummaryStats';
import QueriesGraph from '../components/QueriesGraph';
import ClientsGraph from '../components/ClientsGraph';
import QueryTypesChart from "../components/QueryTypesChart";
import ForwardDestinationsChart from "../components/ForwardDestinationsChart";
import TopDomains from '../components/TopDomains';
import TopBlocked from '../components/TopBlocked';
import TopClients from '../components/TopClients';
import SummaryStats from '../components/dashboard/SummaryStats';
import QueriesGraph from '../components/dashboard/QueriesGraph';
import ClientsGraph from '../components/dashboard/ClientsGraph';
import QueryTypesChart from "../components/dashboard/QueryTypesChart";
import ForwardDestinationsChart from "../components/dashboard/ForwardDestinationsChart";
import TopDomains from '../components/dashboard/TopDomains';
import TopBlocked from '../components/dashboard/TopBlocked';
import TopClients from '../components/dashboard/TopClients';
import { api } from "../utils";
export default () => (
+28 -19
View File
@@ -8,18 +8,19 @@
* 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 { Redirect } from 'react-router-dom';
import sha from 'sha.js';
import React, { Component, Fragment } from "react";
import { Redirect } from "react-router-dom";
import sha from "sha.js";
import { api } from "../utils";
import logo from '../img/logo.svg';
import logo from "../img/logo.svg";
import { routes } from "../routes";
import ForgotPassword from "../components/ForgotPassword";
import { translate } from 'react-i18next';
import ForgotPassword from "../components/login/ForgotPassword";
import { translate } from "react-i18next";
import config from "../config";
class Login extends Component {
state = {
password: '',
password: "",
error: false,
cookiesEnabled: false
};
@@ -41,19 +42,22 @@ class Login extends Component {
if(e.keyCode === 13)
this.authenticate();
else
this.setState({ password: e.target.value })
this.setState({ password: e.target.value });
};
/**
* Try to authenticate the user
*/
authenticate = () => {
authenticate = e => {
// Prevent the page from reloading when the user gets redirected
e.preventDefault();
// Hash the password twice before sending to the API
let hashedPassword = sha("sha256").update(this.state.password).digest("hex");
hashedPassword = sha("sha256").update(hashedPassword).digest("hex");
// Clear the state
this.setState({ password: '', error: false });
this.setState({ password: "", error: false });
// Send the password to the API to authenticate the user
api.authenticate(hashedPassword)
@@ -67,9 +71,14 @@ class Login extends Component {
api.loggedIn = true;
if(config.fakeAPI) {
// When using the fake API, set the cookie ourselves
document.cookie = "user_id=;";
}
// Redirect to the page the user was originally going to, or if that doesn't exist, go to home
const redirect = this.props.location.state.from || '/';
this.props.history.push(redirect);
const locationState = this.props.location.state || "/";
this.props.history.push(locationState.from.pathname);
})
// If there was an error, tell the user they used the wrong password
.catch(() => this.setState({ error: true }));
@@ -83,16 +92,16 @@ class Login extends Component {
const { t } = this.props;
return (
<div className="mainbox col-md-8 offset-md-2 col-lg-6 offset-lg-3" style={{'float': 'none'}}>
<div className="mainbox col-md-8 offset-md-2 col-lg-6 offset-lg-3" style={{ "float": "none" }}>
<div className="card">
<div className="card-header">
<div style={{'textAlign': 'center'}}>
<div style={{ "textAlign": "center" }}>
<img src={logo} alt="Logo" width="30%"/>
</div>
<br/>
<div className="card-title text-center" style={{'marginBottom': 0}}>
<span className="logo-lg" style={{'fontSize': '25px'}}>
<div className="card-title text-center" style={{ "marginBottom": 0 }}>
<span className="logo-lg" style={{ "fontSize": "25px" }}>
Pi-<b>hole</b>
</span>
</div>
@@ -106,7 +115,7 @@ class Login extends Component {
<Fragment>
<br/>
{t(
"You will be transferred to the {{page}}",
"You will be transferred to the \"{{page}}\" page",
{ page: routes(t)[this.props.location.state.from.pathname] }
)}
</Fragment>
@@ -115,7 +124,7 @@ class Login extends Component {
{
// If cookies are not enabled (or detected), show a warning
!this.state.cookiesEnabled ?
<div className="text-center" style={{'color': '#F00'}}>
<div className="text-center" style={{ "color": "#F00" }}>
{t("Verify that cookies are allowed for {{host}}", { host: window.location.host })}
</div>
: null
@@ -135,7 +144,7 @@ class Login extends Component {
<div className="card-body">
<form id="loginform">
<div className={'input-group' + (this.state.error ? ' has-error' : '')}>
<div className={"input-group" + (this.state.error ? " has-error" : "")}>
<input type="password" className="form-control"
value={this.state.password} onChange={this.handlePasswordChange}
placeholder={t("Password")} autoFocus/>
+9 -1
View File
@@ -11,11 +11,19 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import { api } from "../utils";
import config from "../config";
export default class Logout extends Component {
componentWillMount() {
api.loggedIn = false;
api.logout();
if(config.fakeAPI) {
// When using the fake API, don't try deleting the resource
// (it results in an error). Instead, delete the cookie.
document.cookie = 'user_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
} else {
api.logout();
}
}
render() {
+72 -24
View File
@@ -8,30 +8,78 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
import React from 'react';
import React, { Component } from "react";
import { translate } from "react-i18next";
import { Nav, NavItem, NavLink, TabContent, TabPane } from "reactstrap";
import DHCPInfo from "../components/settings/DHCPInfo";
import DNSInfo from "../components/settings/DNSInfo";
import NetworkInfo from "../components/settings/NetworkInfo";
import FTLInfo from "../components/settings/FTLInfo";
import DHCPInfo from '../components/DHCPInfo';
import DNSInfo from '../components/DNSInfo';
import NetworkInfo from '../components/NetworkInfo';
import FTLInfo from '../components/FTLInfo';
class Networking extends Component {
state = {
activeTab: "network"
};
export default () => (
<div className="animated fadeIn">
<div className="container-flex">
<div className="row">
<div className="col-lg-6 col-xs-6">
<NetworkInfo/>
</div>
<div className="col-lg-6 col-xs-6">
<DHCPInfo/>
</div>
<div className="col-lg-12 col-xs-12">
<DNSInfo/>
</div>
<div className="col-lg-3 col-xs-3">
<FTLInfo/>
</div>
/**
* Set the active tab to the input
*
* @param tab the tab ID to switch to
*/
setTab = tab => {
if(this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
};
/**
* Create a navigation tab
*
* @param id the tab's ID
* @param name the tab's display name
* @returns {NavItem} the tab component
*/
tab = (id, name) => (
<NavItem>
<NavLink active={this.state.activeTab === id} onClick={() => this.setTab(id)}>
{name}
</NavLink>
</NavItem>
);
/**
* Create tab content
*
* @param id the tab's ID
* @param component the component to render in the tab
* @returns {TabPane} the tab content component
*/
tabContent = (id, component) => (
<TabPane tabId={id}>{component}</TabPane>
);
render() {
const { t } = this.props;
return (
<div className="animated fadeIn">
<Nav tabs>
{this.tab("network", t("Network"))}
{this.tab("dhcp", t("DHCP"))}
{this.tab("dns", t("DNS"))}
{this.tab("ftl", t("FTL"))}
</Nav>
<TabContent activeTab={this.state.activeTab}>
{this.tabContent("network", <NetworkInfo/>)}
{this.tabContent("dhcp", <DHCPInfo/>)}
{this.tabContent("dns", <DNSInfo/>)}
{this.tabContent("ftl", <FTLInfo/>)}
</TabContent>
</div>
</div>
</div>
);
);
}
}
export default translate(["common", "settings"])(Networking);
+1 -1
View File
@@ -10,7 +10,7 @@
import React from 'react';
import { translate } from 'react-i18next';
import ListPage from "../components/ListPage";
import ListPage from "../components/list/ListPage";
import { api } from "../utils";
const Regexlist = props => {
+1 -1
View File
@@ -9,7 +9,7 @@
* Please see LICENSE file for your rights under this license. */
import React from 'react';
import VersionInfo from '../components/VersionInfo';
import VersionInfo from '../components/settings/VersionInfo';
export default () => (
<div className="animated fadeIn">
+1 -1
View File
@@ -10,7 +10,7 @@
import React from 'react';
import { translate } from 'react-i18next';
import ListPage from "../components/ListPage";
import ListPage from "../components/list/ListPage";
import { api } from "../utils";
const Whitelist = props => {