JorekTheGlitch

Verdandi

Apr 21st, 2020
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.24 KB | None | 0 0
  1. import tkinter as tk
  2. import tkinter.ttk as ttk
  3.  
  4. import asyncio
  5. import operator
  6. import json
  7. import logging
  8. from datetime import datetime as dt, timedelta as td
  9.  
  10.  
  11. DELAULT_TABLE_IDENTITY = 2
  12.  
  13.  
  14. def human_readable(size:str):
  15.     sizing = {
  16.         range(0,  4):  ('B', 1),
  17.         range(4,  7):  ('KB', 10**3),
  18.         range(7,  10): ('MB', 10**6),
  19.         range(10, 13): ('GB', 10**9)
  20.     }
  21.     lenght = len(str(size))
  22.     for scope, notation in sizing.items():
  23.         if lenght in scope:
  24.             size = size/notation[1]
  25.             size = round(size, 2)
  26.             return '{}{}'.format(size, notation[0])
  27.  
  28.  
  29. class YggdrasilAPI():
  30.     #__slots__ = ['addr', 'stats', '__callbacks']
  31.     def __init__(self, address='localhost', port=9001, unixsock='/var/run/yggdrasil.sock'):
  32.         self.addr = address, port
  33.         self.stats = {}
  34.         self.__init_trackers()
  35.         asyncio.ensure_future(self.getSelf())
  36.  
  37.     async def _send_request(self, method:str, **kwargs)->dict:
  38.         req = {'request': method, **kwargs}
  39.         reader, writer = await asyncio.open_connection(*self.addr)
  40.         writer.write(json.dumps(req).encode())
  41.         response = json.loads(await reader.read())
  42.         writer.close()
  43.         await writer.wait_closed()
  44.         logging.info('\n[{}] {}:{} {} request'.format(dt.now(), *self.addr, method))
  45.         logging.debug('Response: {}'.format(response))
  46.         if not response['status']=='success':
  47.             raise Exception(response)
  48.         self.stats.update(response['response'])
  49.         return response['response']
  50.  
  51.     def _preprocess(self, **kwargs):
  52.         if 'bytes_sent' in kwargs:
  53.             kwargs['bytes_sent'] = human_readable(kwargs['bytes_sent'])
  54.         if 'bytes_recvd' in kwargs:
  55.             kwargs['bytes_recvd'] = human_readable(kwargs['bytes_recvd'])
  56.         if 'proto' in kwargs and 'endpoint' in kwargs:
  57.             kwargs['faddr'] = '{}://{}'.format(kwargs['proto'], kwargs['endpoint'])
  58.         if 'uptime' in kwargs:
  59.             sec = round(kwargs['uptime'])
  60.             kwargs['uptime'] = str(td(seconds=sec))
  61.         if 'last_seen' in kwargs:
  62.             sec = round(kwargs['last_seen'])
  63.             date = dt.now() - td(seconds=sec)
  64.             kwargs['last_seen'] = date.strftime("%d/%m/%y %H:%M:%S")
  65.         return kwargs
  66.  
  67.     #@self.callbacks_caller()
  68.     async def getSelf(self)->dict:
  69.         raw = await self._send_request('getSelf')
  70.         response:dict = raw['self']
  71.         addr, params = tuple(response.items())[0]
  72.         return dict(addr=addr, **params)
  73.  
  74.     #@self.callbacks_caller()
  75.     async def getPeers(self)->dict:
  76.         raw = await self._send_request('getPeers')
  77.         response = raw['peers']
  78.         return [self._preprocess(addr=addr, **params) for addr, params in response.items()]
  79.  
  80.     #@self.callbacks_caller()
  81.     async def getDHT(self)->dict:
  82.         raw = await self._send_request('getDHT')
  83.         response = raw['dht']
  84.         return [self._preprocess(addr=addr, **params) for addr, params in response.items()]
  85.  
  86.     #@self.callbacks_caller()
  87.     async def getSwitchPeers(self)->dict:
  88.         raw = await self._send_request('getSwitchPeers')
  89.         response = raw['switchpeers']
  90.         return [self._preprocess(id=_id, **speer) for _id, speer in response.items()]
  91.  
  92.     #@self.callbacks_caller()
  93.     async def getSessions(self)->dict:
  94.         raw = await self._send_request('getSessions')
  95.         response = raw['sessions']
  96.         return [self._preprocess(addr=addr, **params) for addr, params in response.items()]
  97.  
  98.     #@self.callbacks_caller()
  99.     async def getAllowedEncryptionPublicKeys(self)->list:
  100.         raw = await self._send_request('getAllowedEncryptionPublicKeys')
  101.         return raw['allowed_box_pubs']
  102.    
  103.     #@self.callbacks_caller()
  104.     def getTransitTrafic(self):
  105.         return {
  106.             'sent': sum(node['bytes_sent'] for node in self.stats['switchpeers'].values()),
  107.             'recvd': sum(node['bytes_recvd'] for node in self.stats['switchpeers'].values())
  108.         }
  109.  
  110.     def getNumOfNodes(self):
  111.         return len(self.stats['switchpeers'])
  112.  
  113.     def add_done_callback(self, method, cb):
  114.         self.__callbacks[method].append[cb]
  115.         return len(self.__callbacks[method]) - 1
  116.  
  117.     def del_done_callback(self, method, cb=None):
  118.         try:
  119.             self.__callbacks[method].remove(cb)
  120.         except ValueError:
  121.             pass
  122.  
  123.     def del_done_callback_by_id(self, method, cb_id):
  124.         try:
  125.             del self.__callbacks[method][cb_id]
  126.         except IndexError:
  127.             pass
  128.  
  129.     def __init_trackers(self):
  130.         methods = ('getAllowedEncryptionPublicKeys', 'getDHT', 'getPeers', 'getSelf', 'getSessions', 'getSwitchPeers')
  131.         self.__callbacks = {method: [] for method in methods}
  132.         for method_name in methods:
  133.             wrapper = self.done_tracker()
  134.             wrapped = wrapper(getattr(self, method_name))
  135.             setattr(self, method_name, wrapped)
  136.  
  137.     def done_tracker(self):
  138.         def decorator(function):
  139.             def wrapped(*args, **kwargs):
  140.                 result = function(*args, **kwargs)
  141.                 callbacks = self.__callbacks.get(function, [])
  142.                 for callback in callbacks:
  143.                     callback(result)
  144.                 return result
  145.             return wrapped
  146.         return decorator
  147.  
  148.  
  149. class Table(tk.Frame):
  150.     __slots__ = []
  151.     def __init__(
  152.             self, parent=None, headings=(), unique_heading=None, params=(),
  153.             updater=None, updater_exc_handler=None, update_delay=5,
  154.             identity_lvl=DELAULT_TABLE_IDENTITY,
  155.         ):
  156.         super().__init__(parent)
  157.  
  158.         table = ttk.Treeview(self, show="headings", selectmode="extended")
  159.         table["columns"]=headings
  160.         table["displaycolumns"]=headings
  161.  
  162.         for head in headings:
  163.             table.heading(head, text=head, anchor=tk.CENTER, command= self.sort_column(head))
  164.             table.column(head, anchor=tk.CENTER, width=150)
  165.  
  166.         yscrolltable = tk.Scrollbar(self, command=table.yview)
  167.         xscrolltable = tk.Scrollbar(self, command=table.xview, orient=tk.HORIZONTAL)
  168.         table.configure(yscrollcommand=yscrolltable.set)
  169.         table.configure(xscrollcommand=xscrolltable.set)
  170.         yscrolltable.pack(side=tk.RIGHT, fill=tk.Y)
  171.         xscrolltable.pack(side=tk.BOTTOM, fill=tk.X)
  172.  
  173.         table.place(relheight=1, relwidth=1, relx=0, rely=0)
  174.         self.tv = table
  175.        
  176.         self.uniques = {}
  177.         if unique_heading:
  178.             self.uh_index = headings.index(unique_heading)
  179.         self.names = params
  180.         self._paused = False
  181.         self._ident_lvl = identity_lvl
  182.         if updater:
  183.             self._updater = updater
  184.             asyncio.ensure_future(self.updater(updater, update_delay))
  185.         self.upd_exc_handler = updater_exc_handler if updater_exc_handler else lambda *args, **kwargs: None
  186.  
  187.     def add_row(self, row):
  188.         new_id = self.tv.insert('', tk.END, values=tuple(row))
  189.         self.uniques[row[self.uh_index]] = new_id
  190.    
  191.     def add_rows(self, rows):
  192.         for row in rows:
  193.             self.add_row(row)
  194.  
  195.     async def updater(self, updater, delay:int):
  196.         updater = asyncio.coroutine(updater)
  197.         while True:
  198.             while not self._paused:
  199.                 try:
  200.                     raw = await updater()
  201.                 except Exception as e:
  202.                     #print('{}: {}'.format(e.__class__.__name__, str(e)))
  203.                     self.upd_exc_handler(e)
  204.                 else:
  205.                     rows = ((item[name] for name in self.names) for item in raw)
  206.                     self.update_table(rows, self._ident_lvl)
  207.                 finally:
  208.                     await asyncio.sleep(delay)
  209.             await asyncio.sleep(delay)
  210.    
  211.     async def update_nowait(self):
  212.         self.pause()
  213.         raw = await asyncio.coroutine(self._updater())
  214.         rows = ((item[name] for name in self.names) for item in raw)
  215.         self.update_table(rows, self._ident_lvl)
  216.         self.run()
  217.    
  218.     def pause(self):
  219.         self._paused = True
  220.  
  221.     def run(self):
  222.         self._paused = False
  223.  
  224.     def bind_menu(self, menu:tk.Menu):
  225.         def call_menu(event: tk.Event):
  226.             menu.post(event.x_root, event.y_root)
  227.         self.tv.bind("<Button-3>", call_menu)
  228.  
  229.     def delete_selected(self, event:tk.Event):
  230.         pass
  231.  
  232.     def clean(self):
  233.         parent = ''
  234.         items = self.tv.get_children(parent)
  235.         self.tv.delete(*items)
  236.    
  237.     def _similarity(self, one, other):
  238.         return sum(operator.eq(ones, others) for ones, others in zip(one, other))
  239.    
  240.     def _find_similar(self, row, identity_lvl):
  241.         for iid in self.tv.get_children(''):
  242.             item = self.tv.item(iid)
  243.             similarity = self._similarity(item['values'], row)
  244.             if similarity>=identity_lvl:
  245.                 return iid
  246.  
  247.     def update_table(self, rows, identity_lvl=None):
  248.         if not identity_lvl:
  249.             identity_lvl = len(self.names)
  250.         for row in rows:
  251.             row = tuple(row)
  252.             if row[self.uh_index] in self.uniques:
  253.                 self.tv.item(self.uniques[row[self.uh_index]], values=row)
  254.             else:
  255.                 self.add_row(row)
  256.             """
  257.            replaced = self._find_similar(row, identity_lvl)
  258.            if replaced:
  259.                self.tv.item(replaced, values=row)
  260.            else:
  261.                self.add_row(row)"""
  262.         if hasattr(self, 'last_sort'):
  263.             self.last_sort()
  264.  
  265.     def sort_column(self, column, reverse=False):
  266.         def sorter():
  267.             l = [(self.tv.set(k, column), k) for k in self.tv.get_children('')]
  268.             l.sort(reverse=reverse)
  269.             # rearrange items in sorted positions
  270.             for index, (_, k) in enumerate(l):
  271.                 self.tv.move(k, '', index)
  272.             self.last_sort = self.sort_column(column, reverse)
  273.             self.tv.heading(column, command= self.sort_column(column, not reverse))
  274.         return sorter
  275.  
  276.  
  277. class VarsUpdater():
  278.     def __init__(self):
  279.         self.groups = {}
  280.     def add(self, var, group_name):
  281.         self.groups[group_name]['vars'].append(var)
  282.  
  283.  
  284. class AutoUpdatingVar():
  285.  
  286.     __slots__ = ['var', 'type_coercion', 'delay']
  287.     comparison = {
  288.         tk.BooleanVar: bool,
  289.         tk.DoubleVar: float,
  290.         tk.IntVar: int,
  291.         tk.StringVar: str
  292.     }
  293.  
  294.     def __init__(self, var:tk.Variable, updater, delay=5):
  295.         self.var = var
  296.         self.type_coercion = self.comparison.get(type(var), str)
  297.         self.delay = delay
  298.         asyncio.ensure_future(self.updater(updater))
  299.  
  300.     async def updater(self, source):
  301.         updater = asyncio.coroutine(source)
  302.         while True:
  303.             upd = self.type_coercion(await updater())
  304.             self.var.set(upd)
  305.             await asyncio.sleep(self.delay)
  306.  
  307.  
  308. class FStringVar(tk.StringVar):
  309.     def __init__(self, *args, template:str, fargs:list, fkwargs:dict, **kwargs):
  310.         self.templ = template
  311.         kwargs['value'] = template.format(*fargs, **fkwargs)
  312.         super().__init__(*args, **kwargs)
  313.     def set(self, *args, **kwargs):
  314.         upd = self.templ.format(*args, **kwargs)
  315.         super().set(upd)
  316.  
  317.  
  318. def get_tables(ygg:YggdrasilAPI):
  319.     return {
  320.         'Peers': {
  321.             'updater': ygg.getPeers,
  322.             'headings': ('IP', 'received', 'sent', 'endpoint', 'uptime'),
  323.             'params': ('addr', 'bytes_recvd', 'bytes_sent', 'endpoint', 'uptime'),
  324.             'unique_heading': 'IP',
  325.         },
  326.         'DHT': {
  327.             'updater': ygg.getDHT,
  328.             'headings': ('IP', 'coordinates', 'last seen'),
  329.             'params': ('addr', 'coords', 'last_seen'),
  330.             'unique_heading': 'IP',
  331.             'identity_lvl': 1
  332.         },
  333.         'Sessions': {
  334.             'updater': ygg.getSessions,
  335.             'headings': ('IP', 'received', 'sent', 'uptime', 'MTU', 'MTU fixed', 'coordinates'),
  336.             'params': ('addr', 'bytes_recvd', 'bytes_sent', 'uptime', 'mtu', 'was_mtu_fixed', 'coords'),
  337.             'unique_heading': 'IP'
  338.         },
  339.         'Switch peers': {
  340.             'updater': ygg.getSwitchPeers,
  341.             'headings': ('port', 'IP', 'endpoint', 'coordinates', 'received', 'sent'),
  342.             'params': ('port', 'ip', 'faddr', 'coords', 'bytes_recvd', 'bytes_sent'),
  343.             'unique_heading': 'IP'
  344.         },
  345.         'Allowed keys': {
  346.             'updater': ygg.getAllowedEncryptionPublicKeys,
  347.             'headings': ('Key',),
  348.             'params': ('key',),
  349.             'unique_heading': 'Key'
  350.         }
  351.     }
  352.  
  353.  
  354. class Tk(tk.Tk):
  355.     __slots__ = []
  356.     def __init__(self, *args, delay=1/50, **kwargs):
  357.         super().__init__(*args, **kwargs)
  358.         self._delay = delay
  359.         self._loop = asyncio.get_event_loop()
  360.         asyncio.ensure_future(self.updater(), loop=self._loop)
  361.  
  362.     async def updater(self):
  363.         while not await asyncio.sleep(self._delay):
  364.             try:
  365.                 self.update()
  366.             except tk._tkinter.TclError:
  367.                 self._loop.stop()
  368.             except Exception as E:
  369.                 info = E.__class__.__name__, str(E)
  370.                 print("{}: {}".format(*info))
  371.                 self._loop.stop()
  372.    
  373.     def mainloop(self, n=0):
  374.         self._loop.run_forever()
  375.  
  376.  
  377. if __name__ == "__main__":
  378.     names_priority = ('Overview', 'Peers', 'DHT', 'Sessions', 'Switch peers', 'Allowed keys')
  379.  
  380.     root = Tk('root')
  381.     root.title('Verdandi')
  382.     root.geometry('800x500')
  383.    
  384.     notebook = ttk.Notebook(root)
  385.  
  386.     ygg = YggdrasilAPI()
  387.  
  388.     tables = get_tables(ygg)
  389.  
  390.     for name in names_priority:
  391.         frame = ttk.Frame(notebook, name=name.lower())
  392.         notebook.add(frame, text=name)
  393.         if name not in tables:
  394.             continue
  395.         table_params = tables[name]
  396.         table = Table(frame, **table_params)
  397.         table.place(relheight=1, relwidth=5/6, relx=1/6, rely=0)
  398.     notebook.place(relx=0, rely=0, relheight=1, relwidth=1)
  399.  
  400.     overview = notebook.children['overview']
  401.     addr = tk.StringVar()
  402.     #addr_upd = AutoUpdatingVar(addr, ygg.getSelfAddr)
  403.     addr_label = tk.Label(overview, textvariable=addr)
  404.     subnet_label = tk.Label(overview)
  405.     nodes = tk.Label(overview)
  406.     nodes_traffic = tk.Label(overview)
  407.     summary_traffic = tk.Label(overview)
  408.     active_sessions = tk.Label(overview)
  409.     services_availability = tk.Label(overview)
  410.  
  411.     root.mainloop()
Add Comment
Please, Sign In to add comment