Last active
October 31, 2023 15:20
-
-
Save malesfth/38885be4617f61913af74a695be82b07 to your computer and use it in GitHub Desktop.
iOS Scriptable Widget for Portainer (Docker)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Parameters: | |
// {"url":"https://portainer:9000","user":"username","pass":"secret"} | |
// Optional key in parameters: "endpoint": 1|2|... | |
// Optional key in parameters: "theme": system|light|dark|docker | |
// Optional key in parameters: "name": "Portainer|Docker" | |
let portainerUser = "" // set the username here for debug | |
let portainerPass = "" // set the password here for debug | |
let portainerURL = "" //set the URL here for debug | |
let portainerEndpoint = "1" //set the endpoint id for debug | |
let wTheme = "system" // set the theme for debug | |
let wName = "Portainer" | |
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("username") && !widgetParams.hasOwnProperty("pass")) { | |
throw new Error("Wrong parameters.") | |
} | |
portainerURL = widgetParams.url | |
portainerUser = widgetParams.user | |
portainerPass = widgetParams.pass | |
if (widgetParams.hasOwnProperty("endpoint")) { | |
portainerEndpoint = widgetParams.endpoint | |
} | |
if (widgetParams.hasOwnProperty("theme")) { | |
wTheme = widgetParams.theme | |
} | |
if (widgetParams.hasOwnProperty("name")) { | |
if (widgetParams.name=="Docker") { | |
wName = widgetParams.name | |
} | |
} | |
} | |
let wBackground = new LinearGradient() | |
let wColor = new Color("#ffffff") | |
setTheme() | |
let portainerStats = await getStats() | |
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() } | |
} | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget() { | |
let content = null | |
let w = new ListWidget() | |
w.backgroundGradient = wBackground | |
w.addSpacer() | |
if (portainerStats==null) { | |
w.setPadding(5, 15, 0, 0) | |
content = w.addText(wName) | |
content.font = Font.blackSystemFont(16) | |
content.textColor = wColor | |
w.addSpacer(3) | |
content = w.addText("No connection") | |
content.font = Font.thinSystemFont(14) | |
content.textColor = wColor | |
w.addSpacer() | |
return w | |
} | |
w.url = portainerURL | |
let iRunning = 0 | |
let iNotRunning = 0 | |
let iContainers = portainerStats.length | |
for(var i = 0; i < portainerStats.length; i++) { | |
if (portainerStats[i].State=="running") { | |
iRunning = iRunning + 1 | |
} else { | |
iNotRunning = iNotRunning + 1 | |
} | |
} | |
if (wSize=="large" || wSize=="medium") { | |
let maxRows = (wSize=="large" ? 8 : 4) | |
let arrShow = [] | |
if (iContainers>maxRows) { | |
for (var i = 0; i < maxRows; i++) { | |
arrShow = getRandom(arrShow) | |
} | |
} else { | |
for (var i = 0; i < iContainers; i++) { | |
arrShow.push(i) | |
} | |
} | |
let topStack = w.addStack() | |
topStack.setPadding(15,0,2,0) | |
let topStr = topStack.addText(wName) | |
topStr.font = Font.blackSystemFont(16) | |
topStr.textColor = wColor | |
topStack.addSpacer() | |
topStr = topStack.addText(iRunning +" / "+ iContainers + " container running") | |
topStr.font = Font.lightSystemFont(9) | |
topStr.textColor = wColor | |
w.addSpacer(5) | |
let layoutStack = w.addStack() | |
layoutStack.layoutVertically() | |
for(var i = 0; i < arrShow.length; i++) { | |
let cntnr = portainerStats[arrShow[i]] | |
let state = (cntnr.State=="running" ? true : false) | |
let icn = SFSymbol.named((state ? "circle.fill" : "circle")) | |
let row = layoutStack.addStack() | |
row.setPadding(5,0,0,0) | |
content = row.addImage(icn.image) | |
content.tintColor = (state ? Color.green() : Color.red()) | |
content.imageSize = new Size(16,16) | |
row.addSpacer(20) | |
content = row.addText((cntnr.Names[0]).replace("/","")) | |
content.font = Font.boldMonospacedSystemFont(14) | |
content.textColor = wColor | |
row.addSpacer() | |
let txt = (cntnr.Status).replace(" (healthy)", "") | |
content = row.addText(txt) | |
content.font = Font.thinSystemFont(16) | |
content.textColor = wColor | |
} | |
layoutStack.addSpacer() | |
} else { | |
let rowStack = w.addStack() | |
rowStack.layoutVertically() | |
rowStack.topAlignContent() | |
let innerStack = rowStack.addStack() | |
let txt = innerStack.addText(wName) | |
txt.font = Font.blackSystemFont(16) | |
txt.textColor = wColor | |
innerStack.addSpacer() | |
innerStack = rowStack.addStack() | |
txt = innerStack.addText("—") | |
txt.font = Font.thinSystemFont(16) | |
txt.textColor = wColor | |
innerStack.addSpacer(3) | |
innerStack = rowStack.addStack() | |
txt = innerStack.addText(iRunning +" / "+ iContainers) | |
txt.font = Font.blackSystemFont(16) | |
txt.textColor = wColor | |
innerStack.addSpacer(5) | |
txt = innerStack.addText("running") | |
txt.font = Font.italicSystemFont(16) | |
txt.textColor = wColor | |
innerStack.addSpacer(3) | |
} | |
w.addSpacer() | |
return w | |
} | |
function setTheme() { | |
if (wTheme=="system") { | |
if (Device.isUsingDarkAppearance()) { | |
wTheme = "dark" | |
} else { | |
wTheme = "light" | |
} | |
} | |
wBackground.locations = [0, 1] | |
if (wTheme=="docker") { | |
wBackground.colors = [ | |
new Color("#0db7ede6"), | |
new Color("#0db7ed") | |
] | |
wColor = new Color("#ffffff") | |
} else 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") | |
} | |
} | |
function getRandom(arr) { | |
let count = portainerStats.lenght | |
let rand = Math.floor(Math.random()*portainerStats.length) | |
if (arr.indexOf(rand)==-1) { | |
arr.push(rand) | |
} else { | |
return getRandom(arr) | |
} | |
return arr | |
} | |
async function getStats() { | |
try { | |
let url = portainerURL | |
let req = new Request(url+"/api/auth") | |
req.allowInsecureRequest = true | |
req.method = "POST" | |
req.headers = {"Accept":"application/json"} | |
req.body = JSON.stringify({"Username":portainerUser,"Password":portainerPass}) | |
let json = await req.loadJSON() | |
let bearer = json.jwt || null | |
if (bearer!=null){ | |
req = new Request(url+"/api/endpoints/"+portainerEndpoint+"/docker/containers/json") | |
req.allowInsecureRequest = true | |
req.method = "GET" | |
req.headers = {"Authorization":"Bearer "+bearer} | |
json = await req.loadJSON() | |
return json | |
} else { | |
throw new Error("Error getting Bearer Token.") | |
} | |
} catch { | |
return null | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Because of the insecure connection. It is fixed now within the new version.