Embed API documentation into FTL. This ensures it is (a) always available locally, and (b) always corresponds to the API you have available locally.

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2021-01-25 21:40:10 +01:00
parent b851566372
commit e4c55ec52a
22 changed files with 1800 additions and 18 deletions
+3
View File
@@ -22,3 +22,6 @@ version~
# MAC->Vendor database files
tools/manuf.data
tools/macvendor.db
# Documentation files generated by cmake
src/api/docs/hex
+7 -5
View File
@@ -18,6 +18,9 @@ if [[ "${1}" == "clean" ]]; then
exit 0
fi
# Remove possibly generated api/docs elements
rm -rf src/api/docs/hex
# Configure build, pass CMake CACHE entries if present
# Wrap multiple options in "" as first argument to ./build.sh:
# ./build.sh "-DA=1 -DB=2" install
@@ -29,13 +32,12 @@ else
cmake ..
fi
# Build the sources
cmake --build . -- -j $(nproc)
# If we are asked to install, we do this here
# Otherwise, we simply copy the binary one level up
# Otherwise, we simply build the sources and copy the binary one level up
if [[ "${1}" == "install" || "${2}" == "install" ]]; then
sudo make install
sudo make install -j $(nproc)
else
# Build the sources
make -j $(nproc)
cp pihole-FTL ../
fi
+1
View File
@@ -170,6 +170,7 @@ add_dependencies(FTL gen_version)
add_executable(pihole-FTL
$<TARGET_OBJECTS:FTL>
$<TARGET_OBJECTS:api>
$<TARGET_OBJECTS:api_docs>
$<TARGET_OBJECTS:webserver>
$<TARGET_OBJECTS:ph7_ext>
$<TARGET_OBJECTS:civetweb>
+1
View File
@@ -27,3 +27,4 @@ add_library(api OBJECT ${sources})
add_dependencies(api gen_version)
target_compile_options(api PRIVATE ${EXTRAWARN})
target_include_directories(api PRIVATE ${PROJECT_SOURCE_DIR}/src)
add_subdirectory(docs)
+48
View File
@@ -0,0 +1,48 @@
# Pi-hole: A black hole for Internet advertisements
# (c) 2021 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# FTL Engine
# /src/api/docs/CMakeList.txt
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
set(sources
hex/index.html
hex/index.css
hex/external/rapidoc-min.js
hex/external/rapidoc-min.js.map
hex/external/highlight.min.js
hex/external/highlight-default.min.css
hex/images/logo.svg
hex/specs/main.yaml
hex/specs/domains.yaml
hex/specs/groups.yaml
hex/specs/clients.yaml
hex/specs/lists.yaml
docs.c
)
# Create relevant directories for processed content
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/specs)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/images)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/external)
find_program(RESOURCE_COMPILER xxd)
file(GLOB_RECURSE COMPILED_RESOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/content" "content/*")
foreach(INPUT_FILE ${COMPILED_RESOURCES})
set(IN ${CMAKE_CURRENT_SOURCE_DIR}/content/${INPUT_FILE})
set(OUTPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/hex/${INPUT_FILE})
add_custom_command(
OUTPUT hex/${INPUT_FILE}
COMMAND ${RESOURCE_COMPILER} -i < ${IN} > ${OUTPUT_FILE}
COMMENT "Compiling ${INPUT_FILE} to binary"
VERBATIM)
list(APPEND COMPILED_RESOURCES ${OUTPUT_FILE})
endforeach()
add_library(api_docs OBJECT ${sources})
#target_compile_options(api_docs PRIVATE ${EXTRAWARN})
target_include_directories(api_docs PRIVATE ${PROJECT_SOURCE_DIR}/src)
@@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"rapidoc-min.js","sources":["webpack:///rapidoc-min.js"],"mappings":";;;;;AAMA;;;;;;;;AAwvSA;;;;;;;AA2oBA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAw3dA;;;;;;AAktBA;;;;;;;;;;;;;AA4ogBA;;;;;;;;;;;;;;AAmYA;;;;;;;;;;;;;;AAiGA;;;;;;;;;;;;;AAsIA;;;;;;;;;;;;;;AAsHA;;;;;;;;;;;;;AAqcA;;;;;;;;;;;;;AAgIA;;;;;;;;;;;;;;AAjCA;;;;;;;;;;;;;AAkFA;;;;;;;;;;;AA22CA;;;;;;;;;;;;;;AAmIA;;;;;;AA6zEA;;;;;;;;;;;;;AA49HA","sourceRoot":""}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88.32 129.93"><defs><linearGradient id="New_Gradient_Swatch_1" x1="2.71" x2="69.77" y1="20.04" y2="20.04" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#12b212"/><stop offset="1" stop-color="#0f0"/></linearGradient><style>.cls-2{fill:#980200}.cls-3{fill:red}</style></defs><title>NewVortex</title><path fill="url(#New_Gradient_Swatch_1)" d="M36.56 39.93C20.34 38.2 4 25.94 2.71 0c25.17 0 38.63 14.9 39.93 38.51 4.76-28.32 27.07-25 27.07-25 1.06 16.05-12.12 25.78-27.07 26.59-4.2-8.85-29.36-30.56-29.36-30.56a.07.07 0 00-.11.08s24.28 21.15 23.39 30.31"/><path d="M44.16 129.93c-1.57-.09-16.22-.65-17.11-17.11-.72-10 7.18-17.37 7.18-27.08C32.44 61.53 0 64.53 0 85.74a19.94 19.94 0 005.83 14.14L30 124.06a19.94 19.94 0 0014.14 5.83" class="cls-2"/><path d="M88.32 85.75c-.09 1.57-.65 16.22-17.11 17.11-10 .72-17.38-7.18-27.08-7.18-24.21 1.79-21.21 34.22 0 34.22a19.94 19.94 0 0014.14-5.83L82.46 99.9a19.94 19.94 0 005.83-14.14" class="cls-3"/><path d="M44.16 41.59c1.57.09 16.22.65 17.11 17.11.72 10-7.18 17.37-7.18 27.08 1.79 24.21 34.22 21.21 34.22 0a19.94 19.94 0 00-5.83-14.14L58.3 47.45a19.94 19.94 0 00-14.14-5.83" class="cls-2"/><path d="M.08 85.75c.09-1.57.65-16.22 17.11-17.11 10-.72 17.38 7.18 27.08 7.18 24.21-1.82 21.21-34.22 0-34.22a19.94 19.94 0 00-14.14 5.83L5.94 71.61A19.94 19.94 0 00.11 85.75" class="cls-3"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+26
View File
@@ -0,0 +1,26 @@
.btn {
width: 90px;
height: 32px;
padding:2px;
font-size:13px;
background-color: #47AFE8;
color: #fff;
border: none;
margin: 0 2px;
border-radius: 2px;
cursor:pointer;
outline:none;
}
.btn.large {
width: 120px;
height: 24px
}
.btn.medium {
width: 75px;
height: 24px
}
.btn.small {
width: 60px;
height: 24px
}
+41
View File
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,user-scalable=yes">
<title>Pi-hole API documentation</title>
<!--<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,600&display=swap" rel="stylesheet">-->
<link rel="stylesheet" href="external/highlight_default.min.css">
<script src="external/highlight.min.js"></script>
<script type='text/javascript' src='external/rapidoc-min.js'></script>
<link href='index.css' rel='stylesheet'>
</head>
<body>
<rapi-doc id = "thedoc"
spec-url = "specs/main.yaml"
allow-server-selection = "true"
allow-authentication = "false"
allow-spec-url-load = "false"
allow-spec-file-load = "false"
show-header = "true"
show-info = "true"
theme = "dark"
allow-try = "true"
sort-endpoints-by = "path"
default-schema-tab = 'example'
schema-style = "tree"
render-style = "view"
primary-color = "#2d87e2"
header-color = "#2d87e2">
<img slot="logo" style="padding-left: 10px;" src="images/logo.svg" width="20px" />
<div slot="header" style="font-weight:700; font-size:32px">Pi-hole API Documentation</div>
<!-- content at the bottom -->
<div slot="footer" style="margin:0; padding:16px 36px; background-color:#2d87e2; color:#fff; text-align:center;">
<button class='btn large' onclick="document.getElementById('thedoc').setAttribute('render-style', 'view')" >Default</button>
<button class='btn large' onclick="document.getElementById('thedoc').setAttribute('render-style', 'read')" >Reader</button>
<button class='btn large' onclick="document.getElementById('thedoc').setAttribute('render-style', 'focused')" >Focused reader</button>
</div>
</rapi-doc>
</body>
</html>
+279
View File
@@ -0,0 +1,279 @@
openapi: 3.0.2
components:
paths:
client:
summary: Modify client
parameters:
- $ref: '#/components/parameters/client'
get:
summary: Get clients
description: |
`{client}` is optional. Specifying it will result in only the requested client being returned.
Valid combinations are:
- `/api/clients` (all clients)
- `/api/clients/my_client` (client identical to `my_client`)
responses:
'200':
description: Successful operation
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/client'
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/readonly'
put:
summary: Replace client
description: |
Items may be updated by replacing them. `{type}`, `{kind}`, and `{item}` are required.
Ensure to send all the required parameters (such as `description`) to ensure these properties are retained.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
You can move existing items to another list type/kind by `PUT`ting the item to the new destination by specifying the optional fields `oldtype` and `oldkind`. Example:
You can use `PUT allow/exact/abc.com` with `oldtype="deny", oldkind="exact"` to change `abc.com` from exact denied to exact allowed. Make sure to always specify *both* values.
When replacing a regular expression, ensure that `{item}` body is properly URI-escaped.
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/client'
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/item_missing'
delete:
summary: Delete client
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/errors/item_missing'
direct:
parameters:
- $ref: '#/components/parameters/type'
- $ref: '#/components/parameters/kind'
post:
summary: Add new client
description: |
Creates a new client in the `clients` object. The `{client}` itself is specified in the request body (POST JSON).
Clients may be described either by their IP addresses (IPv4 and IPv6 are supported),
IP subnets (CIDR notation, like `192.168.2.0/24`), their MAC addresses (like `12:34:56:78:9A:BC`), by their hostnames (like `localhost`), or by the interface they are connected to (prefaced with a colon, like `:eth0`).</p>
Note that client recognition by IP addresses (incl. subnet ranges) is prefered over MAC address, host name or interface recognition as the two latter will only be available after some time.
Furthermore, MAC address recognition only works for devices at most one networking hop away from your Pi-hole.
On success, a new ressource is created at `/clients/{client}`.
The `database_error` with message `UNIQUE contraint failed` error indicates that this client already exists.
parameters:
- $ref: '#/components/schemas/client'
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/client'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/client'
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/duplicate'
schemas:
client:
type: object
properties:
client:
description: client IP / MAC / hostname / interface
type: string
required: true
example: 127.0.0.1
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this client (may be `null` if not specified)
type: string
required: false
x-nullable: true
default: null
example: Some comment for this client
name:
type: object
properties:
name:
description: hostname (only available when {client} is an IP address)
type: string
readOnly: true
example: localhost
groups:
type: object
properties:
groups:
description: Array of group IDs
type: array
default: [0]
required: false
items:
type: integer
enabled:
type: object
properties:
enabled:
description: Status of domain
type: boolean
required: false
default: true
example: true
readonly:
type: object
summary: Readonly properties of a single client
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of item addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last item modification
type: integer
readOnly: true
example: 1611239099
errors:
item_missing:
type: object
description: Item to be modified is missing
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "uri_error"
message:
type: string
description: "Human-readable error message"
example: "Invalid request: Specify item in URI"
data:
type: object
properties:
action:
type: string
description: Action as received by the API
example: "/api/clients"
item:
type: string
description: Item as received by the API
example: ""
no_payload:
type: object
description: No JSON payload found
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "bad_request"
message:
type: string
description: "Human-readable error message"
example: "Invalid request body data (no valid JSON)"
data:
type: null
description: "Additional data (if available)"
example: null
duplicate:
type: object
description: Database error
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "database_error"
message:
type: string
description: "Human-readable error message"
example: "Could not add to gravity database"
data:
type: object
properties:
item:
type: string
description: Item as received by the API
example: 127.0.0.1
comment:
type: string
description: Comment property as received by the API
example: Some comment for this client
sql_msg:
type: string
description: Error returned by the database engine
example: "UNIQUE constraint failed: clientlist.client"
parameters:
client:
in: path
name: client
schema:
type: string
required: true
description: client IP / MAC / hostname / interface
example: 127.0.0.1
+437
View File
@@ -0,0 +1,437 @@
openapi: 3.0.2
components:
paths:
type_kind_domain:
summary: Modify domains and regular expressions
parameters:
- $ref: '#/components/parameters/type'
- $ref: '#/components/parameters/kind'
- $ref: '#/components/parameters/domain'
get:
summary: Get domain
description: |
`{type}`, `{kind}`, and `{domain}` are optional. Specifying any of these may result in only a subset of the available data being returned.
Valid combinations are:
- `/api/domains` (all domains)
- `/api/domains/abc.com` (all domains identical to `abc.com`)
- `/api/domains/allow` (only allowed domains)
- `/api/domains/allow/abc.com` (only allowed domains identical to `abc.com`)
- `/api/domains/allow/exact` (only exactly allowed domains)
- `/api/domains/allow/exact/abc.com` (only exactly allowed domain identical to `abc.com`)
- `/api/domains/allow/regex` (only allowed regex domains)
- `/api/domains/allow/regex/abc.com` (only allowed regex domains identical to `abc.com`)
- `/api/domains/deny` (only denied domains)
- `/api/domains/deny/abc.com` (only denied domains identical to `abc.com`)
- `/api/domains/deny/exact` (only exactly denied domains)
- `/api/domains/deny/exact/abc.com` (only exactly denied domain identical to `abc.com`)
- `/api/domains/deny/regex` (only denied regex domains)
- `/api/domains/deny/regex/abc.com` (only denied regex domains identical to `abc.com`)
- `/api/domains/exact` (allowed and denied exact domains)
- `/api/domains/exact/abc.com` (allowed and denied exact domains identical to `abc.com`)
- `/api/domains/regex` (allowed and denied regex domains)
- `/api/domains/regex/abc.com` (allowed and denied regex domains identical to `abc.com`)
responses:
'200':
description: Successful operation
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/domain'
- $ref: '#/components/schemas/oldtype'
- $ref: '#/components/schemas/oldkind'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
put:
summary: Replace domain
description: |
Items may be updated by replacing them. `{type}`, `{kind}`, and `{domain}` are required.
Ensure to send all the required parameters (such as `comment`) to ensure these properties are retained.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
You can move existing domains to another list type/kind by `PUT`ting the domain to the new destination by specifying the optional fields `oldtype` and `oldkind`. Example:
You can use `PUT allow/exact/abc.com` with `oldtype="deny", oldkind="exact"` to change `abc.com` from exact denied to exact allowed. Make sure to always specify *both* values.
When adding/replacing a regular expression, ensure that `{domain}` is properly URI-escaped.
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/oldtype'
- $ref: '#/components/schemas/oldkind'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
responses:
'201':
description: Created domain
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/domain'
- $ref: '#/components/schemas/oldtype'
- $ref: '#/components/schemas/oldkind'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/list_imprecise'
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/item_missing'
- $ref: '#/components/schemas/errors/invalid_regex'
delete:
summary: Delete domain
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/list_imprecise'
- $ref: '#/components/schemas/errors/item_missing'
type_kind:
parameters:
- $ref: '#/components/parameters/type'
- $ref: '#/components/parameters/kind'
post:
summary: Add new domain
description: |
Creates a new domain in the `domains` object. This may be either an exact domain or a regex, depending on `{kind}`.
Both `{type}` and `{kind}` are mandatory for this endpoint.
The `{domain}` itself is specified in the request body (POST JSON).
On success, a new ressource is created at `/domains/{type}/{kind}/{domain}`.
The `database_error` with message `UNIQUE contraint failed` error indicates that the same entry (`domain`, `type`, `kind`) already exists.
When adding a regular expression, ensure the request body is properly JSON-escaped.
parameters:
- $ref: '#/components/schemas/domain'
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/domain'
- $ref: '#/components/schemas/type'
- $ref: '#/components/schemas/kind'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
responses:
'201':
description: Created domain
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/domain'
- $ref: '#/components/schemas/type'
- $ref: '#/components/schemas/kind'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/list_imprecise'
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/duplicate'
- $ref: '#/components/schemas/errors/invalid_regex'
schemas:
domain:
type: object
properties:
domain:
description: Domain
type: string
required: true
example: testdomain.com
type:
type: object
properties:
type:
description: String specifying domain type
type: enum
required: true
enum:
- allow
- deny
example: allow
oldtype:
type: object
summary: Extra property needed when moving an domain between types and/or kinds
properties:
oldtype:
description: Type of domain *before* moving
type: enum
required: false
enum:
- allow
- deny
example: allow
kind:
type: object
properties:
kind:
description: String specifying domain kind
type: enum
required: true
enum:
- exact
- regex
example: exact
oldkind:
type: object
summary: Extra property needed when moving an domain between types and/or kinds
properties:
oldkind:
description: Kind of domain *before* moving
type: enum
required: false
enum:
- exact
- regex
example: exact
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this domain (may be `null` if not specified)
type: string
required: false
x-nullable: true
default: null
example: Some comment describing this domain
groups:
type: object
properties:
groups:
description: Array of group IDs
type: array
default: [0]
required: false
items:
type: integer
enabled:
type: object
properties:
enabled:
description: Status of domain
type: boolean
required: false
default: true
example: true
readonly:
type: object
summary: Readonly properties of a domain
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of domain addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last domain modification
type: integer
readOnly: true
example: 1611239099
errors:
list_imprecise:
type: object
description: List not specified precisely enough
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "bad_request"
message:
type: string
description: "Human-readable error message"
example: "Invalid request: Specify list to modify more precisely"
data:
type: object
properties:
action:
type: string
description: Action as received by the API
example: "/api/domains/allow"
item:
type: string
description: Item as received by the API
example: "testdomain.com"
item_missing:
type: object
description: Item to be modified is missing
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "uri_error"
message:
type: string
description: "Human-readable error message"
example: "Invalid request: Specify item in URI"
data:
type: object
properties:
action:
type: string
description: Action as received by the API
example: "/api/domains/allow/exact"
item:
type: string
description: Item as received by the API
example: ""
no_payload:
type: object
description: No JSON payload found
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "bad_request"
message:
type: string
description: "Human-readable error message"
example: "Invalid request body data (no valid JSON)"
data:
type: null
description: "Additional data (if available)"
example: null
duplicate:
type: object
description: Database error
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "database_error"
message:
type: string
description: "Human-readable error message"
example: "Could not add to gravity database"
data:
type: object
properties:
domain:
type: string
description: Item as received by the API
example: "testdomain.com"
enabled:
type: boolean
description: Enabled property as received by the API
example: true
comment:
type: string
description: Comment property as received by the API
example: Some comment describing this domain
sql_msg:
type: string
description: Error returned by the database engine
example: "UNIQUE constraint failed: domainlist.domain"
invalid_regex:
type: object
description: Invalid regex rejected
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "regex_error"
message:
type: string
description: "Human-readable error message"
example: "Regex validation failed"
data:
type: object
properties:
domain:
type: string
description: Item as received by the API
example: "[[[[^"
enabled:
type: boolean
description: Enabled property as received by the API
example: true
comment:
type: string
description: Comment property as received by the API
example: Some comment describing this domain
regex_msg:
type: string
description: Error returned by the regex engine
example: "Missing ']'"
parameters:
type:
in: path
description: Type (allowed or denied domain)
name: type
schema:
type: string
enum:
- allow
- deny
required: true
example: allow
kind:
in: path
name: kind
schema:
type: string
enum:
- exact
- regex
required: true
description: Kind (exact domain or regular expression)
example: exact
domain:
in: path
name: domain
schema:
type: string
required: true
description: Domain
example: testdomain.com
+257
View File
@@ -0,0 +1,257 @@
openapi: 3.0.2
components:
paths:
name:
summary: Modify group
parameters:
- $ref: '#/components/parameters/name'
get:
summary: Get groups
description: |
`{name}` is optional. Specifying it will result in only the requested group being returned.
Valid combinations are:
- `/api/groups` (all groups)
- `/api/groups/my_group` (group identical to `my_group`)
responses:
'200':
description: Successful operation
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
put:
summary: Replace group
description: |
Items may be updated by replacing them. `{type}`, `{kind}`, and `{item}` are required.
Ensure to send all the required parameters (such as `description`) to ensure these properties are retained.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
You can move existing items to another list type/kind by `PUT`ting the item to the new destination by specifying the optional fields `oldtype` and `oldkind`. Example:
You can use `PUT allow/exact/abc.com` with `oldtype="deny", oldkind="exact"` to change `abc.com` from exact denied to exact allowed. Make sure to always specify *both* values.
When replacing a regular expression, ensure that `{item}` body is properly URI-escaped.
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/enabled'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/item_missing'
delete:
summary: Delete group
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/item_missing'
direct:
parameters:
- $ref: '#/components/parameters/type'
- $ref: '#/components/parameters/kind'
post:
summary: Add new group
description: |
Creates a new group in the `groups` object. The `{group}` itself is specified in the request body (POST JSON).
On success, a new ressource is created at `/groups/{name}`.
The `database_error` with message `UNIQUE contraint failed` error indicates that a group with the same name already exists.
parameters:
- $ref: '#/components/schemas/name'
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/enabled'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/duplicate'
schemas:
name:
type: object
properties:
name:
description: Group name
type: string
required: true
example: test_group
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this group (may be `null` if not specified)
type: string
required: false
x-nullable: true
default: null
example: Some comment for this group
enabled:
type: object
properties:
enabled:
description: Status of item
type: boolean
required: false
default: true
example: true
readonly:
type: object
summary: Readonly properties of a single group
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of item addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last item modification
type: integer
readOnly: true
example: 1611239099
errors:
item_missing:
type: object
description: Item to be modified is missing
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "uri_error"
message:
type: string
description: "Human-readable error message"
example: "Invalid request: Specify item in URI"
data:
type: object
properties:
action:
type: string
description: Action as received by the API
example: "/api/groups"
item:
type: string
description: Item as received by the API
example: ""
no_payload:
type: object
description: No JSON payload found
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "bad_request"
message:
type: string
description: "Human-readable error message"
example: "Invalid request body data (no valid JSON)"
data:
type: null
description: "Additional data (if available)"
example: null
duplicate:
type: object
description: Database error
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "database_error"
message:
type: string
description: "Human-readable error message"
example: "Could not add to gravity database"
data:
type: object
properties:
item:
type: string
description: Item as received by the API
example: test_group
enabled:
type: boolean
description: Enabled property as received by the API
example: true
comment:
type: string
description: Comment property as received by the API
example: Some comment for this group
sql_msg:
type: string
description: Error returned by the database engine
example: "UNIQUE constraint failed: grouplist.group"
parameters:
name:
in: path
name: name
schema:
type: string
required: true
description: Group name
example: test_group
+280
View File
@@ -0,0 +1,280 @@
openapi: 3.0.2
components:
paths:
list:
summary: Modify list
parameters:
- $ref: '#/components/parameters/list'
get:
summary: Get lists
description: |
`{list}` is optional. Specifying it will result in only the requested list being returned.
Valid combinations are:
- `/api/lists` (all lists)
- `/api/lists/my_list` (list identical to `my_list`)
responses:
'200':
description: Successful operation
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/list'
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
put:
summary: Replace list
description: |
Items may be updated by replacing them. `{type}`, `{kind}`, and `{item}` are required.
Ensure to send all the required parameters (such as `description`) to ensure these properties are retained.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
You can move existing items to another list type/kind by `PUT`ting the item to the new destination by specifying the optional fields `oldtype` and `oldkind`. Example:
You can use `PUT allow/exact/abc.com` with `oldtype="deny", oldkind="exact"` to change `abc.com` from exact denied to exact allowed. Make sure to always specify *both* values.
When replacing a regular expression, ensure that `{item}` body is properly URI-escaped.
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/list'
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/item_missing'
delete:
summary: Delete list
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/errors/item_missing'
direct:
parameters:
- $ref: '#/components/parameters/type'
- $ref: '#/components/parameters/kind'
post:
summary: Add new list
description: |
Creates a new list in the `lists` object. The `{list}` itself is specified in the request body (POST JSON).
lists may be described either by their IP addresses (IPv4 and IPv6 are supported),
IP subnets (CIDR notation, like `192.168.2.0/24`), their MAC addresses (like `12:34:56:78:9A:BC`), by their hostnames (like `localhost`), or by the interface they are connected to (prefaced with a colon, like `:eth0`).</p>
Note that list recognition by IP addresses (incl. subnet ranges) is prefered over MAC address, host name or interface recognition as the two latter will only be available after some time.
Furthermore, MAC address recognition only works for devices at most one networking hop away from your Pi-hole.
On success, a new ressource is created at `/lists/{list}`.
The `database_error` with message `UNIQUE contraint failed` error indicates that this list already exists.
parameters:
- $ref: '#/components/schemas/list'
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: '#/components/schemas/list'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/list'
- $ref: '#/components/schemas/name'
- $ref: '#/components/schemas/comment'
- $ref: '#/components/schemas/groups'
- $ref: '#/components/schemas/enabled'
- $ref: '#/components/schemas/readonly'
'400':
description: Bad request
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/errors/no_payload'
- $ref: '#/components/schemas/errors/duplicate'
schemas:
list:
type: object
properties:
address:
description: Address of the list
type: string
required: true
example: https://hosts-file.net/ad_servers.txt
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this list (may be `null` if not specified)
type: string
required: false
x-nullable: true
default: null
example: Some comment for this list
groups:
type: object
properties:
groups:
description: Array of group IDs
type: array
default: [0]
required: false
items:
type: integer
enabled:
type: object
properties:
enabled:
description: Status of domain
type: boolean
required: false
default: true
example: true
readonly:
type: object
summary: Readonly properties of a single list
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of item addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last item modification
type: integer
readOnly: true
example: 1611239099
errors:
item_missing:
type: object
description: Item to be modified is missing
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "uri_error"
message:
type: string
description: "Human-readable error message"
example: "Invalid request: Specify item in URI"
data:
type: object
properties:
action:
type: string
description: Action as received by the API
example: "/api/lists"
item:
type: string
description: Item as received by the API
example: ""
no_payload:
type: object
description: No JSON payload found
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "bad_request"
message:
type: string
description: "Human-readable error message"
example: "Invalid request body data (no valid JSON)"
data:
type: null
description: "Additional data (if available)"
example: null
duplicate:
type: object
description: Database error
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "database_error"
message:
type: string
description: "Human-readable error message"
example: "Could not add to gravity database"
data:
type: object
properties:
item:
type: string
description: Item as received by the API
example: "https://hosts-file.net/ad_servers.txt"
enabled:
type: boolean
description: Enabled property as received by the API
example: true
comment:
type: string
description: Comment property as received by the API
example: Some comment for this list
sql_msg:
type: string
description: Error returned by the database engine
example: "UNIQUE constraint failed: listlist.list"
parameters:
list:
in: path
name: list
schema:
type: string
required: true
description: Address of the list
example: https://hosts-file.net/ad_servers.txt
+42
View File
@@ -0,0 +1,42 @@
openapi: 3.0.2
info:
title: Pi-hole API
version: "6.0"
description: >
The Pi-hole API is organized around [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer). Our API has predictable resource-oriented URLs, accepts and returns reliable UTF-8 [JavaScript Object Notation (JSON)-encoded](http://www.json.org/) data for all API responses, and uses standard HTTP response codes and verbs.
Most (but not all) endpoints require authentication. API endpoints requiring authentication will fail with code `401 Unauthorized` if no key is supplied.
servers:
- url: http://127.0.0.1/api/
- url: 'http://{url}:{port}/api/'
variables:
url:
description: URL or address of your Pi-hole
default: pi.hole
port:
description: Port of your Pi-hole's API
default: 8080
paths:
/domains/{type}/{kind}/{domain}:
$ref: 'domains.yaml#/components/paths/type_kind_domain'
/domains/{type}/{kind}:
$ref: 'domains.yaml#/components/paths/type_kind'
/groups/{name}:
$ref: 'groups.yaml#/components/paths/name'
/groups:
$ref: 'groups.yaml#/components/paths/direct'
/clients/{client}:
$ref: 'clients.yaml#/components/paths/client'
/clients:
$ref: 'clients.yaml#/components/paths/direct'
/lists/{list}:
$ref: 'lists.yaml#/components/paths/list'
/lists:
$ref: 'lists.yaml#/components/paths/direct'
+43
View File
@@ -0,0 +1,43 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2021 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/docs
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "docs.h"
int api_docs(struct ftl_conn *api)
{
// Handle ressource request by redirecting to "/"
if(strcmp(api->request->request_uri, "/api/docs") == 0)
{
mg_send_http_redirect(api->conn, "/api/docs/", 301);
}
// Handle root request by redirecting to "/"
bool serve_index = false;
if(strcmp(api->request->request_uri, "/api/docs/") == 0)
{
serve_index = true;
}
// Loop over all available files and see if we can serve this request
for(unsigned int i = 0; i < (sizeof(docs_files)/sizeof(docs_files[0])); i++)
{
// Check if this is the requested file
if(strcmp(docs_files[i].path, api->item) == 0 ||
(serve_index && strcmp(docs_files[i].path, "index.html") == 0))
{
// Send the file
mg_send_http_ok(api->conn, docs_files[i].mime_type, NULL, docs_files[i].content_size);
return mg_write(api->conn, docs_files[i].content, docs_files[i].content_size);
}
}
// Requested path was not found
return 0;
}
+90
View File
@@ -0,0 +1,90 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2021 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/docs (helper)
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef API_DOCS_H
#define API_DOCS_H
#include "../../FTL.h"
#include "../../civetweb/civetweb.h"
#include "../../webserver/http-common.h"
#include "../../webserver/json_macros.h"
#include "../routes.h"
static const char index_html[] = {
#include "hex/index.html"
};
static const char index_css[] = {
#include "hex/index.css"
};
static const char rapidoc_min_js[] = {
#include "hex/external/rapidoc-min.js"
};
static const char rapidoc_min_map_js[] = {
#include "hex/external/rapidoc-min.js.map"
};
static const char highlight_default_min_css[] = {
#include "hex/external/highlight-default.min.css"
};
static const char highlight_min_js[] = {
#include "hex/external/highlight.min.js"
};
static const char images_logo_svg[] = {
#include "hex/images/logo.svg"
};
static const char specs_main_yaml[] = {
#include "hex/specs/main.yaml"
};
static const char specs_domains_yaml[] = {
#include "hex/specs/domains.yaml"
};
static const char specs_groups_yaml[] = {
#include "hex/specs/groups.yaml"
};
static const char specs_clients_yaml[] = {
#include "hex/specs/clients.yaml"
};
static const char specs_lists_yaml[] = {
#include "hex/specs/lists.yaml"
};
struct {
const char *path;
const char *mime_type;
const char *content;
const size_t content_size;
} docs_files[] =
{
{"index.html", "text/html", index_html, sizeof(index_html)},
{"index.css", "text/css", index_css, sizeof(index_css)},
{"external/rapidoc-min.js", "application/javascript", rapidoc_min_js, sizeof(rapidoc_min_js)},
{"external/rapidoc-min.map.js", "text/plain", rapidoc_min_map_js, sizeof(rapidoc_min_map_js)},
{"external/highlight-default.min.css", "text/css", highlight_default_min_css, sizeof(highlight_default_min_css)},
{"external/highlight.min.js", "text/css", highlight_min_js, sizeof(highlight_min_js)},
{"images/logo.svg", "image/svg+xml", images_logo_svg, sizeof(images_logo_svg)},
{"specs/main.yaml", "text/plain", specs_main_yaml, sizeof(specs_main_yaml)},
{"specs/domains.yaml", "text/plain", specs_domains_yaml, sizeof(specs_domains_yaml)},
{"specs/groups.yaml", "text/plain", specs_groups_yaml, sizeof(specs_groups_yaml)},
{"specs/clients.yaml", "text/plain", specs_clients_yaml, sizeof(specs_clients_yaml)},
{"specs/lists.yaml", "text/plain", specs_lists_yaml, sizeof(specs_lists_yaml)},
};
#endif // API_DOCS_H
+12 -13
View File
@@ -62,21 +62,21 @@ static int api_list_read(struct ftl_conn *api,
}
else if(listtype == GRAVITY_CLIENTS)
{
char *name = NULL;
if(table.client != NULL)
{
JSON_OBJ_COPY_STR(row, "client", table.client);
char *name = getNameFromIP(table.client);
JSON_OBJ_COPY_STR(row, "name", name);
if(name != NULL)
free(name);
// Try to obtain hostname if this is a valid IP address
if(isValidIPv4(table.client) || isValidIPv6(table.client))
name = getNameFromIP(table.client);
}
else
{
JSON_OBJ_ADD_NULL(row, "ip");
JSON_OBJ_ADD_NULL(row, "name");
}
JSON_OBJ_COPY_STR(row, "client", table.client);
JSON_OBJ_COPY_STR(row, "name", name);
JSON_OBJ_COPY_STR(row, "comment", table.comment);
// Free allocated memory (if applicable)
if(name != NULL)
free(name);
}
else // domainlists
{
@@ -94,7 +94,6 @@ static int api_list_read(struct ftl_conn *api,
// the group_concat result delivered from the database,
// parse it as valid array and append it as row to the
// data
logg("table.group_ids = %p \"%s\"", table.group_ids, table.group_ids);
char group_ids_str[strlen(table.group_ids)+3u];
group_ids_str[0] = '[';
strcpy(group_ids_str+1u , table.group_ids);
@@ -324,7 +323,7 @@ static int api_list_write(struct ftl_conn *api,
{
// Error adding item, prepare error object
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(json, "item", item);
JSON_OBJ_REF_STR(json, "item", row.item);
JSON_OBJ_ADD_BOOL(json, "enabled", row.enabled);
if(row.comment != NULL)
JSON_OBJ_REF_STR(json, "comment", row.comment);
+5
View File
@@ -178,6 +178,11 @@ int api_handler(struct mg_connection *conn, void *ignored)
{
ret = api_settings_web(&api);
}
/******************************** /api/settings ****************************/
else if((api.item = startsWith("/api/docs", &api)) != NULL)
{
ret = api_docs(&api);
}
/******************************** not found or invalid request**************/
if(ret == 0)
{
+3
View File
@@ -68,4 +68,7 @@ int api_auth(struct ftl_conn *api);
// Settings methods
int api_settings_web(struct ftl_conn *api);
// Documentation methods
int api_docs(struct ftl_conn *api);
#endif // ROUTES_H