-
-
Save malesfth/d5fb2eb36aab4e726727c14ba32f9f8b to your computer and use it in GitHub Desktop.
// Parameters: | |
// {"url":"https://pihole","token": "x"} | |
// Optional key in parameters: "theme": system|light|dark | |
let piholeURL = "" // set the URL here for debug | |
let apiToken = "" // set the api token here for debug | |
let wTheme = "" // set the theme for debug | |
if (config.runsInWidget) { | |
const widgetParams = (args.widgetParameter != null ? JSON.parse(args.widgetParameter) : null) | |
if (widgetParams==null) { | |
throw new Error("Please long press the widget and add the parameters.") | |
} else if (!widgetParams.hasOwnProperty("url") || !widgetParams.hasOwnProperty("token")) { | |
throw new Error("Wrong parameters.") | |
} | |
piholeURL = widgetParams.url | |
apiToken = widgetParams.token | |
if (widgetParams.hasOwnProperty("theme")) { | |
wTheme = widgetParams.theme | |
} | |
} | |
let wBackground = new LinearGradient() | |
let wColor = new Color("#ffffff") | |
setTheme() | |
let piholeStats = await getStats() | |
let wSize = config.widgetFamily || "large" //set size of widget for debug | |
let widget = await createWidget() || null | |
if (!config.runsInWidget) { | |
if (wSize=="large") { await widget.presentLarge() } | |
else if (wSize=="medium") { await widget.presentMedium() } | |
else { await widget.presentSmall() } | |
} | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget() { | |
let w = new ListWidget() | |
w.backgroundGradient = wBackground | |
w.addSpacer() | |
w.setPadding(5, 15, 0, (wSize=="small" ? 0 : 10)) | |
let state = (piholeStats!=null ? (piholeStats.status=="enabled" ? true : false) : null) | |
let icn = null | |
if (state==true) { | |
icn = SFSymbol.named((state ? "checkmark.shield.fill" : "xmark.shield.fill")) | |
} else { | |
icn = SFSymbol.named("xmark.circle.fill") | |
state = false | |
} | |
let topStack = w.addStack() | |
topStack.layoutHorizontally() | |
topStack.setPadding(5, 0, 0, 10) | |
let content = topStack.addImage(icn.image) | |
content.tintColor = (state ? Color.green() : Color.red()) | |
content.imageSize = new Size(16,16) | |
topStack.addSpacer(5) | |
content = topStack.addText("Pi-hole") | |
content.font = Font.blackSystemFont(16) | |
content.textColor = wColor | |
if (state==true && wSize != "small") { | |
topStack.addSpacer() | |
topStack.addText(" ") // same line with distance to title | |
addUpdateItem(topStack, (piholeStats.core_current==piholeStats.core_latest ? true : false), "Pi" + ((wSize=="small") ? "" : "-hole")) | |
topStack.addText(" ") | |
addUpdateItem(topStack, (piholeStats.web_current==piholeStats.web_latest ? true : false), "WebUI") | |
topStack.addText(" ") | |
addUpdateItem(topStack, (piholeStats.FTL_current==piholeStats.FTL_latest ? true : false), "FTL") | |
} | |
w.addSpacer(8) | |
if (piholeStats==null) { | |
content = w.addText("No connection") | |
content.font = Font.thinSystemFont(14) | |
content.textColor = wColor | |
w.addSpacer() | |
return w | |
} | |
w.url = piholeURL + "/admin/" | |
addItem(w, "Total Queries", piholeStats.dns_queries_all_types) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
addItem(w, "Queries Blocked", piholeStats.ads_blocked_today) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
addItem(w, "Percent Blocked", Number.parseFloat(piholeStats.ads_percentage_today).toFixed(1).replace(".", ",") + "%") | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
addItem(w, "Domains on Blocklist", piholeStats.domains_being_blocked) | |
if (wSize=="large") { | |
addItem(w, "Unique Domains", piholeStats.unique_domains) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
addItem(w, "Cached Queries", piholeStats.queries_cached) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
addItem(w, "Queries Forwarded", piholeStats.queries_forwarded) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
addItem(w, "Clients Seen / Unique", piholeStats.clients_ever_seen + " / " + piholeStats.unique_clients) | |
} | |
w.addSpacer() | |
return w | |
} | |
function addItem(w, strHeadline, strData) { | |
let fontSizeHeadline = 12 | |
let fontSizeString = 9 | |
switch (wSize) { | |
case "large": | |
fontSizeHeadline = 18 | |
fontSizeString = 14 | |
break; | |
case "medium": | |
fontSizeHeadline = 14 | |
fontSizeString = 11 | |
break; | |
} | |
let layoutStack = w.addStack() | |
layoutStack.setPadding(3, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
content = layoutStack.addText(strHeadline) | |
content.font = Font.mediumSystemFont(fontSizeHeadline) | |
content.textColor = wColor | |
layoutStack.addSpacer() | |
content = layoutStack.addText(strData.toString()) | |
content.font = Font.mediumSystemFont(fontSizeString) | |
content.textColor = wColor | |
} | |
function addUpdateItem(stack, status, text) { | |
let icn = SFSymbol.named((status ? "checkmark.circle.fill" : "exclamationmark.triangle.fill")) | |
let content = stack.addImage(icn.image) | |
content.tintColor = (status ? Color.green() : Color.red()) | |
content.imageSize = new Size(14,14) | |
content = stack.addText(((wSize!="small") ? " " : "" ) + text) | |
content.font = Font.semiboldMonospacedSystemFont(12) | |
content.textColor = wColor | |
} | |
function setTheme() { | |
if (wTheme=="system") { | |
if (Device.isUsingDarkAppearance()) { | |
wTheme = "dark" | |
} else { | |
wTheme = "light" | |
} | |
} | |
wBackground.locations = [0, 1] | |
if (wTheme=="dark") { | |
wBackground.colors = [ | |
new Color("#384d54"), | |
new Color("#384d54") | |
] | |
wColor = new Color("#ffffff") | |
} else { | |
wBackground.colors = [ | |
new Color("#ffffffe6"), //ffffffe6 | |
new Color("#ffffffe6") | |
] | |
wColor = new Color("#000000") | |
} | |
} | |
async function getStats() { | |
try { | |
let req = new Request(piholeURL + "/admin/api.php?versions&summary&auth="+apiToken) | |
let json = await req.loadJSON() | |
if (json.length === 0) { return null } | |
return json | |
} catch(err) { | |
console.error(err) | |
return null | |
} | |
} |
Just replaced the comma’s and dots again (in Europe that is the used decimal setting)
@marked80
How did you replaced them?
Just replaced the comma’s and dots again (in Europe that is the used decimal setting)
@marked80
How did you replaced them?
With a .replace function.
Below is the modified script:
// Parameters:
// {"url":"https://pihole","token": "x"}
// Optional key in parameters: "theme": system|light|dark
let piholeURL = "http://192.xxx.x.x" // set the URL here for debug
let apiToken = "xxxxxxx" // set the api token here for debug
let wTheme = "" // set the theme for debug
if (config.runsInWidget) {
const widgetParams = (args.widgetParameter != null ? JSON.parse(args.widgetParameter) : null)
if (widgetParams==null) {
throw new Error("Please long press the widget and add the parameters.")
} else if (!widgetParams.hasOwnProperty("url")) {
throw new Error("Wrong parameters.")
}
piholeURL = widgetParams.url
apiToken = widgetParams.token
if (widgetParams.hasOwnProperty("theme")) {
wTheme = widgetParams.theme
}
}
let wBackground = new LinearGradient()
let wColor = new Color("#ffffff")
setTheme()
let piholeStats = await getStats()
let adminPage = await getAdminPage()
let isLatestVersion = await getUpdateStats(adminPage)
let wSize = config.widgetFamily || "large" //set size of widget for debug
let widget = await createWidget() || null
if (!config.runsInWidget) {
if (wSize=="large") { await widget.presentLarge() }
else if (wSize=="medium") { await widget.presentMedium() }
else { await widget.presentSmall() }
}
Script.setWidget(widget)
Script.complete()
async function createWidget() {
let w = new ListWidget()
w.backgroundGradient = wBackground
w.addSpacer()
w.setPadding(5, 15, 0, (wSize=="small" ? 0 : 10))
let state = (piholeStats!=null ? (piholeStats.status=="enabled" ? true : false) : null)
let icn = null
if (state==true) {
icn = SFSymbol.named((state ? "checkmark.shield.fill" : "xmark.shield.fill"))
} else {
icn = SFSymbol.named("xmark.circle.fill")
state = false
}
let topStack = w.addStack()
topStack.layoutHorizontally()
topStack.setPadding(5, 0, 0, 10)
let content = topStack.addImage(icn.image)
content.tintColor = (state ? Color.green() : Color.red())
content.imageSize = new Size(16,16)
topStack.addSpacer(5)
content = topStack.addText("Pi-hole")
content.font = Font.blackSystemFont(16)
content.textColor = wColor
if (state==true && wSize != "small") {
topStack.addSpacer()
topStack.addText(" ") // same line with distance to title
addUpdateItem(topStack, isLatestVersion[0], "Pi" + ((wSize=="small") ? "" : "-hole"))
topStack.addText(" ")
addUpdateItem(topStack, isLatestVersion[2], "WebUI")
topStack.addText(" ")
addUpdateItem(topStack, isLatestVersion[1], "FTL")
}
w.addSpacer(8)
if (piholeStats==null) {
content = w.addText("No connection")
content.font = Font.thinSystemFont(14)
content.textColor = wColor
w.addSpacer()
return w
}
w.url = piholeURL + "/admin/"
addItem(w, "Total Queries", piholeStats.dns_queries_all_types.replace(",", "."))
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Queries Blocked", piholeStats.ads_blocked_today.replace(",", "."))
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Percent Blocked", Number.parseFloat(piholeStats.ads_percentage_today).toFixed(1).replace(".", ",") + "%")
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Domains on Blocklist", piholeStats.domains_being_blocked.replace(",", "."))
if (wSize=="large") {
addItem(w, "Unique Domains", piholeStats.unique_domains)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Cached Queries", piholeStats.queries_cached.replace(",", "."))
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Queries Forwarded", piholeStats.queries_forwarded.replace(",", "."))
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Clients Seen / Unique", piholeStats.clients_ever_seen + " / " + piholeStats.unique_clients)
}
w.addSpacer()
return w
}
function addItem(w, strHeadline, strData) {
let fontSizeHeadline = 12
let fontSizeString = 9
switch (wSize) {
case "large":
fontSizeHeadline = 18
fontSizeString = 14
break;
case "medium":
fontSizeHeadline = 14
fontSizeString = 11
break;
}
let layoutStack = w.addStack()
layoutStack.setPadding(3, 0, 0, 10)
layoutStack.centerAlignContent()
content = layoutStack.addText(strHeadline)
content.font = Font.mediumSystemFont(fontSizeHeadline)
content.textColor = wColor
layoutStack.addSpacer()
content = layoutStack.addText(strData.toString())
content.font = Font.mediumSystemFont(fontSizeString)
content.textColor = wColor
}
function addUpdateItem(stack, status, text) {
let icn = SFSymbol.named((status ? "checkmark.circle.fill" : "exclamationmark.triangle.fill"))
let content = stack.addImage(icn.image)
content.tintColor = (status ? Color.green() : Color.red())
content.imageSize = new Size(14,14)
content = stack.addText(((wSize!="small") ? " " : "" ) + text)
content.font = Font.semiboldMonospacedSystemFont(12)
content.textColor = wColor
}
function setTheme() {
if (wTheme=="system") {
if (Device.isUsingDarkAppearance()) {
wTheme = "dark"
} else {
wTheme = "light"
}
}
wBackground.locations = [0, 1]
if (wTheme=="dark") {
wBackground.colors = [
new Color("#384d54"),
new Color("#384d54")
]
wColor = new Color("#ffffff")
} else {
wBackground.colors = [
new Color("#ffffffe6"), //ffffffe6
new Color("#ffffffe6")
]
wColor = new Color("#000000")
}
}
async function getUpdateStats (webPage) {
let latestVersions = [true, true, true]
try {
let view = new WebView()
await view.loadHTML(webPage);
let data = await view.evaluateJavaScript(`
let retoure = [true, true, true]
let a = Array.from(document.querySelectorAll('div.version-info div ul li'))
for (let i=0; i<a.length; i++) {
let el = a[i]
if (el.getElementsByTagName('a').length == 2) {
retoure[i] = false
}
}
completion(retoure)`, true)
latestVersions = data
} catch(e) {
console.log("Cannot get update stats: " + e)
}
return latestVersions
}
async function getStats() {
try {
let req = new Request(piholeURL + "/admin/api.php?summary&auth="+apiToken)
let json = await req.loadJSON()
if (json.length === 0) { json == null }
return json
} catch(err) {
console.error(err)
return null
}
}
async function getAdminPage() {
try {
let req = new Request(piholeURL + "/admin/")
let adminHTMLPage = await req.loadString()
return adminHTMLPage
} catch(err) {
console.log(err)
return null
}
}
THX
Hi
does this work with pi hole v6? Cant seem to get a connection when setting up the script.
I'll second Tristy13's post. I was finally able to get the updated Pi-hole Remote app working, with Pi-hole V6, using the App Password. Will there be an update for the Pi-Hole widget?
Pi-Hole v6 has introduced a new REST-API.
API-Tokens are not available anymore, Its required to generate a new app-password for api-access.
Also it's using authenticated sessions now.
This is working on my side:
// Parameters:
// {"url":"https://pihole","apikey":"123abc"}
// Optional key in parameters: "theme": system|light|dark, "apiPath": "/api"
let piholeURL = "http://localhost:80" //set the URL here for debug
let piholeAppPassword = "qP8JjyWxmhb7Tdij1geQJa05GHsMBYOaJtVAdk6Czjk=" // set the API-key here for debug
let wTheme = "system" // set the theme for debug
let piholeApiPath = "/api"
if (config.runsInWidget) {
const widgetParams = (args.widgetParameter != null ? JSON.parse(args.widgetParameter) : null)
if (widgetParams==null) {
throw new Error("Please long press the widget and add the parameters.")
} else if (!widgetParams.hasOwnProperty("url") && !widgetParams.hasOwnProperty("apikey")) {
throw new Error("Wrong parameters.")
}
piholeURL = widgetParams.url
piholeAppPassword = widgetParams.apikey
if (widgetParams.hasOwnProperty("theme")) {
wTheme = widgetParams.theme
}
if (widgetParams.hasOwnProperty("apiPath")) {
piholeApiPath = widgetParams.apiPath
}
}
let wBackground = new LinearGradient()
let wColor = new Color("#ffffff")
setTheme()
let sid = await authenticate();
let piholeStats = await sendGetRequest("/stats/summary", sid);
let dns = await sendGetRequest("/dns/blocking", sid);
let wSize = config.widgetFamily || "small" //set size of widget for debug
let widget = await createWidget() || null
if (!config.runsInWidget) {
if (wSize=="large") { await widget.presentLarge() }
else if (wSize=="medium") { await widget.presentMedium() }
else { await widget.presentSmall() }
}
await invalidateSession(sid);
Script.setWidget(widget)
Script.complete()
async function createWidget() {
let w = new ListWidget()
w.backgroundGradient = wBackground
w.addSpacer()
w.setPadding(5, 15, 0, (wSize=="small" ? 0 : 10))
let state = (piholeStats!=null ? (dns.blocking=="enabled" ? true : false) : null)
let icn = null
if (state==true) {
icn = SFSymbol.named((state ? "checkmark.shield.fill" : "xmark.shield.fill"))
} else {
icn = SFSymbol.named("xmark.circle.fill")
state = false
}
let topStack = w.addStack()
let content = topStack.addImage(icn.image)
content.tintColor = (state ? Color.green() : Color.red())
content.imageSize = new Size(16,16)
topStack.addSpacer(5)
content = topStack.addText("Pi-hole")
content.font = Font.blackSystemFont(16)
content.textColor = wColor
w.addSpacer(8)
if (piholeStats==null) {
content = w.addText("No connection")
content.font = Font.thinSystemFont(14)
content.textColor = wColor
w.addSpacer()
return w
}
w.url = piholeURL
addItem(w, "Total Queries", `${piholeStats.queries.total}` )
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Queries Blocked", `${piholeStats.queries.blocked}`)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Percent Blocked", piholeStats.queries.percent_blocked.toFixed(1) + "%")
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Domains on Blocklist", `${piholeStats.gravity.domains_being_blocked}`)
if (wSize=="large") {
addItem(w, "Unique Domains", `${piholeStats.queries.unique_domains}`)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Cached Queries", `${piholeStats.queries.cached}`)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Queries Forwarded", `${piholeStats.queries.cached}`)
}
w.addSpacer()
return w
}
function addItem(w, strHeadline, strData) {
let fontSizeHeadline = 12
let fontSizeString = 9
switch (wSize) {
case "large":
fontSizeHeadline = 18
fontSizeString = 14
break;
case "medium":
fontSizeHeadline = 14
fontSizeString = 11
break;
}
let layoutStack = w.addStack()
layoutStack.setPadding(3, 0, 0, 10)
layoutStack.centerAlignContent()
content = layoutStack.addText(strHeadline)
content.font = Font.mediumSystemFont(fontSizeHeadline)
content.textColor = wColor
layoutStack.addSpacer()
content = layoutStack.addText(strData)
content.font = Font.mediumSystemFont(fontSizeString)
content.textColor = wColor
}
function setTheme() {
if (wTheme=="system") {
if (Device.isUsingDarkAppearance()) {
wTheme = "dark"
} else {
wTheme = "light"
}
}
wBackground.locations = [0, 1]
if (wTheme=="dark") {
wBackground.colors = [
new Color("#384d54"),
new Color("#384d54")
]
wColor = new Color("#ffffff")
} else {
wBackground.colors = [
new Color("#ffffffe6"),
new Color("#ffffffe6")
]
wColor = new Color("#000000")
}
}
async function sendGetRequest(endpoint, sid) {
try {
let req = new Request(piholeURL + piholeApiPath + endpoint)
req.headers = { 'accept': 'application/json', "sid": sid};
let json = await req.loadJSON();
return json
} catch {
return null
}
}
async function authenticate() {
try {
let req = new Request(piholeURL + piholeApiPath + "/auth")
req.method = 'POST'
req.body = JSON.stringify({ password: piholeAppPassword });
let json = await req.loadJSON()
if (!json.session?.valid) {
throw new Error("Authentication failed");
}
return json.session?.sid
} catch {
return null
}
}
async function invalidateSession(sid){
try {
let req = new Request(piholeURL + piholeApiPath+ "/auth")
req.method = 'DELETE'
req.headers = { 'accept': 'application/json', "sid": sid};
let json = await req.load()
} catch { }
}
Perfect! Great job! Thank you very much!! Unfortunately, I can't see the status. Do you have an idea to solve the problem?
I have added the version info to @bjarne-dietrich‘s version:
// Parameters:
// {"url":"https://pihole","apikey":"123abc"}
// Optional key in parameters: "theme": system|light|dark, "apiPath": "/api"
let piholeURL = "" //set the URL here for debug
let piholeAppPassword = "" // set the API-key here for debug
let wTheme = "" // set the theme for debug
let piholeApiPath = "/api"
if (config.runsInWidget) {
const widgetParams = (args.widgetParameter != null ? JSON.parse(args.widgetParameter) : null)
if (widgetParams==null) {
throw new Error("Please long press the widget and add the parameters.")
} else if (!widgetParams.hasOwnProperty("url") && !widgetParams.hasOwnProperty("apikey")) {
throw new Error("Wrong parameters.")
}
piholeURL = widgetParams.url
piholeAppPassword = widgetParams.apikey
if (widgetParams.hasOwnProperty("theme")) {
wTheme = widgetParams.theme
}
if (widgetParams.hasOwnProperty("apiPath")) {
piholeApiPath = widgetParams.apiPath
}
}
let wBackground = new LinearGradient()
let wColor = new Color("#ffffff")
setTheme()
let sid = await authenticate();
let piholeStats = await sendGetRequest("/stats/summary", sid);
let dns = await sendGetRequest("/dns/blocking", sid);
let version = await sendGetRequest("/info/version", sid);
let wSize = config.widgetFamily || "small" //set size of widget for debug
let widget = await createWidget() || null
if (!config.runsInWidget) {
if (wSize=="large") { await widget.presentLarge() }
else if (wSize=="medium") { await widget.presentMedium() }
else { await widget.presentSmall() }
}
await invalidateSession(sid);
Script.setWidget(widget)
Script.complete()
async function createWidget() {
let w = new ListWidget()
w.backgroundGradient = wBackground
w.addSpacer()
w.setPadding(5, 15, 0, (wSize=="small" ? 0 : 10))
let state = (piholeStats!=null ? (dns.blocking=="enabled" ? true : false) : null)
let icn = null
if (state==true) {
icn = SFSymbol.named((state ? "checkmark.shield.fill" : "xmark.shield.fill"))
} else {
icn = SFSymbol.named("xmark.circle.fill")
state = false
}
let topStack = w.addStack()
let content = topStack.addImage(icn.image)
content.tintColor = (state ? Color.green() : Color.red())
content.imageSize = new Size(16,16)
topStack.addSpacer(5)
content = topStack.addText("Pi-hole")
content.font = Font.blackSystemFont(16)
content.textColor = wColor
if (state==true && wSize != "small") {
topStack.addSpacer()
topStack.addText(" ") // same line with distance to title
addUpdateItem(topStack, (version.version.core.local.version==version.version.core.remote.version ? true : false), "Pi" + ((wSize=="small") ? "" : "-hole"))
topStack.addText(" ")
addUpdateItem(topStack, (version.version.web.local.version==version.version.web.remote.version ? true : false), "WebUI")
topStack.addText(" ")
addUpdateItem(topStack, (version.version.ftl.local.version==version.version.ftl.remote.version ? true : false), "FTL")
}
w.addSpacer(8)
if (piholeStats==null) {
content = w.addText("No connection")
content.font = Font.thinSystemFont(14)
content.textColor = wColor
w.addSpacer()
return w
}
w.url = piholeURL
addItem(w, "Total Queries", `${piholeStats.queries.total}` )
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Queries Blocked", `${piholeStats.queries.blocked}`)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Percent Blocked", piholeStats.queries.percent_blocked.toFixed(1) + "%")
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
layoutStack.centerAlignContent()
addItem(w, "Domains on Blocklist", `${piholeStats.gravity.domains_being_blocked}`)
if (wSize=="large") {
addItem(w, "Unique Domains", `${piholeStats.queries.unique_domains}`)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Cached Queries", `${piholeStats.queries.cached}`)
layoutStack = w.addStack()
layoutStack.setPadding(5, 0, 0, 10)
addItem(w, "Queries Forwarded", `${piholeStats.queries.cached}`)
}
w.addSpacer()
return w
}
function addItem(w, strHeadline, strData) {
let fontSizeHeadline = 12
let fontSizeString = 9
switch (wSize) {
case "large":
fontSizeHeadline = 18
fontSizeString = 14
break;
case "medium":
fontSizeHeadline = 14
fontSizeString = 11
break;
}
let layoutStack = w.addStack()
layoutStack.setPadding(3, 0, 0, 10)
layoutStack.centerAlignContent()
content = layoutStack.addText(strHeadline)
content.font = Font.mediumSystemFont(fontSizeHeadline)
content.textColor = wColor
layoutStack.addSpacer()
content = layoutStack.addText(strData)
content.font = Font.mediumSystemFont(fontSizeString)
content.textColor = wColor
}
function setTheme() {
if (wTheme=="system") {
if (Device.isUsingDarkAppearance()) {
wTheme = "dark"
} else {
wTheme = "light"
}
}
wBackground.locations = [0, 1]
if (wTheme=="dark") {
wBackground.colors = [
new Color("#384d54"),
new Color("#384d54")
]
wColor = new Color("#ffffff")
} else {
wBackground.colors = [
new Color("#ffffffe6"),
new Color("#ffffffe6")
]
wColor = new Color("#000000")
}
}
async function sendGetRequest(endpoint, sid) {
try {
let req = new Request(piholeURL + piholeApiPath + endpoint)
req.headers = { 'accept': 'application/json', "sid": sid};
let json = await req.loadJSON();
return json
} catch {
return null
}
}
function addUpdateItem(stack, status, text) {
let icn = SFSymbol.named((status ? "checkmark.circle.fill" : "exclamationmark.triangle.fill"))
let content = stack.addImage(icn.image)
content.tintColor = (status ? Color.green() : Color.red())
content.imageSize = new Size(14,14)
content = stack.addText(((wSize!="small") ? " " : "" ) + text)
content.font = Font.semiboldMonospacedSystemFont(12)
content.textColor = wColor
}
async function authenticate() {
try {
let req = new Request(piholeURL + piholeApiPath + "/auth")
req.method = 'POST'
req.body = JSON.stringify({ password: piholeAppPassword });
let json = await req.loadJSON()
if (!json.session?.valid) {
throw new Error("Authentication failed");
}
return json.session?.sid
} catch {
return null
}
}
async function invalidateSession(sid){
try {
let req = new Request(piholeURL + piholeApiPath+ "/auth")
req.method = 'DELETE'
req.headers = { 'accept': 'application/json', "sid": sid};
let json = await req.load()
} catch { }
}
In line 107 I would add + "/admin/" If you then press the widget it forwards nicely to the pihole login screen instead of to the root IP that will give you an error message on the page.
w.url = piholeURL + "/admin/"
Fixed!