#!/usr/bin/env python3 # Copyright 2024 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. #import datetime from datetime import datetime import os import errno import configparser import time import signal import sys import subprocess import re import argparse from threading import Thread import json import tornado.ioloop import tornado.web import tornado.websocket from tornado.gen import sleep import asyncio VERSION = '0.9.9' konfig_file = 'config.cfg' s_certfile = 'server.crt' s_keyfile = 'server.key' ws_method = 'ws' host_dict = dict() failed_pingc = dict() loop_interval = 3 listen_port = 8888 listen_addr = '0.0.0.0' anz_hosts = 0 fping_parameter = [] fping_cmd = 'fping' def signal_handler(signal, frame): print('\nCtrl+C, keyboardInterrupt detected!') sys.exit(0) signal.signal(signal.SIGINT, signal_handler) config = configparser.ConfigParser(delimiters=('='), comment_prefixes=('#'), allow_no_value=False, strict=True, empty_lines_in_values=False) def read_config(): try: if config.read([konfig_file]) != []: pass else: print("Configfile '" + konfig_file + "' not found!") sys.exit() except configparser.DuplicateSectionError as e: print("Error: Duplicate Section '%s' not allowed." % str(e.section)) sys.exit() except configparser.DuplicateOptionError as e: print("Error: Duplicate Option '%s' in Section '%s' not allowed." % ( str(e.option), str(e.section) )) sys.exit() except configparser.Error as e: print("Error in Configfile: %s." % str(e)) sys.exit() #print(config.sections()) global anz_hosts for sect in config.sections(): for key in config[sect]: if (sect == "Main"): try: global loop_interval loop_interval = int(config["Main"]["LoopInterval"]) except KeyError: pass try: global listen_port listen_port = int(config["Main"]["ListenPort"]) except KeyError: pass try: global listen_addr listen_addr = str(config["Main"]["ListenAddr"]) except KeyError: pass try: title_add = str(config["Main"]["TitleAdd"]) except KeyError: config["Main"]["TitleAdd"] = '' try: global ws_method if ( str(config["Main"]["UseHTTPS"]) ): ws_method = 'wss' s_certfile = str(config["Main"]["CertFile"]) s_keyfile = str(config["Main"]["KeyFile"]) apath = os.path.dirname(os.path.abspath(__file__)) if not file_exists(s_certfile): print("CertFile not readable or does not exist: %s" % apath + '/' + s_certfile) sys.exit() if not file_exists(s_keyfile): print("KeyFile not readable or does not exist: %s" % apath + '/' + s_keyfile) sys.exit() except KeyError: pass else: #host_dict[key] = config[sect][key] host_dict[key] = sect.replace(' ','_') failed_pingc[key] = 0 anz_hosts += 1 if args.v: print(sect + ": " + key + " = " + config[sect][key]) print("Pinging " + str(anz_hosts) + " Devices in total.") def cmd_exists(cmd): path = os.environ["PATH"].split(os.pathsep) for prefix in path: filename = os.path.join(prefix, cmd) executable = os.access(filename, os.X_OK) is_not_directory = os.path.isfile(filename) if executable and is_not_directory: return True return False def file_exists(file): filename = os.path.dirname(os.path.abspath(__file__)) + '/' + file if os.access(filename, os.R_OK): return True return False def create_fpingparam(): global fping_parameter global fping_cmd try: fping_parameter.append(config["Main"]["fpingCommand"]) fping_cmd = config["Main"]["fpingCommand"] except KeyError: fping_parameter.append(fping_cmd) fping_parameter += ['--quiet', '--vcount=3'] try: fping_parameter.append('--interval=' + config["Main"]["Fping-interval"]) except KeyError: fping_parameter.append('--interval=1') try: fping_parameter.append('--period=' + config["Main"]["Fping-period"]) except KeyError: fping_parameter.append('--period=100') try: fping_parameter.append('--size=' + config["Main"]["Fping-size"]) except KeyError: pass def ping(ip, packets=1, timeout=2): comd = ['ping', '-c', str(packets), '-w', str(timeout), ip] res = subprocess.run(comd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return res.returncode == 0 def fping(ips): #comd = ['fping', '--quiet', '--interval=2', '--vcount=3', '--period=100'] + ips comd = fping_parameter + ips res = subprocess.run(comd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) #, universal_newlines = True) if args.v: print("fping finished: " + datetime.now().strftime("%H:%M:%S")) if res.returncode == 2: print("fping: any IP addresses were not found !") elif res.returncode == 3: print("fping: invalid command line arguments !") elif res.returncode == 4: print("fping: system call failure !") #print("RET:"+str(res.returncode)) return res.stdout.decode("utf-8") # Pinging Loop async def PingLoop(): print("PingLoop gestartet.") # sleep for a moment while True: await asyncio.sleep(3) # Sinnlos wenn niemend zuschaut: if ( len(BroadcastHandler.clients) < 1 ): continue for host, name in host_dict.items(): #print("ping host: " + host) if ping(host,1,2): line = { "host": host, "status": "OK" } BroadcastHandler.broadcast(line) else: line = { "host": host, "status": "FAILED" } BroadcastHandler.broadcast(line) # End while True # Fping Loop async def FpingLoop(): print("FpingLoop ready.") ip_list = list(host_dict.keys()) create_fpingparam() # check for ping command if not cmd_exists(fping_cmd): print("Exit with Error: fping command not found or not executable: '%s'" % fping_cmd) sys.exit(1) if args.v: tmp_cmd = [] + fping_parameter + ip_list comd_out = ' '.join(tmp_cmd) comd_out.replace("'", "") print('ping command used: "' + comd_out + '"') # The Loop while True: # Sinnlos wenn niemend zuschaut: if ( len(BroadcastHandler.clients) < 1 ): await asyncio.sleep(3) continue ac_time = datetime.now().strftime("%H:%M:%S") if args.v: print("call fping: " + ac_time) ret = fping(ip_list) rl = ret.splitlines() json_bc = [] for line in rl: host,rtims = line.split(' : ') if ( re.search(r"duplicate", rtims, flags=re.I) ): #print(host+": "+rtims) continue host = host.strip() rtims = rtims.strip() ux_sect = 'ux_' + host_dict[host] if re.search(r"[\d\.]+ [\d\.]+ [\d\.]+", rtims): #print(host+" OK") json_bc.append({ "host": host, "status": "OK", "rtt": rtims, "actime": ac_time, "sect": ux_sect }) failed_pingc[host] = 0 #BroadcastHandler.broadcast(line) elif re.search(r"[\d\.]+ [\d\.]+ -", rtims) or re.search(r"[\d\.]+ - [\d\.]+", rtims) or re.search(r"- [\d\.]+ [\d\.]+", rtims): #print(host+" OK") json_bc.append({ "host": host, "status": "OK", "rtt": rtims, "actime": ac_time, "sect": ux_sect }) failed_pingc[host] = 0 #BroadcastHandler.broadcast(line) elif re.search(r"- - -", rtims): #print(host+" FAILED") if failed_pingc[host] < int(config["Main"]["FlashThreshold"]): json_bc.append({ "host": host, "status": "NEWFAIL", "rtt": rtims, "actime": ac_time, "sect": ux_sect }) failed_pingc[host] += 1 else: json_bc.append({ "host": host, "status": "FAILED", "rtt": rtims, "actime": ac_time, "sect": ux_sect }) failed_pingc[host] = int(config["Main"]["FlashThreshold"]) + 1 #BroadcastHandler.broadcast(line) elif re.search(r"[\d\.]+", rtims): #print(host+" HALB") json_bc.append({ "host": host, "status": "HALB", "rtt": rtims, "actime": ac_time, "sect": ux_sect }) failed_pingc[host] = 0 #BroadcastHandler.broadcast(line) else: print(host+" ???????????????????") #print(json.dumps(json_bc, separators=(',', ':'), ensure_ascii=False)) BroadcastHandler.broadcast(json_bc) # sleep for Config-LoopInterval if ( len(BroadcastHandler.clients) >= 1 ): await asyncio.sleep(loop_interval) # End while True class BroadcastHandler(tornado.websocket.WebSocketHandler): clients = [] def open(self): o_time = datetime.now().strftime("%d.%m.%y %H:%M") if not re.search(r"^/websocket$", self.request.uri): log_msg = '"Bad request uri"' print("%s: Client %s: WebSocket Open-Error: %s " % (o_time, self.request.remote_ip, log_msg)) self.close(1111) return BroadcastHandler.clients.append(self) print("%s: Client %s: WebSocket opened: %s " % (o_time, self.request.remote_ip, self.request)) # oder "self.request.remote_ip" etc. / oder "self.__dict__" def on_close(self): BroadcastHandler.clients.remove(self) c_time = datetime.now().strftime("%d.%m.%y %H:%M") if ( self.close_code ): print("%s: Client %s: WebSocket closed: %s (close_code: %d)" % (c_time, self.request.remote_ip, self.request, self.close_code)) else: print("%s: Client %s: WebSocket undefined closed." % (c_time, self.request.remote_ip)) @classmethod def broadcast(cls, message): errored = [] for client in cls.clients: #print("Broadcasted: %s" % message) #print("Broadcasted: %s" % client.request.remote_ip) try: client.write_message(json.dumps(message, separators=(',', ':'), ensure_ascii=False)) #print("Broadcasted: %s" % message) except tornado.websocket.WebSocketClosedError: print("Error sending Broadcast message: WebSocketClosedError() raised") errored.append(client) except tornado.websocket.Exception as e: print("Error sending message: " + str(e)) errored.append(client) for conn in errored: cls.clients.remove(conn) class WWWSocketHandler(tornado.web.RequestHandler): #@tornado.gen.coroutine def prepare(self): #if self.request.protocol == 'http': # print("mmmm" + self.request.protocol) pass def get(self): #print(self.request.headers) #print(self.__dict__) if not re.search(r"^/$", self.request.uri): return html_modal01 = create_html_modal01() html_tab = create_html_table() self.write(html_html % (config["Main"]["TitleAdd"], ws_method, self.request.host, html_css, html_tab, html_modal01)) def on_connection_close(self): print("RequestHandler on_connection_close(): " + str(self.__dict__)) async def test(): while True: print("test") await asyncio.sleep(5) def make_app(): abs_path = os.path.dirname(os.path.abspath(__file__)) web_app = tornado.web.Application([ (r"^/websocket$", BroadcastHandler), (r"/", WWWSocketHandler), (r'/css/(.+\.css)', tornado.web.StaticFileHandler, {'path': abs_path + '/'}), (r'/img/(.+\.gif)', tornado.web.StaticFileHandler, {'path': abs_path + '/'}), (r'/(favicon.ico)', tornado.web.StaticFileHandler, {'path': abs_path + '/'}), ],debug=False,websocket_ping_interval=10,websocket_ping_timeout=30) if ws_method == 'ws': # http method in use: print("Serving HTTP and ws.") print("Listen on "+listen_addr+" Port "+str(listen_port)+" (http://"+listen_addr+":"+str(listen_port)+"/).") return web_app # https method in use: print("Serving HTTPS and wss.") print("Listen on "+listen_addr+" Port "+str(listen_port)+" (https://"+listen_addr+":"+str(listen_port)+"/).") http_server = tornado.httpserver.HTTPServer(web_app, ssl_options = { "certfile": abs_path + '/' + s_certfile, "keyfile": abs_path + '/' + s_keyfile, } ) return http_server async def main(): app = make_app() try: app.listen(int(listen_port), str(listen_addr)) except OSError as e: if e.errno == errno.EADDRINUSE: print("listen(): 'EADDRINUSE': " + str(e.args[1])) sys.exit(1) else: raise #await asyncio.gather(PingLoop(), return_exceptions=True) #await asyncio.gather(PingLoop()) await asyncio.gather(FpingLoop()) # ------ Div ------------------------------- def create_html_table(): html_tab = "" html_tab += '<div id="top_header" class="top_header w3-black">' html_tab += '<span class="w3-left tbars" style="text-align: left;">V'+VERSION+' by <a href="https://pss.zweiernet.ch" target="_blank">PSS</a><br><span style="font-size:1em;opacity:0.8;background-color:#4e4ec4;cursor:pointer;" onclick="document.getElementById(\'modal_01\').style.display=\'block\'"> <span class="w3-text-white w3-monospace"><b>i</b></span> ❔ </span></span>' html_tab += '<span class="w3-center top_head_tit">PingPanel<span class="top_head_tit_pfix"> ' + config["Main"]["TitleAdd"] + '</span></span>' html_tab += '<span class="w3-right tbars"><span id="cur_actime" style="text-align: right;color: #22ff00;font-weight: bold;"> </span><br><button id="close_button" onclick="socket_close(1000)" class="sbutt">stop</button><br></span>' html_tab += '</div>\n' html_tab += '<div class="w3-container">' for sect in config.sections(): if (sect == "Main"): continue html_tab += '<div class="w3-container" style="padding: 0;font-size:14px;">' sect_x = sect.replace(' ','_') clickId = 'accord_' + sect_x html_tab += '<br><div onclick="Accordion(\'' + clickId + '\')" class="w3-container w3-teal" style="padding: 3px 10px;margin-top: 5px;box-shadow: 0 2px 5px 0 #04040424,0 2px 10px 0 #040404c4;letter-spacing: 2px;cursor: pointer;text-shadow: 0 0 0px #fff;">' + sect + '<span id="ux_' + sect_x + '" class="w3-right"></span></div>\n' html_tab += '<div id="' + clickId + '" class="w3-show">' for key in config[sect]: tkey = key.replace('.', '_') html_tab += '<div class="w3-left si3-dark-grey w3-border w3-border-teal tooltip" style="text-align: center;margin: 1px; min-width: 13ch; padding: 4px min(3px, 1vw) !important;box-shadow:3px 2px 4px #000 !important;">' html_tab += '<span class="w3-card-2 tooltiptext">'+config[sect][key]+'<br><span id="ttip_' + tkey + '" class="ttip_small"></span></span>' html_tab += '<span id="' + tkey + '" class="ip_num">' + key + '</span><br><span id="stat_' + tkey + '" class="w3-center"></span></div>' html_tab += '</div>\n</div>\n' html_tab += '</div>\n' return html_tab html_html = ''' <!doctype html> <html> <header> <title>PingPanel %s</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="UTF-8"> <script type="text/javascript"> window.onload = start_websocket(); function start_websocket() { var ws = new WebSocket("%s://%s/websocket"); ws.onmessage = function (evt) { //let test = evt.data.replace(/'/g, '"'); // needed " by JSON.parse //var json_arr = JSON.parse(test); var json_arr = JSON.parse(evt.data); var cur_actime = ""; // Colortable counters initialisation let M_red = new Map(); let M_green = new Map(); let M_yellow = new Map(); for( let i = 0; i < json_arr.length; i++ ) { if ( M_red.has(json_arr[i].sect) ) { continue; } else { M_red.set(json_arr[i].sect, 0); M_green.set(json_arr[i].sect, 0); M_yellow.set(json_arr[i].sect, 0); } } // DOM Updates for(let i = 0; i < json_arr.length; i++) { var data_obj = json_arr[i]; cur_actime = data_obj.actime; var cur_host = data_obj.host; var cur_status = data_obj.status; var cur_sect_ux = data_obj.sect; var cur_rtt = data_obj.rtt; cur_rtt = cur_rtt.replace(/ /g,' \/ '); var uc_cur_host = cur_host.replace(/\./g,'_'); document.getElementById(uc_cur_host).textContent = cur_host; var stat_id = 'stat_' + uc_cur_host; var ttip_id = 'ttip_' + uc_cur_host; if (cur_status == 'OK') { M_green.set(cur_sect_ux, M_green.get(cur_sect_ux) + 1); document.getElementById(stat_id).innerHTML = '<img src="/img/green_on.gif">'; document.getElementById(ttip_id).innerHTML = '<b>RTT</b>: '+cur_rtt; } else if (cur_status == 'FAILED') { M_red.set(cur_sect_ux, M_red.get(cur_sect_ux) + 1); document.getElementById(stat_id).innerHTML = '<img src="/img/red_on.gif">'; document.getElementById(ttip_id).innerHTML = '<b>RTT</b>: '+cur_rtt; } else if (cur_status == 'NEWFAIL') { M_red.set(cur_sect_ux, M_red.get(cur_sect_ux) + 1); document.getElementById(stat_id).innerHTML = '<img src="/img/red_anim.gif">'; document.getElementById(ttip_id).innerHTML = '<b>RTT</b>: '+cur_rtt; } else { M_yellow.set(cur_sect_ux, M_yellow.get(cur_sect_ux) + 1); document.getElementById(stat_id).innerHTML = '<img src="/img/yellow_on.gif">'; document.getElementById(ttip_id).innerHTML = '<b>RTT</b>: '+cur_rtt; } } // End for document.getElementById('cur_actime').innerHTML = cur_actime; for (let [sect, val] of M_red) { document.getElementById(sect).innerHTML = '<span class="unreach_r"><span class="red_r">'+M_red.get(sect).toString().padStart(3, " ")+'</span> | <span class="yellow_r">'+M_yellow.get(sect).toString().padStart(3, " ")+'</span> | <span class="green_r">'+M_green.get(sect).toString().padStart(3, " ")+'</span></span>'; } }; ws.onclose = function(evt) { //alert("WebSocket closed."); document.getElementById('cur_actime').innerHTML = '<span style="color:red;">stopped</span>'; document.getElementById('close_button').style.opacity = "0.3"; //document.getElementById('modal_alert_text').style.display='block'; //document.getElementById('modal_alert_text').innerHTML = 'WebSocket closed.'; }; ws.onerror = function(evt) { alert("WebSocket closed due to an error."); document.getElementById('cur_actime').innerHTML = '<span style="color:red;">Error</span>'; document.getElementById('close_button').style.opacity = "0.3"; }; socket_close = (code) => { document.getElementById('close_button').style.opacity = "0.3"; ws.close(code); }; }; function Accordion(id) { var x = document.getElementById(id); //alert(x); //.previousSibling.getElementsByTagName("*").item(0).innerHTML); if (x.className.indexOf("w3-show") == -1) { x.className = "w3-show"; } else { x.className = x.className.replace("w3-show", "w3-hide"); } }; </script> <script> // modal handling var modal = document.getElementById('modal_01'); // When the user clicks anywhere outside of the modal, close it window.onclick = function(event) { if (event.target == modal) { modal.style.display = "none"; } } </script> <link rel="stylesheet" href="/css/w3.css"> <style> %s </style> </header> <body> <div id="main">%s</div>\n <p><br></p> <div id="modal_01" class="w3-modal modal_01" onclick="this.style.display='none'"> <div class="w3-modal-content modal_01_text"> <div class="w3-container"> <h4 style="font-variant:small-caps;letter-spacing:3px;">PingPanel</h4> %s </div> </div> </div> </body> </html> ''' html_css = ''' body { background: #333; font-family: Verdana, Arial, sans-serif; color: #ddd; line-height: 110%; font-size: 13px; } .red { color: red; } .green { color: #0f0; } .yellow { color: yellow; } .red_r { color: red; white-space: pre; text-align: right; font-family: monospace;text-shadow: 0px -0px 0px #f44;} .green_r { color: #0f0; white-space: pre; text-align: right;; font-family: monospace;} .yellow_r { color: yellow; white-space: pre; text-align: right;; font-family: monospace;} .ip_num { font-size: 80%; } .si3-dark-grey{color:#fff!important;background-color:#626262!important;text-shadow: 0 0 0px #fff, 0 0 1px #000;} .top_header { text-align: center; padding: 5px 10px; height: 43px; box-shadow: 0px 1px 9px 1px #c3d0cd78 !important; position: sticky; z-index: 100; top: 0px; } .tbars { color: #aaa; font-size: 11px; font-stretch: semi-expanded; } .unreach_r { background-color:#313131; padding-left:5px; padding-right:5px; letter-spacing: 0px; border-radius: 2px; font-weight: bold; font-size: 12px; } .modal_01 { } .modal_01_text { background-color: #000; color: #f0f0ca; line-height: 120%; font-size: 1.1em; } .sbutt { border: none; display: inline-block; vertical-align: middle; overflow: hidden; text-decoration: none; text-align: center; cursor: pointer; white-space: nowrap; border-radius: 4px; padding: 0px 8px !important; background-color: #9b9b9b !important; color: #000 !important; } .top_head_tit { font-size: 22px; font-variant: small-caps; letter-spacing: 4px; top: 0.3em; position: sticky; } .top_head_tit_pfix { font-size: 0.5em; font-variant: small-caps; letter-spacing: 0px; } .ttip_small { font-size: 64%; text-align: center; padding-top: 6px; } .tooltip { position: relative; display: inline-block; border-bottom: 1px dotted black; } .tooltip .tooltiptext { font-family: Verdana, 'PT Sans Caption', Arial, sans-serif; visibility: hidden; width: 170px; background-color: black; color: #fff; text-align: center; font-size: 17px; border-radius: 6px; padding: 5px 5px; position: absolute; z-index: 1; top: 120%; left: 50%; margin-left: -60px; line-height: 1.1em; text-shadow: 0 0 0 #a8a8a8 !important; } .tooltip .tooltiptext::after { content: ""; position: absolute; bottom: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: transparent transparent black transparent; } .tooltip:hover .tooltiptext { visibility: visible; } .sticky { position: fixed; top: 0; width: 100%; } .sticky + .content { padding-top: 50px; } ''' def create_html_modal01(): html_modal = ''' <p style="font-size:1.2em;font-variant:small-caps;letter-spacing:3px;font-style:italic;text-decoration:underline">Info</p> Config File: ''' + konfig_file + '''<br> Total number of Devices: _NUM_HOSTS_ <br> LoopInterval: ''' + str(loop_interval) + ''' s<br> FlashThreshold: ''' + str(config["Main"]["FlashThreshold"]) + ''' rounds<br> fping: ''' + fping_parameter[2].replace('--','') + '''<br> fping: ''' + fping_parameter[3].replace('--','') + ''' ms<br> fping: ''' + fping_parameter[4].replace('--','') + ''' ms<br> <br> <p style="font-size:1.2em;font-variant:small-caps;letter-spacing:3px;font-style:italic;text-decoration:underline">Help</p> The PingPanel board provides the header and the configured, expandable and collapsible sections with the corresponding devices to be monitored.<br> Among other things, the header contains the current time (<span class="green">green</span>) of the last ping run or the status "stopped" (<span class="red">red</span>) if the connection to the server has been lost or the "Stop" button below has been pressed.<br> Pressing the "Stop" button terminates the connection to the server and, if no other client is active, the server pauses the ping rounds.<p> The blue-green <span class="w3-teal">section bars</span> contain the name of the section and consolidated information about the status of the devices in this section.<br> The section bars can be expanded and collapsed by clicking or tapping on them.<br> Consolidated status information <img src="/img/stat-inf.gif"> is divided into <span class="red">failed</span>, <span class="yellow">partially answered</span> and <span class="green">answered</span> ping requests.<br> Each monitored device displays its IP address and status. By hovering over or tapping on it its comment is displayed and the three ping RTT's of each ping round is reported.<p> A <span class="red">flashing red</span> status light indicates that the device has recently not responded during 'FlashThreshold' ping rounds.<br> A <span class="red">red</span> status light indicates that the device has not answered any of the three pings.<br> A <span class="yellow">yellow</span> status light indicates that the device has answered one of three pings.<br> A <span class="green">green</span> status light indicates that the device has answered to all three or at least two pings.<br> <p></p> <p> </p> ''' html_modal = html_modal.replace('_NUM_HOSTS_', str(anz_hosts)) return html_modal # -------- MAIN ------------------------------ if __name__ == "__main__": print("PingPanel v"+VERSION+" by PSS -- started at " + datetime.now().strftime("%F %H:%M:%S")) # Commandline parsing parser = argparse.ArgumentParser(description='PingPanel V'+VERSION+"\n2024 by sigi", formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('-c', help="Alternative configuration file (standard: config.cfg)", type=str, metavar='config-file') parser.add_argument('-v', help="Verbose mode", action="store_true") args = parser.parse_args() if args.c: konfig_file = args.c print("Configfile: " + konfig_file) # Config read_config() # create the app asyncio.run(main()) # ------