iOS-Widgets/Health-Check/Health-Check.js
Benny Samir Hierl c546830443
health check widget
Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
2020-11-28 16:44:18 +01:00

184 lines
5.2 KiB
JavaScript

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-brown; icon-glyph: power-off;
// Version 1.0.0
//////////////////////////////////////////////////////
let fm
try {
fm = FileManager.iCloud()
fm.documentsDirectory()
} catch (e) {
console.log(e)
fm = FileManager.local()
}
const settingsPath = fm.joinPath(fm.documentsDirectory(), 'health-check-settings.json')
if (!fm.fileExists(settingsPath)) {
const examplePath = fm.joinPath(fm.documentsDirectory(), 'health-check-settings.example.json')
fm.writeString(examplePath, JSON.stringify([{
name: 'Server 1',
endpoint: 'https://example.com/api/health',
expectedContentType: 'application/json',
timeoutInterval: 1,
notification: true,
headers: [{key: 'x-customer-header-key',value: 'x-customer-header-value'}]
}], null, 2))
throw new Error('No health-check-settings.json found! Please rename health-check-settings.example.json to health-check-settings.json and add your settings.')
} else {
try {
fm.downloadFileFromiCloud(settingsPath)
} catch(e) {
console.error(e)
}
}
//////////////////////////////////////////////////////
const chooseRandom = (arr, num = 1) => {
const res = [];
for(let i = 0; i < num; ){
const random = Math.floor(Math.random() * arr.length);
if(res.indexOf(arr[random]) !== -1){
continue;
};
res.push(arr[random]);
i++;
};
return res;
};
//////////////////////////////////////////////////////
const check = async ({ endpoint, headers, timeoutInterval }) => {
const req = new Request(endpoint)
req.timeoutInterval = timeoutInterval || 5
if (headers) {
let i
const h = {}
for (i = 0; i < headers.length; i++) {
h[headers[i].key] = headers[i].value
}
req.headers = h
}
let body, httpStatus, contentType
try {
const res = await req.loadString()
if (req.response.headers['Content-Type'] === 'application/json') {
body = JSON.parse(res)
}
httpStatus = req.response.statusCode
contentType = req.response.headers['Content-Type']
} catch (e) {
httpStatus = 504
}
return {
httpStatus,
contentType,
body
}
}
//////////////////////////////////////////////////////
const settings = JSON.parse(fm.readString(settingsPath))
let i
for (i = 0; i < settings.length; i++) {
const { httpStatus, contentType } = await check(settings[i])
if (settings[i].history === undefined) {
settings[i].history = []
}
let status = 'healthy'
if (!/^2/.test(httpStatus)) {
status = 'unhealthy'
}
if (settings[i].contentType && settings[i].expectedContentType !== contentType) {
status = 'unhealthy'
}
settings[i].history.push({
date: new Date().toISOString(),
status
})
settings[i].history = settings[i].history.splice(-40)
if (settings[i].notification && /^2/.test(httpStatus) === false) {
const n = new Notification()
n.body = `🚨 Service '${settings[i].name}' is ${(httpStatus === 504) ? 'slow' : 'unhealthy'}`
n.schedule()
}
}
//////////////////////////////////////////////////////
fm.writeString(settingsPath, JSON.stringify(settings, null, 2))
//////////////////////////////////////////////////////
const widget = new ListWidget()
const titleStack = widget.addStack()
titleStack.layoutHorizontally()
const widgetTitle = titleStack.addText('Health Check')
titleStack.addSpacer()
const globalStatus = settings.filter((e) => {
return e.history[e.history.length - 1].status === 'healthy'
})
const widgetGlobalStatus = titleStack.addText(`${globalStatus.length}/${settings.length} healthy`)
widgetGlobalStatus.font = Font.regularSystemFont(10)
widget.addSpacer(5)
const stack = widget.addStack()
stack.layoutHorizontally()
const leftColumn = stack.addStack()
leftColumn.layoutVertically()
leftColumn.spacing = 5
let rightColumn
if (config.widgetFamily !== 'small' ) {
stack.addSpacer(20)
rightColumn = stack.addStack()
rightColumn.layoutVertically()
rightColumn.spacing = 5
}
const maxView = (config.widgetFamily !== 'large') ? 4 : 8
let toShow
if (settings.length > maxView) {
toShow = chooseRandom(settings, maxView)
} else {
toShow = settings
}
for (i = 0; i < toShow.length; i++) {
const lastCheck = toShow[i].history[toShow[i].history.length - 1]
const labelText = leftColumn.addText(`${lastCheck.status === 'healthy' ? '🟢' : '⚠️'} ${toShow[i].name}`)
labelText.font = Font.regularSystemFont(12)
labelText.lineLimit = 1
if (config.widgetFamily !== 'small' ) {
let j
const field = rightColumn.addStack()
field.layoutHorizontally()
for (j = 0; j < toShow[i].history.length; j++) {
const status = field.addText('|')
status.font = Font.regularSystemFont(12)
status.textColor = (toShow[i].history[j].status === 'healthy') ? Color.green() : Color.red()
}
}
}
widget.addSpacer()
const widgetUpdate = widget.addText(`Last Check: ${new Date().toLocaleString()}`)
widgetUpdate.font = Font.regularSystemFont(8)
widgetUpdate.textColor = Color.lightGray()
widgetUpdate.centerAlignText()
if (!config.runsInWidget) {
await widget.presentLarge()
} else {
// Tell the system to show the widget.
Script.setWidget(widget)
Script.complete()
}