initial commit
This commit is contained in:
commit
fe58b5a874
10 changed files with 1149 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
services.json
|
||||||
|
config.json
|
||||||
|
*.xml
|
||||||
|
*.pyc
|
||||||
|
.venv
|
||||||
|
*.swp
|
138
hosted.js
Normal file
138
hosted.js
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* info-beamer hosted.js Mockup for local development.
|
||||||
|
* You can find the latest version of this file at:
|
||||||
|
*
|
||||||
|
* https://github.com/info-beamer/package-sdk
|
||||||
|
*
|
||||||
|
* BSD 2-Clause License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017-2019 Florian Wesch <fw@info-beamer.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var head = document.getElementsByTagName("head")[0];
|
||||||
|
var asset_root = "https://cdn.infobeamer.com/s/mock-use-latest/";
|
||||||
|
|
||||||
|
function setupResources(js, css) {
|
||||||
|
for (var idx = 0; idx < js.length; idx++) {
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.setAttribute("type","text/javascript");
|
||||||
|
script.setAttribute("src", asset_root + 'js/' + js[idx]);
|
||||||
|
head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var idx = css.length-1; idx >= 0; idx--) {
|
||||||
|
var link = document.createElement('link')
|
||||||
|
link.setAttribute('rel', 'stylesheet')
|
||||||
|
link.setAttribute('type', 'text/css')
|
||||||
|
link.setAttribute('href', asset_root + 'css/' + css[idx])
|
||||||
|
head.insertBefore(link, head.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var style = document.createElement('style');
|
||||||
|
var rules = document.createTextNode(
|
||||||
|
"body { width: 750px; margin: auto; }"
|
||||||
|
)
|
||||||
|
style.type = 'text/css';
|
||||||
|
style.appendChild(rules);
|
||||||
|
head.appendChild(style);
|
||||||
|
|
||||||
|
if (window.MOCK_ASSETS == undefined)
|
||||||
|
console.error("[MOCK HOSTED.JS] window.MOCK_ASSETS undefined");
|
||||||
|
if (window.MOCK_NODE_ASSETS == undefined)
|
||||||
|
console.error("[MOCK HOSTED.JS] window.MOCK_NODE_ASSETS undefined");
|
||||||
|
if (window.MOCK_DEVICES == undefined)
|
||||||
|
console.error("[MOCK HOSTED.JS] window.MOCK_DEVICES undefined");
|
||||||
|
if (window.MOCK_CONFIG == undefined)
|
||||||
|
console.error("[MOCK HOSTED.JS] window.MOCK_CONFIG undefined");
|
||||||
|
|
||||||
|
var ib = {
|
||||||
|
assets: window.MOCK_ASSETS,
|
||||||
|
node_assets: window.MOCK_NODE_ASSETS,
|
||||||
|
config: window.MOCK_CONFIG,
|
||||||
|
devices: window.MOCK_DEVICES,
|
||||||
|
doc_link_base: 'data:text/plain,This would have opened the package documentation for ',
|
||||||
|
apis: {
|
||||||
|
geo: {
|
||||||
|
get: function(params) {
|
||||||
|
if (!params.q) {
|
||||||
|
console.error("no q parameter for weather query");
|
||||||
|
}
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
setTimeout(function() { // simulate latency
|
||||||
|
resolve({"hits":[
|
||||||
|
{"lat":49.00937,"lon":8.40444,"name":"Karlsruhe (Baden-W\u00fcrttemberg, Germany)"},
|
||||||
|
{"lat":48.09001,"lon":-100.62042,"name":"Karlsruhe (North Dakota, United States)"}
|
||||||
|
]})
|
||||||
|
}, 800);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ib.setDefaultStyle = function() {
|
||||||
|
setupResources([], ['reset.css', 'bootstrap.css'])
|
||||||
|
}
|
||||||
|
|
||||||
|
var asset_chooser_response = window.MOCK_ASSET_CHOOSER_RESPONSE
|
||||||
|
if (asset_chooser_response) {
|
||||||
|
console.log("[MOCK HOSTED.JS] emulating asset chooser");
|
||||||
|
ib.assetChooser = function() {
|
||||||
|
console.log("[MOCK HOSTED.JS] asset chooser mockup returns", asset_chooser_response);
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
resolve(asset_chooser_response);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ib.setConfig = function(config) {
|
||||||
|
var as_string = JSON.stringify(config);
|
||||||
|
ib.config = JSON.parse(as_string);
|
||||||
|
console.log("[MOCK HOSTED.JS] setConfig", as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
ib.getConfig = function(cb) {
|
||||||
|
console.warn("[MOCK HOSTED.JS] using .getConfig is deprecated. Use .ready.then(...) instead");
|
||||||
|
cb(ib.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
ib.getDocLink = function(name) {
|
||||||
|
return ib.doc_link_base + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
ib.onAssetUpdate = function(cb) {
|
||||||
|
console.warn("[MOCK HOSTED.JS] onAssetUpdate is a no-op in the mock environment");
|
||||||
|
}
|
||||||
|
|
||||||
|
ib.ready = new Promise(function(resolve) {
|
||||||
|
console.log("[MOCK HOSTED.JS] ready");
|
||||||
|
resolve(ib.config);
|
||||||
|
})
|
||||||
|
|
||||||
|
window.infobeamer = window.ib = ib;
|
||||||
|
})();
|
231
hosted.lua
Normal file
231
hosted.lua
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
--[[
|
||||||
|
|
||||||
|
Part of info-beamer hosted. You can find the latest version
|
||||||
|
of this file at:
|
||||||
|
|
||||||
|
https://github.com/info-beamer/package-sdk
|
||||||
|
|
||||||
|
Copyright (c) 2014,2015,2016,2017 Florian Wesch <fw@dividuum.de>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local resource_types = {
|
||||||
|
["image"] = function(value)
|
||||||
|
local surface
|
||||||
|
local image = {
|
||||||
|
asset_name = value.asset_name,
|
||||||
|
filename = value.filename,
|
||||||
|
type = value.type,
|
||||||
|
}
|
||||||
|
|
||||||
|
function image.ensure_loaded()
|
||||||
|
if not surface then
|
||||||
|
surface = resource.load_image(value.asset_name)
|
||||||
|
end
|
||||||
|
return surface
|
||||||
|
end
|
||||||
|
function image.load()
|
||||||
|
image.ensure_loaded()
|
||||||
|
local state = surface:state()
|
||||||
|
return state ~= "loading"
|
||||||
|
end
|
||||||
|
function image.get_surface()
|
||||||
|
return image.ensure_loaded()
|
||||||
|
end
|
||||||
|
function image.draw(...)
|
||||||
|
image.ensure_loaded():draw(...)
|
||||||
|
end
|
||||||
|
function image.unload()
|
||||||
|
if surface then
|
||||||
|
surface:dispose()
|
||||||
|
surface = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function image.get_config()
|
||||||
|
return image
|
||||||
|
end
|
||||||
|
return image
|
||||||
|
end;
|
||||||
|
["video"] = function(value)
|
||||||
|
local surface
|
||||||
|
local video = {
|
||||||
|
asset_name = value.asset_name,
|
||||||
|
filename = value.filename,
|
||||||
|
type = value.type,
|
||||||
|
}
|
||||||
|
function video.ensure_loaded(opt)
|
||||||
|
if not surface then
|
||||||
|
surface = util.videoplayer(value.asset_name, opt)
|
||||||
|
end
|
||||||
|
return surface
|
||||||
|
end
|
||||||
|
function video.load(opt)
|
||||||
|
video.ensure_loaded(opt)
|
||||||
|
local state = surface:state()
|
||||||
|
return state ~= "loading"
|
||||||
|
end
|
||||||
|
function video.get_surface(opt)
|
||||||
|
return video.ensure_loaded(opt)
|
||||||
|
end
|
||||||
|
function video.draw(...)
|
||||||
|
video.ensure_loaded():draw(...)
|
||||||
|
end
|
||||||
|
function video.unload()
|
||||||
|
if surface then
|
||||||
|
surface:dispose()
|
||||||
|
surface = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function video.get_config()
|
||||||
|
return video
|
||||||
|
end
|
||||||
|
return video
|
||||||
|
end;
|
||||||
|
["child"] = function(value)
|
||||||
|
local surface
|
||||||
|
local child = {
|
||||||
|
asset_name = value.asset_name,
|
||||||
|
filename = value.filename,
|
||||||
|
type = value.type,
|
||||||
|
}
|
||||||
|
function child.ensure_loaded()
|
||||||
|
if surface then
|
||||||
|
surface:dispose()
|
||||||
|
end
|
||||||
|
surface = resource.render_child(value.asset_name)
|
||||||
|
return surface
|
||||||
|
end
|
||||||
|
function child.load()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
function child.get_surface()
|
||||||
|
return child.ensure_loaded()
|
||||||
|
end
|
||||||
|
function child.draw(...)
|
||||||
|
child.ensure_loaded():draw(...)
|
||||||
|
end
|
||||||
|
function child.unload()
|
||||||
|
if surface then
|
||||||
|
surface:dispose()
|
||||||
|
surface = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function child.get_config()
|
||||||
|
return child
|
||||||
|
end
|
||||||
|
return child
|
||||||
|
end;
|
||||||
|
["json"] = function(value)
|
||||||
|
return require("json").decode(value)
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
|
||||||
|
local types = {
|
||||||
|
["date"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["json"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["text"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["string"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["integer"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["select"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["device"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["boolean"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["duration"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["custom"] = function(value)
|
||||||
|
return value
|
||||||
|
end;
|
||||||
|
["color"] = function(value)
|
||||||
|
local color = {}
|
||||||
|
color.r = value.r
|
||||||
|
color.g = value.g
|
||||||
|
color.b = value.b
|
||||||
|
color.a = value.a
|
||||||
|
color.rgba_table = {color.r, color.g, color.b, color.a}
|
||||||
|
color.rgba = function()
|
||||||
|
return color.r, color.g, color.b, color.a
|
||||||
|
end
|
||||||
|
color.rgb_with_a = function(a)
|
||||||
|
return color.r, color.g, color.b, a
|
||||||
|
end
|
||||||
|
color.clear = function()
|
||||||
|
gl.clear(color.r, color.g, color.b, color.a)
|
||||||
|
end
|
||||||
|
return color
|
||||||
|
end;
|
||||||
|
["resource"] = function(value)
|
||||||
|
return resource_types[value.type](value)
|
||||||
|
end;
|
||||||
|
["font"] = function(value)
|
||||||
|
return resource.load_font(value.asset_name)
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
|
||||||
|
local function parse_config(options, config)
|
||||||
|
local function parse_recursive(options, config, target)
|
||||||
|
for _, option in ipairs(options) do
|
||||||
|
local name = option.name
|
||||||
|
if name then
|
||||||
|
if option.type == "list" then
|
||||||
|
local list = {}
|
||||||
|
for _, child_config in ipairs(config[name]) do
|
||||||
|
local child = {}
|
||||||
|
parse_recursive(option.items, child_config, child)
|
||||||
|
list[#list + 1] = child
|
||||||
|
end
|
||||||
|
target[name] = list
|
||||||
|
else
|
||||||
|
target[name] = types[option.type](config[name])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local current_config = {}
|
||||||
|
parse_recursive(options, config, current_config)
|
||||||
|
return current_config
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
parse_config = parse_config;
|
||||||
|
}
|
553
hosted.py
Normal file
553
hosted.py
Normal file
|
@ -0,0 +1,553 @@
|
||||||
|
#
|
||||||
|
# Part of info-beamer hosted. You can find the latest version
|
||||||
|
# of this file at:
|
||||||
|
#
|
||||||
|
# https://github.com/info-beamer/package-sdk
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014,2015,2016,2017,2018 Florian Wesch <fw@info-beamer.com>
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the
|
||||||
|
# distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
VERSION = "1.3"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import errno
|
||||||
|
import socket
|
||||||
|
import select
|
||||||
|
import pyinotify
|
||||||
|
import thread
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
types = {}
|
||||||
|
|
||||||
|
def init_types():
|
||||||
|
def type(fn):
|
||||||
|
types[fn.__name__] = fn
|
||||||
|
return fn
|
||||||
|
|
||||||
|
@type
|
||||||
|
def color(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def string(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def text(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def section(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def boolean(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def select(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def duration(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def integer(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def float(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def font(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def device(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def resource(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def json(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def custom(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@type
|
||||||
|
def date(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
init_types()
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print >>sys.stderr, "[hosted.py] %s" % msg
|
||||||
|
|
||||||
|
def abort_service(reason):
|
||||||
|
log("restarting service (%s)" % reason)
|
||||||
|
try:
|
||||||
|
thread.interrupt_main()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
time.sleep(2)
|
||||||
|
os.kill(os.getpid(), 2)
|
||||||
|
time.sleep(2)
|
||||||
|
os.kill(os.getpid(), 15)
|
||||||
|
time.sleep(2)
|
||||||
|
os.kill(os.getpid(), 9)
|
||||||
|
time.sleep(100)
|
||||||
|
|
||||||
|
class Configuration(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._restart = False
|
||||||
|
self._options = []
|
||||||
|
self._config = {}
|
||||||
|
self._parsed = {}
|
||||||
|
self.parse_node_json(do_update=False)
|
||||||
|
self.parse_config_json()
|
||||||
|
|
||||||
|
def restart_on_update(self):
|
||||||
|
log("going to restart when config is updated")
|
||||||
|
self._restart = True
|
||||||
|
|
||||||
|
def parse_node_json(self, do_update=True):
|
||||||
|
with open("node.json") as f:
|
||||||
|
self._options = json.load(f).get('options', [])
|
||||||
|
if do_update:
|
||||||
|
self.update_config()
|
||||||
|
|
||||||
|
def parse_config_json(self, do_update=True):
|
||||||
|
with open("config.json") as f:
|
||||||
|
self._config = json.load(f)
|
||||||
|
if do_update:
|
||||||
|
self.update_config()
|
||||||
|
|
||||||
|
def update_config(self):
|
||||||
|
if self._restart:
|
||||||
|
return abort_service("restart_on_update set")
|
||||||
|
|
||||||
|
def parse_recursive(options, config, target):
|
||||||
|
# print 'parsing', config
|
||||||
|
for option in options:
|
||||||
|
if not 'name' in option:
|
||||||
|
continue
|
||||||
|
if option['type'] == 'list':
|
||||||
|
items = []
|
||||||
|
for item in config[option['name']]:
|
||||||
|
parsed = {}
|
||||||
|
parse_recursive(option['items'], item, parsed)
|
||||||
|
items.append(parsed)
|
||||||
|
target[option['name']] = items
|
||||||
|
continue
|
||||||
|
target[option['name']] = types[option['type']](config[option['name']])
|
||||||
|
|
||||||
|
parsed = {}
|
||||||
|
parse_recursive(self._options, self._config, parsed)
|
||||||
|
log("updated config")
|
||||||
|
self._parsed = parsed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw(self):
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def metadata(self):
|
||||||
|
return self._config['__metadata']
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._parsed[key]
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return self._parsed[key]
|
||||||
|
|
||||||
|
def setup_inotify(configuration):
|
||||||
|
class EventHandler(pyinotify.ProcessEvent):
|
||||||
|
def process_default(self, event):
|
||||||
|
basename = os.path.basename(event.pathname)
|
||||||
|
if basename == 'node.json':
|
||||||
|
log("node.json changed")
|
||||||
|
configuration.parse_node_json()
|
||||||
|
elif basename == 'config.json':
|
||||||
|
log("config.json changed!")
|
||||||
|
configuration.parse_config_json()
|
||||||
|
elif basename.endswith('.py'):
|
||||||
|
abort_service("python file changed")
|
||||||
|
|
||||||
|
wm = pyinotify.WatchManager()
|
||||||
|
|
||||||
|
notifier = pyinotify.ThreadedNotifier(wm, EventHandler())
|
||||||
|
notifier.daemon = True
|
||||||
|
notifier.start()
|
||||||
|
|
||||||
|
wm.add_watch('.', pyinotify.IN_MOVED_TO)
|
||||||
|
|
||||||
|
class Node(object):
|
||||||
|
def __init__(self, node):
|
||||||
|
self._node = node
|
||||||
|
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
def send_raw(self, raw):
|
||||||
|
log("sending %r" % (raw,))
|
||||||
|
self._sock.sendto(raw, ('127.0.0.1', 4444))
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
self.send_raw(self._node + data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_top_level(self):
|
||||||
|
return self._node == "root"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self._node
|
||||||
|
|
||||||
|
def write_file(self, filename, content):
|
||||||
|
f = NamedTemporaryFile(prefix='.hosted-py-tmp', dir=os.getcwd())
|
||||||
|
try:
|
||||||
|
f.write(content)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
f.close()
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
f.delete = False
|
||||||
|
f.close()
|
||||||
|
os.rename(f.name, filename)
|
||||||
|
|
||||||
|
def write_json(self, filename, data):
|
||||||
|
self.write_file(filename, json.dumps(
|
||||||
|
data,
|
||||||
|
ensure_ascii=False,
|
||||||
|
separators=(',',':'),
|
||||||
|
).encode('utf8'))
|
||||||
|
|
||||||
|
class Sender(object):
|
||||||
|
def __init__(self, node, path):
|
||||||
|
self._node = node
|
||||||
|
self._path = path
|
||||||
|
|
||||||
|
def __call__(self, data):
|
||||||
|
if isinstance(data, (dict, list)):
|
||||||
|
raw = "%s:%s" % (self._path, json.dumps(
|
||||||
|
data,
|
||||||
|
ensure_ascii=False,
|
||||||
|
separators=(',',':'),
|
||||||
|
).encode('utf8'))
|
||||||
|
else:
|
||||||
|
raw = "%s:%s" % (self._path, data)
|
||||||
|
self._node.send_raw(raw)
|
||||||
|
|
||||||
|
def __getitem__(self, path):
|
||||||
|
return self.Sender(self, self._node + path)
|
||||||
|
|
||||||
|
def __call__(self, data):
|
||||||
|
return self.Sender(self, self._node)(data)
|
||||||
|
|
||||||
|
def scratch_cached(self, filename, generator):
|
||||||
|
cached = os.path.join(os.environ['SCRATCH'], filename)
|
||||||
|
|
||||||
|
if not os.path.exists(cached):
|
||||||
|
f = NamedTemporaryFile(prefix='scratch-cached-tmp', dir=os.environ['SCRATCH'])
|
||||||
|
try:
|
||||||
|
generator(f)
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
f.delete = False
|
||||||
|
f.close()
|
||||||
|
os.rename(f.name, cached)
|
||||||
|
|
||||||
|
if os.path.exists(filename):
|
||||||
|
try:
|
||||||
|
os.unlink(filename)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
os.symlink(cached, filename)
|
||||||
|
|
||||||
|
class APIError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class APIProxy(object):
|
||||||
|
def __init__(self, apis, api_name):
|
||||||
|
self._apis = apis
|
||||||
|
self._api_name = api_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
index = self._apis.get_api_index()
|
||||||
|
if not self._api_name in index:
|
||||||
|
raise APIError("api '%s' not available" % (self._api_name,))
|
||||||
|
return index[self._api_name]['url']
|
||||||
|
|
||||||
|
def unwrap(self, r):
|
||||||
|
r.raise_for_status()
|
||||||
|
if r.status_code == 304:
|
||||||
|
return None
|
||||||
|
if r.headers['content-type'] == 'application/json':
|
||||||
|
resp = r.json()
|
||||||
|
if not resp['ok']:
|
||||||
|
raise APIError(u"api call failed: %s" % (
|
||||||
|
resp.get('error', '<unknown error>'),
|
||||||
|
))
|
||||||
|
return resp.get(self._api_name)
|
||||||
|
else:
|
||||||
|
return r.content
|
||||||
|
|
||||||
|
def add_defaults(self, kwargs):
|
||||||
|
if not 'timeout' in kwargs:
|
||||||
|
kwargs['timeout'] = 10
|
||||||
|
|
||||||
|
def get(self, **kwargs):
|
||||||
|
self.add_defaults(kwargs)
|
||||||
|
try:
|
||||||
|
return self.unwrap(self._apis.session.get(
|
||||||
|
url = self.url,
|
||||||
|
**kwargs
|
||||||
|
))
|
||||||
|
except APIError:
|
||||||
|
raise
|
||||||
|
except Exception as err:
|
||||||
|
raise APIError(err)
|
||||||
|
|
||||||
|
def post(self, **kwargs):
|
||||||
|
self.add_defaults(kwargs)
|
||||||
|
try:
|
||||||
|
return self.unwrap(self._apis.session.post(
|
||||||
|
url = self.url,
|
||||||
|
**kwargs
|
||||||
|
))
|
||||||
|
except APIError:
|
||||||
|
raise
|
||||||
|
except Exception as err:
|
||||||
|
raise APIError(err)
|
||||||
|
|
||||||
|
class APIs(object):
|
||||||
|
def __init__(self, config):
|
||||||
|
self._config = config
|
||||||
|
self._index = None
|
||||||
|
self._valid_until = 0
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._session = requests.Session()
|
||||||
|
self._session.headers.update({
|
||||||
|
'User-Agent': 'hosted.py version/%s' % (VERSION,)
|
||||||
|
})
|
||||||
|
|
||||||
|
def update_apis(self):
|
||||||
|
log("fetching api index")
|
||||||
|
r = self._session.get(
|
||||||
|
url = self._config.metadata['api'],
|
||||||
|
timeout = 5,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
resp = r.json()
|
||||||
|
if not resp['ok']:
|
||||||
|
raise APIError("cannot retrieve api index")
|
||||||
|
self._index = resp['apis']
|
||||||
|
self._valid_until = resp['valid_until'] - 300
|
||||||
|
|
||||||
|
def get_api_index(self):
|
||||||
|
with self._lock:
|
||||||
|
now = time.time()
|
||||||
|
if now > self._valid_until:
|
||||||
|
self.update_apis()
|
||||||
|
return self._index
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session(self):
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
try:
|
||||||
|
index = self.get_api_index()
|
||||||
|
return sorted(index.keys())
|
||||||
|
except Exception as err:
|
||||||
|
raise APIError(err)
|
||||||
|
|
||||||
|
def __getitem__(self, api_name):
|
||||||
|
return APIProxy(self, api_name)
|
||||||
|
|
||||||
|
def __getattr__(self, api_name):
|
||||||
|
return APIProxy(self, api_name)
|
||||||
|
|
||||||
|
class GPIO(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._pin_fd = {}
|
||||||
|
self._state = {}
|
||||||
|
self._fd_2_pin = {}
|
||||||
|
self._poll = select.poll()
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def setup_pin(self, pin, direction="in", invert=False):
|
||||||
|
if not os.path.exists("/sys/class/gpio/gpio%d" % pin):
|
||||||
|
with open("/sys/class/gpio/export", "wb") as f:
|
||||||
|
f.write(str(pin))
|
||||||
|
# mdev is giving the newly create GPIO directory correct permissions.
|
||||||
|
for i in range(10):
|
||||||
|
try:
|
||||||
|
with open("/sys/class/gpio/gpio%d/active_low" % pin, "wb") as f:
|
||||||
|
f.write("1" if invert else "0")
|
||||||
|
break
|
||||||
|
except IOError as err:
|
||||||
|
if err.errno != errno.EACCES:
|
||||||
|
raise
|
||||||
|
time.sleep(0.1)
|
||||||
|
log("waiting for GPIO permissions")
|
||||||
|
else:
|
||||||
|
raise IOError(errno.EACCES, "Cannot access GPIO")
|
||||||
|
with open("/sys/class/gpio/gpio%d/direction" % pin, "wb") as f:
|
||||||
|
f.write(direction)
|
||||||
|
|
||||||
|
def set_pin_value(self, pin, high):
|
||||||
|
with open("/sys/class/gpio/gpio%d/value" % pin, "wb") as f:
|
||||||
|
f.write("1" if high else "0")
|
||||||
|
|
||||||
|
def monitor(self, pin, invert=False):
|
||||||
|
if pin in self._pin_fd:
|
||||||
|
return
|
||||||
|
self.setup_pin(pin, direction="in", invert=invert)
|
||||||
|
with open("/sys/class/gpio/gpio%d/edge" % pin, "wb") as f:
|
||||||
|
f.write("both")
|
||||||
|
fd = os.open("/sys/class/gpio/gpio%d/value" % pin, os.O_RDONLY)
|
||||||
|
self._state[pin] = bool(int(os.read(fd, 5)))
|
||||||
|
self._fd_2_pin[fd] = pin
|
||||||
|
self._pin_fd[pin] = fd
|
||||||
|
self._poll.register(fd, select.POLLPRI | select.POLLERR)
|
||||||
|
|
||||||
|
def poll(self, timeout=1000):
|
||||||
|
changes = []
|
||||||
|
for fd, evt in self._poll.poll(timeout):
|
||||||
|
os.lseek(fd, 0, 0)
|
||||||
|
state = bool(int(os.read(fd, 5)))
|
||||||
|
pin = self._fd_2_pin[fd]
|
||||||
|
with self._lock:
|
||||||
|
prev_state, self._state[pin] = self._state[pin], state
|
||||||
|
if state != prev_state:
|
||||||
|
changes.append((pin, state))
|
||||||
|
return changes
|
||||||
|
|
||||||
|
def poll_forever(self):
|
||||||
|
while 1:
|
||||||
|
for event in self.poll():
|
||||||
|
yield event
|
||||||
|
|
||||||
|
def on(self, pin):
|
||||||
|
with self._lock:
|
||||||
|
return self._state.get(pin, False)
|
||||||
|
|
||||||
|
class Device(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._socket = None
|
||||||
|
self._gpio = GPIO()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gpio(self):
|
||||||
|
return self._gpio
|
||||||
|
|
||||||
|
@property
|
||||||
|
def screen_resolution(self):
|
||||||
|
with open("/sys/class/graphics/fb0/virtual_size", "rb") as f:
|
||||||
|
return [int(val) for val in f.read().strip().split(',')]
|
||||||
|
|
||||||
|
def ensure_connected(self):
|
||||||
|
if self._socket:
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
log("establishing upstream connection")
|
||||||
|
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
self._socket.connect(os.getenv('SYNCER_SOCKET', "/tmp/syncer"))
|
||||||
|
return True
|
||||||
|
except Exception as err:
|
||||||
|
log("cannot connect to upstream socket: %s" % (err,))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_raw(self, raw):
|
||||||
|
try:
|
||||||
|
if self.ensure_connected():
|
||||||
|
self._socket.send(raw + '\n')
|
||||||
|
except Exception as err:
|
||||||
|
log("cannot send to upstream: %s" % (err,))
|
||||||
|
if self._socket:
|
||||||
|
self._socket.close()
|
||||||
|
self._socket = None
|
||||||
|
|
||||||
|
def send_upstream(self, **data):
|
||||||
|
self.send_raw(json.dumps(data))
|
||||||
|
|
||||||
|
def turn_screen_off(self):
|
||||||
|
self.send_raw("tv off")
|
||||||
|
|
||||||
|
def turn_screen_on(self):
|
||||||
|
self.send_raw("tv on")
|
||||||
|
|
||||||
|
def screen(self, on=True):
|
||||||
|
if on:
|
||||||
|
self.turn_screen_on()
|
||||||
|
else:
|
||||||
|
self.turn_screen_off()
|
||||||
|
|
||||||
|
def reboot(self):
|
||||||
|
self.send_raw("system reboot")
|
||||||
|
|
||||||
|
def halt_until_powercycled(self):
|
||||||
|
self.send_raw("system halt")
|
||||||
|
|
||||||
|
def restart_infobeamer(self):
|
||||||
|
self.send_raw("infobeamer restart")
|
||||||
|
|
||||||
|
def verify_cache(self):
|
||||||
|
self.send_raw("syncer verify_cache")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
device = Device()
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
command = raw_input("syncer> ")
|
||||||
|
device.send_raw(command)
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
else:
|
||||||
|
log("starting version %s" % (VERSION,))
|
||||||
|
node = NODE = Node(os.environ['NODE'])
|
||||||
|
device = DEVICE = Device()
|
||||||
|
config = CONFIG = Configuration()
|
||||||
|
api = API = APIs(CONFIG)
|
||||||
|
setup_inotify(CONFIG)
|
||||||
|
log("ready to go!")
|
45
node.json
Normal file
45
node.json
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "Icinga2 Statusmonitor",
|
||||||
|
"permissions": {
|
||||||
|
"network": "Needs to connect to icinga2 api"
|
||||||
|
},
|
||||||
|
"options": [{
|
||||||
|
"title": "Background color",
|
||||||
|
"ui_width": 4,
|
||||||
|
"name": "background_color",
|
||||||
|
"type": "color",
|
||||||
|
"default": [0,0,0,1]
|
||||||
|
}, {
|
||||||
|
"title": "Font",
|
||||||
|
"ui_width": 8,
|
||||||
|
"name": "font",
|
||||||
|
"type": "font",
|
||||||
|
"default": "silkscreen.ttf"
|
||||||
|
}, {
|
||||||
|
"title": "icinga2 API user",
|
||||||
|
"ui_width": 6,
|
||||||
|
"name": "api_user",
|
||||||
|
"type": "string",
|
||||||
|
"default": "readonly"
|
||||||
|
}, {
|
||||||
|
"title": "icinga2 API password",
|
||||||
|
"ui_width": 6,
|
||||||
|
"name": "api_password",
|
||||||
|
"type": "string",
|
||||||
|
"default": "really_secure"
|
||||||
|
}, {
|
||||||
|
"title": "icinga2 API URL for hosts",
|
||||||
|
"ui_width": 12,
|
||||||
|
"name": "url_hosts",
|
||||||
|
"type": "string",
|
||||||
|
"default": "https://icinga2/api/v1/objects/hosts",
|
||||||
|
"hint": "Full URL to the API endpoint which returns a list of monitored hosts"
|
||||||
|
}, {
|
||||||
|
"title": "icinga2 API URL for services",
|
||||||
|
"ui_width": 12,
|
||||||
|
"name": "url_services",
|
||||||
|
"type": "string",
|
||||||
|
"default": "https://icinga2/api/v1/objects/services?filter=service.state!=ServiceOK",
|
||||||
|
"hint": "Full URL to the API endpoint which returns a list of monitored services. Keeping the filter is strongly recommended!"
|
||||||
|
}]
|
||||||
|
}
|
69
node.lua
Normal file
69
node.lua
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
util.init_hosted()
|
||||||
|
|
||||||
|
local json = require "json"
|
||||||
|
local services = {}
|
||||||
|
local host_width = 0
|
||||||
|
local time_width = 0
|
||||||
|
|
||||||
|
local c_hard = {}
|
||||||
|
c_hard[0] = resource.create_colored_texture(0, 0.6, 0, 1)
|
||||||
|
c_hard[1] = resource.create_colored_texture(0.8, 0.9, 0, 1)
|
||||||
|
c_hard[2] = resource.create_colored_texture(0.8, 0, 0, 1)
|
||||||
|
c_hard[3] = resource.create_colored_texture(0.6, 0, 0.7, 1)
|
||||||
|
|
||||||
|
local c_soft = {}
|
||||||
|
c_soft[1] = resource.create_colored_texture(0.3, 0.4, 0, 1)
|
||||||
|
c_soft[2] = resource.create_colored_texture(0.3, 0, 0, 1)
|
||||||
|
c_soft[3] = resource.create_colored_texture(0.3, 0, 0.4, 1)
|
||||||
|
|
||||||
|
local c_text = {}
|
||||||
|
c_text[0] = {1, 1, 1}
|
||||||
|
c_text[1] = {0, 0, 0}
|
||||||
|
c_text[2] = {1, 1, 1}
|
||||||
|
c_text[3] = {1, 1, 1}
|
||||||
|
|
||||||
|
gl.setup(NATIVE_WIDTH, NATIVE_HEIGHT)
|
||||||
|
|
||||||
|
util.file_watch("services.json", function(content)
|
||||||
|
services = json.decode(content)
|
||||||
|
host_width = 0
|
||||||
|
|
||||||
|
for idx, service in ipairs(services.services) do
|
||||||
|
host_width = math.max(host_width, CONFIG.font:width(service.host, 50))
|
||||||
|
end
|
||||||
|
|
||||||
|
time_width = CONFIG.font:width(services.prettytime, 30)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local white = resource.create_colored_texture(1,1,1,1)
|
||||||
|
local base_time = N.base_time or 0
|
||||||
|
|
||||||
|
function node.render()
|
||||||
|
CONFIG.background_color.clear()
|
||||||
|
CONFIG.font:write(NATIVE_WIDTH/2-time_width/2, 10, services.prettytime, 30, 1,1,1,1)
|
||||||
|
|
||||||
|
local y = 50
|
||||||
|
for idx, serv in ipairs(services.services) do
|
||||||
|
my_height = (#serv.output*40)+90
|
||||||
|
|
||||||
|
if serv.type == 0 then
|
||||||
|
c_soft[serv.state]:draw(0, y, NATIVE_WIDTH, y+my_height)
|
||||||
|
else
|
||||||
|
c_hard[serv.state]:draw(0, y, NATIVE_WIDTH, y+my_height)
|
||||||
|
end
|
||||||
|
|
||||||
|
y = y+20
|
||||||
|
|
||||||
|
CONFIG.font:write(10, y, serv.host, 50, c_text[serv.state][1],c_text[serv.state][2],c_text[serv.state][2],1)
|
||||||
|
CONFIG.font:write(host_width+40, y, serv.service, 50, c_text[serv.state][1],c_text[serv.state][2],c_text[serv.state][3],1)
|
||||||
|
|
||||||
|
y = y+60
|
||||||
|
|
||||||
|
for idx, line in ipairs(serv.output) do
|
||||||
|
CONFIG.font:write(host_width+40, y, line, 30, c_text[serv.state][1],c_text[serv.state][2],c_text[serv.state][3],1)
|
||||||
|
y = y+40
|
||||||
|
end
|
||||||
|
|
||||||
|
y = y+12
|
||||||
|
end
|
||||||
|
end
|
5
package.json
Normal file
5
package.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "icinga2beamer",
|
||||||
|
"author": "hi@kunsmann.eu",
|
||||||
|
"desc": "icinga2 information radiator"
|
||||||
|
}
|
BIN
package.png
Normal file
BIN
package.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
102
service
Executable file
102
service
Executable file
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import requests
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import urllib2
|
||||||
|
import pytz
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from itertools import islice
|
||||||
|
|
||||||
|
from hosted import CONFIG, NODE
|
||||||
|
|
||||||
|
CONFIG.restart_on_update()
|
||||||
|
|
||||||
|
def current_time():
|
||||||
|
timezone = pytz.timezone("Europe/Berlin")
|
||||||
|
now = datetime.utcnow()
|
||||||
|
now = now.replace(tzinfo=pytz.utc)
|
||||||
|
now = now.astimezone(timezone)
|
||||||
|
now = now.replace(tzinfo=None)
|
||||||
|
return now
|
||||||
|
|
||||||
|
def to_unixtimestamp(dt):
|
||||||
|
return int(time.mktime(dt.timetuple()))
|
||||||
|
|
||||||
|
def regenerate():
|
||||||
|
now = current_time()
|
||||||
|
|
||||||
|
services = {
|
||||||
|
'generated': to_unixtimestamp(now),
|
||||||
|
'prettytime': now.strftime('%d.%m.%Y %H:%M:%S'),
|
||||||
|
'services': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
hosts = requests.get(CONFIG["url_hosts"], auth=(CONFIG["api_user"], CONFIG["api_password"]), verify=False).json()
|
||||||
|
serv = requests.get(CONFIG["url_services"], auth=(CONFIG["api_user"], CONFIG["api_password"]), verify=False).json()
|
||||||
|
|
||||||
|
if 'results' not in hosts:
|
||||||
|
raise KeyError('API call for hosts did not return any results')
|
||||||
|
if 'results' not in serv:
|
||||||
|
raise KeyError('API call for services did not return any results')
|
||||||
|
|
||||||
|
for host in hosts['results']:
|
||||||
|
if host['attrs']['downtime_depth'] > 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if host['attrs']['problem']:
|
||||||
|
services['services'].append({
|
||||||
|
'host': host['attrs']['display_name'],
|
||||||
|
'service': '-- HOST --',
|
||||||
|
'state': 2,
|
||||||
|
'type': host['attrs']['state_type'],
|
||||||
|
'output': host['attrs']['last_check_result']['output'].splitlines(),
|
||||||
|
})
|
||||||
|
|
||||||
|
for svc in serv['results']:
|
||||||
|
if host['attrs']['downtime_depth'] > 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if svc['attrs']['problem']:
|
||||||
|
services['services'].append({
|
||||||
|
'host': svc['attrs']['host_name'],
|
||||||
|
'service': svc['attrs']['display_name'],
|
||||||
|
'state': svc['attrs']['state'],
|
||||||
|
'type': svc['attrs']['state_type'],
|
||||||
|
'output': svc['attrs']['last_check_result']['output'].splitlines(),
|
||||||
|
})
|
||||||
|
|
||||||
|
services['services'].sort(key=lambda x: str(x['type'])+str(x['state'])+x['host']+x['service'])
|
||||||
|
services['services'].reverse()
|
||||||
|
except Exception as e:
|
||||||
|
services['services'].append({
|
||||||
|
'host': 'icinga2beamer',
|
||||||
|
'service': 'INTERNAL',
|
||||||
|
'state': 2,
|
||||||
|
'output': [repr(e)],
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(services['services']) == 0:
|
||||||
|
services['services'].append({
|
||||||
|
'host': '',
|
||||||
|
'service': 'icinga2',
|
||||||
|
'state': 0,
|
||||||
|
'output': ['Everything is fine. Go get some coffee.'],
|
||||||
|
})
|
||||||
|
|
||||||
|
with file("services.json", "wb") as f:
|
||||||
|
f.write(json.dumps(services, ensure_ascii=False).encode("utf8"))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
regenerate()
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
time.sleep(20)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
BIN
silkscreen.ttf
Normal file
BIN
silkscreen.ttf
Normal file
Binary file not shown.
Loading…
Reference in a new issue