3rd party Pi-hole Admin portal written in NextJS and styled with Material-UI
Based on Pi-Hole Admin portal, but written in ReactJS using NextJS
npm run dev
npm run build;
npm run start;
- pihole-dashboard
- Getting Started
- Screenshots
- Table of contents
- NPM scripts
- Environment variables
- pages
- api
- Protected routes
- Research resources
-
utils
repoStatus
: return stringclean
ordirty
- if repo has uncommited changes it will return
dirty
- if repo has NO uncomitted changes, it will return
clean
- if repo has uncommited changes it will return
repoLatestTag
: get the latest git tag or useversion
frompackage.json
generateVersion
: generate tag with following structure- <
repoLatestTag
>-dev-<repoStatus
>- Ex: 0.1.0-dev-clean
- Ex: 0.1.0-dev-dirty
- <
generateBuildId
: get the latest commit sha ID ordevelopment
- Ex: 1683a9b
- Ex: development
generateEnv
: runsgenerateEnvs.sh
docker:imagename
: the name of docker image. This will run when making docker imagedocker:tagname
: the tagname of the docker image. This will run when making docker image
-
nextjs
dev
: run scriptgenerateEnv
and start development NextJS serverbuild
: build NextJS project- NOTE: when building the project, make sure the ENV variables are created properly on file
.env.local
- Go to Environment variables
- NOTE: when building the project, make sure the ENV variables are created properly on file
start
: start NextJS project using the files generated from scriptbuild
-
lint
lint:eslint
: Run eslint to lint codelint:tsc
: Run Typescript to lint codelint
: Run scriptlint:eslint
andlint:tsc
-
docker
docker:build
: builds docker image- image name: runs npm script
docker:imagename
- image tagname: runs npm script
docker:tagname
- image name: runs npm script
docker:run
: runs the docker image- image name: runs npm script
docker:imagename
- image tagname: runs npm script
docker:tagname
- image name: runs npm script
docker:stop
: stops docker container- container name: runs npm script
docker:imagename
- container name: runs npm script
docker:logs
- container name: runs npm script
docker:imagename
- container name: runs npm script
This project uses environment variables in order for the NextJS project to work.
environment variable | default | usage | used by |
---|---|---|---|
SECRET_COOKIE_PASSWORD | n/a | encrypting session cookie (32 character long. check iron-session) | Iron session cookies |
SECURE_COOKIE_TTL | n/a | session cookie TTL (time to live) in seconds. check iron-session | Iron session cookies |
NEXT_PUBLIC_POLLING_AUTH_SESSION | 1000 |
Fetch polling time (in milliseconds) for api /api/auth/session |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_SUMMARY | 2000 |
Fetch polling time (in milliseconds) for api /api/queries/summary |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_FORWARDED_DESTINATIONS | 60000 |
Fetch polling time (in milliseconds) for api /api/queries/forwardedDestinations |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_QUERY_TYPES | 60000 |
Fetch polling time (in milliseconds) for api /api/queries/overtime |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_TOP_PERMITTED_QUERIES | 10000 |
Fetch polling time (in milliseconds) for api /api/queries/domains/topAllowed |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_TOP_BLOCKED_QUERIES | 10000 |
Fetch polling time (in milliseconds) for api /api/queries/domains/topBlocked |
Redux Toolkit Query |
NEXT_PUBLIC_NUM_ENTRIES_TOP_PERMITTED_QUERIES | 10 |
Number of entries to fetch from api /api/queries/domains/topAllowed |
Redux Toolkit Query |
NEXT_PUBLIC_NUM_ENTRIES_TOP_BLOCKED_QUERIES | 10 |
Number of entries to fetch from api /api/queries/domains/topBlocked |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_TOP_CLIENTS_ALLOWED_QUERIES | 10000 |
Fetch polling time (in milliseconds) for api /api/queries/clients/topAllowed |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_TOP_CLIENTS_BLOCKED_QUERIES | 10000 |
Fetch polling time (in milliseconds) for api /api/queries/clients/topBlocked |
Redux Toolkit Query |
NEXT_PUBLIC_NUM_ENTRIES_TOP_CLIENTS_ALLOWED_QUERIES | 10 |
Number of entries to fetch from api /api/queries/clients/topAllowed |
Redux Toolkit Query |
NEXT_PUBLIC_NUM_ENTRIES_TOP_CLIENTS_BLOCKED_QUERIES | 10 |
Number of entries to fetch from api /api/queries/clients/topBlocked |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_QUERIES_OVERTIME | 600000 |
Fetch polling time (in milliseconds) for api /api/queries/overtime |
Redux Toolkit Query |
NEXT_PUBLIC_POLLING_CLIENTS_OVERTIME | 600000 |
Fetch polling time (in milliseconds) for api /api/queries/clients/overtime |
Redux Toolkit Query |
All pages in the project
Used for logging into the dashboard
It requires a valid IPv4
address, port number and password (the same as Pi-hole admin portal)
The IP address and port number is required because these two values will be used to connect with Pi-hole API
If the authentication is successful, then the user will be redirected to the Home
page
The login session is valid for the time set in the ENV variable SECURE_COOKIE_TTL
- check ENV variables
displays basic information about Pi-hole statistics.
- Summary
- number of queries made to Pi-hole
- number of blocked queries
- percentage of blocked queries and total queries
- total number entries in ad block list
- charts
- bar chart of queries in the last 24 hours (blocked vs allowed)
- bar chart of queries made by clients in the last 24 hours
- doughnut chart of query types (ex: AAAA, HTTPS, DS)
- doughnut chart of upstream servers
- who handled the query (blocked by ad list, allowed, sent to router, sent to DNS resolver (Google DNS))
- tables
- top allowed domains
- top blocked domains
- top queries made by clients in Pi-hole
- top blocked queries made by clients in Pi-hole
data are fetched using Redux Toolkit Query. The data fetched in a polling form (refetch at an interval). These polling times are set using ENVs
- check ENV variables
./src/pages/api/ ├── auth │ ├── login.ts │ ├── logout.ts │ ├── session.ts │ └── unauthorized.ts └── queries ├── clients │ ├── overtime.ts │ ├── topAllowed.ts │ └── topBlocked.ts ├── domains │ ├── topAllowed.ts │ └── topBlocked.ts ├── forwardedDestinations.ts ├── overtime.ts ├── queryTypes.ts └── summary.ts
All authentication API endpoints
logging in
HTTP request type: POST
- check if ip address is valid
- check if port number is valid
- check if Pi-hole admin portal credentials are valid
- if valid,
- create session cookie using iron-session.
- Return response
success
- if NOT VALID
- return response
invalid credentials
- return response
- if valid,
logging out
HTTP request type: DELETE
- invalidates session cookie using iron-session cookie destroy
- returns response
success
get current auth session
HTTP request type: GET
- checks if user is logged in using iron-session
- if logged in
- return
IP address
andport number
used to log in
- return
- if NOT logged in
- return
IP address
andport number
as empty strings
- return
- if logged in
return message unauthorized
HTTP request type: GET
- return response
Authorization required
Used when accessing an API endpoint that requires authorization and the user is NOT logged in.
- check protected routes
All API endpoints regarding Pi-hole queries
NOTE: all endpoints under /api/queries
are protected routes.
check Protected routes
Pi-hole Queries stats regarding clients
Data to graph queries overtime with the clients
HTTP request type: GET
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?overTimeDataClients&getClientNames
Example:
Raw data returned from Pi-hole API endpoint
HTTP request type: GET
API route: /api/queries/clients/overtime
Example:
{
"clients": [
{
"name": "iPad-Air.local",
"ip": "192.168.1.85"
},
{
"name": "MacBook-Pro.local",
"ip": "192.168.1.67"
},
{
"name": "pi.hole",
"ip": "::"
}
],
"over_time": {
"1667669700": [0, 0, 13, 15, 0, 0, 0, 0, 0, 1, 0, 0, 3, 0, 0, 22, 0, 1, 1, 0, 14, 112],
"1667670300": [5, 0, 2, 0, 3, 0, 0, 1, 0, 3, 0, 0, 2, 0, 0, 16, 2, 0, 1, 0, 13, 5],
"1667670900": [38, 0, 3, 20, 3, 0, 0, 2, 0, 0, 0, 0, 5, 0, 0, 5, 0, 2, 1, 0, 13, 1]
}
}
Data formatted to be used by ChartJS bar chart
HTTP request type: GET
API route: /api/queries/clients/overtime?formatted=true
Example:
{
"labels": [1667669700000, 1667670300000, 1667670900000],
"datasets": [
{
"data": [
0, 5, 38, 6, 4, 11, 9, 38, 263, 196, 8, 5, 4, 4, 11, 237, 209, 196, 209, 201, 199, 210
],
"backgroundColor": "#f56954",
"label": "iPad-Air.local"
},
{
"data": [236, 90, 61, 43, 53, 29, 64, 50, 32, 34, 56, 49, 38, 0, 60, 0, 54, 0, 30, 46, 0, 0],
"backgroundColor": "#3c8dbc",
"label": "MacBook-Pro.local"
},
{
"data": [13, 2, 3, 39, 9, 49, 24, 7, 11, 3, 0, 4, 6, 5, 10, 10, 1, 2, 4, 0, 7, 3],
"backgroundColor": "#00a65a",
"label": "pi.hole"
}
]
}
Data needed for generating the Top Clients list
HTTP request type: GET
API route: /api/queries/clients/topAllowed?numEntries=10
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?topClients=10
Example:
{
"iPad-Air.local|192.168.1.85": 5874,
"iPhone-31.local|192.168.1.69": 3397,
"Android.local|192.168.1.94": 2833,
"iPhone.local|192.168.1.71": 2752,
"iPad.local|192.168.1.68": 2315,
"apollo-prime.local|192.168.1.82": 1878,
"MacBook-Pro.local|192.168.1.93": 1681,
"apollo-prime.local|192.168.1.78": 1456,
"MacBook-Pro.local|192.168.1.67": 1437,
"pi.hole|::": 1334
}
Data needed for generating the Top blocked Clients list
HTTP request type: GET
API route: /api/queries/clients/topBlocked?numEntries=10
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?topClientsBlocked=10
Example:
{
"apollo-prime.local|192.168.1.82": 1353,
"iPad-Air.local|192.168.1.85": 948,
"Android.local|192.168.1.94": 617,
"iPad.local|192.168.1.68": 598,
"MacBook-Pro.local|192.168.1.67": 563,
"iPhone-31.local|192.168.1.69": 408,
"iPhone.local|192.168.1.71": 320,
"apollo-prime.local|192.168.1.78": 202,
"Android.local|192.168.1.79": 74,
"iPad-pro.local|192.168.1.87": 48
}
Pi-hole Queries stats regarding domains
Data needed for generating the Top allowed Domain list
HTTP request type: GET
API route: /api/queries/domains/topBlocked?numEntries=10
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?topItems=10
Example:
{
"es.ring.com": 1157,
"gateway.fe.apple-dns.net": 593,
"www.google.com": 497,
"fw-snaps.ring.com": 476,
"eas.outlook.com": 341,
"nrdp.prod.ftl.netflix.com": 304,
"eat-efz.ms-acdc.office.com": 288,
"2.android.pool.ntp.org": 274,
"e673.dsce9.akamaiedge.net": 224,
"play.googleapis.com": 212
}
Data needed for generating the Top blocked Domain list
HTTP request type: GET
API route: /api/queries/domains/topBlocked?numEntries=10
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?topItems=10
Example:
{
"xp.itunes-apple.com.akadns.net": 5260,
"api2.branch.io": 949,
"a1887.dscq.akamai.net": 752,
"prod.disqus.map.fastlylb.net": 746,
"cc-api-data.adobe.io": 370,
"e.reddit.com": 351,
"ssl.google-analytics.com": 322,
"app-measurement.com": 275,
"dit.whatsapp.net": 230,
"xp.apple.com": 216
}
Shows number of queries that have been forwarded and the target
HTTP request type: GET
API route: /api/queries/forwardedDestinations
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?getForwardDestinations
Example:
{
"forward_destinations": {
"blocked|blocked": 18.84,
"cached|cached": 5.12,
"other|other": 0.34,
"localhost#5335|127.0.0.1#5335": 73.95,
"192.168.1.254#53|192.168.1.254#53": 1.75
}
}
Data needed for generating the domains/ads over time graph on the Pi-hole web dashboard
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?overTimeData10mins
Raw data returned from Pi-hole API endpoint
HTTP request type: GET
API route: /api/queries/overtime
Example:
{
"domains_over_time": {
"1667438100": 328,
"1667438700": 239,
"1667439300": 236,
"1667439900": 384
},
"ads_over_time": {
"1667438100": 57,
"1667438700": 50,
"1667439300": 41,
"1667439900": 90
}
}
Data formatted to be used by ChartJS bar chart
HTTP request type: GET
API route: api/queries/overtime?formatted=true
Example:
{
"labels": [1667438100000, 1667438700000, 1667439300000, 1667439900000],
"datasets": [
{
"data": [57, 50, 41, 90],
"backgroundColor": "#f56954",
"label": "Blocked DNS Queries"
},
{
"data": [271, 189, 195, 294],
"backgroundColor": "#00a65a",
"label": "Permitted DNS Queries"
}
]
}
Shows number of queries that the Pi-hole's DNS server has processed
HTTP request type: GET
API route: /api/queries/queryTypes
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?getQueryTypes
Example:
{
"querytypes": {
"A (IPv4)": 66.99,
"AAAA (IPv6)": 9.92,
"ANY": 0,
"SRV": 0.01,
"SOA": 0.06,
"PTR": 1.63,
"TXT": 0,
"NAPTR": 0,
"MX": 0,
"DS": 1.97,
"RRSIG": 0,
"DNSKEY": 1.01,
"NS": 0.03,
"OTHER": 0.08,
"SVCB": 0,
"HTTPS": 18.3
}
}
Summary Pi-hole statistics Ex:
- number of queries handled
- number of queries blocked
- number of queries blocked in percentage
- number domains in adlist
Gives statistics in raw format (no number formatting applied)
HTTP request type: GET
API route /api/queries/summary?raw=true
Pi-hole API route: http://<ip address>:<port>/admin/api.php?summaryRaw
Example:
{
"domains_being_blocked": 1249022,
"dns_queries_today": 35987,
"ads_blocked_today": 5097,
"ads_percentage_today": 14.163448,
"unique_domains": 9354,
"queries_forwarded": 25610,
"queries_cached": 5205,
"clients_ever_seen": 33,
"unique_clients": 27,
"dns_queries_all_types": 35987,
"reply_UNKNOWN": 362,
"reply_NODATA": 4693,
"reply_NXDOMAIN": 798,
"reply_CNAME": 19689,
"reply_IP": 9878,
"reply_DOMAIN": 2,
"reply_RRNAME": 0,
"reply_SERVFAIL": 0,
"reply_REFUSED": 29,
"reply_NOTIMP": 0,
"reply_OTHER": 0,
"reply_DNSSEC": 451,
"reply_NONE": 0,
"reply_BLOB": 85,
"dns_queries_all_replies": 35987,
"privacy_level": 0,
"status": "enabled",
"gravity_last_updated": {
"file_exists": true,
"absolute": 1667125216,
"relative": {
"days": 4,
"hours": 14,
"minutes": 6
}
}
}
Gives statistics in formatted style
HTTP request type: GET
API route: /api/queries/summary
Pi-hole API endpoint: http://<ip address>:<port>/admin/api.php?summary
Example:
{
"domains_being_blocked": "1,249,022",
"dns_queries_today": "36,163",
"ads_blocked_today": "5,175",
"ads_percentage_today": "14.3",
"unique_domains": "9,354",
"queries_forwarded": "25,733",
"queries_cached": "5,181",
"clients_ever_seen": "33",
"unique_clients": "27",
"dns_queries_all_types": "36,163",
"reply_UNKNOWN": "359",
"reply_NODATA": "4,710",
"reply_NXDOMAIN": "800",
"reply_CNAME": "19,819",
"reply_IP": "9,910",
"reply_DOMAIN": "2",
"reply_RRNAME": "0",
"reply_SERVFAIL": "0",
"reply_REFUSED": "29",
"reply_NOTIMP": "0",
"reply_OTHER": "0",
"reply_DNSSEC": "454",
"reply_NONE": "0",
"reply_BLOB": "80",
"dns_queries_all_replies": "36,163",
"privacy_level": "0",
"status": "enabled",
"gravity_last_updated": {
"file_exists": true,
"absolute": 1667125216,
"relative": {
"days": 4,
"hours": 13,
"minutes": 55
}
}
}
Certain routes that protected and require authentication.
This is accomplished using NextJS middleware and iron-session
check file ./src/middleware.ts
If loading a page that is a protected route, NextJS middleware will check if the user is authenticated.
- if the user is authenticated, the protected page will be loaded
- if the user is NOT authenticated, the user will be redirected to the
Login
page
If making a request to an API that is a protected route, NextJS middleware will check if the user is authenticated.
- if the user is authenticated, the protected API route will be handled accordingly
- if the user is NOT authenticated, the user will be redirected to API route
/api/auth/unauthorized
Resources on helping developing this project