pvl/verkko/hosts.py
changeset 30 841d856293a1
parent 29 38265b7d8f62
child 31 3e6d0feb115c
equal deleted inserted replaced
29:38265b7d8f62 30:841d856293a1
   148 
   148 
   149     def query (self) :
   149     def query (self) :
   150         return self.db.query(Host)
   150         return self.db.query(Host)
   151     
   151     
   152     def sort (self, hosts, default=HOST_SORT) :
   152     def sort (self, hosts, default=HOST_SORT) :
   153         self.sort = self.request.args.get('sort')
   153         sort = self.request.args.get('sort')
   154 
   154 
   155         if self.sort :
   155         if sort :
   156             sort = self.HOST_ATTRS[self.sort]
   156             order_by = self.HOST_ATTRS[sort]
   157         else :
   157         else :
   158             sort = default
   158             order_by = default
   159 
   159 
   160         log.debug("sort: %s", sort)
   160         log.debug("sort: %s", order_by)
   161         
   161         
   162         hosts = hosts.order_by(sort)
   162         hosts = hosts.order_by(order_by)
   163 
   163 
   164         # k
   164         # k
   165         return hosts
   165         return sort, hosts
   166     
   166     
       
   167     def filter_attr (self, attr, value) :
       
   168         """
       
   169             Return filter expression for given attr == value
       
   170         """
       
   171 
       
   172         if attr == 'seen' :
       
   173             column = Host.last_seen
       
   174 
       
   175             if value.isdigit() :
       
   176                 # specific date
       
   177                 date = datetime.datetime.strptime(value, Host.DATE_FMT).date()
       
   178 
       
   179                 return db.between(date.strftime(Host.DATE_FMT), 
       
   180                         db.func.strftime(Host.DATE_FMT, Host.first_seen),
       
   181                         db.func.strftime(Host.DATE_FMT, Host.last_seen)
       
   182                 )
       
   183             else :
       
   184                 # recent
       
   185                 timedelta = parse_timedelta(value)
       
   186                 
       
   187                 return ((db.func.now() - Host.last_seen) < timedelta)
       
   188 
       
   189                 # XXX: for sqlite, pgsql should handle this natively?
       
   190                 # to seconds
       
   191                 #timeout = timedelta.days * (24 * 60 * 60) + timedelta.seconds
       
   192                 
       
   193                 # WHERE strftime('%s', 'now') - strftime('%s', last_seen) < :timeout
       
   194                 #filter = (db.func.strftime('%s', 'now') - db.func.strftime('%s', Host.last_seen) < timeout)
       
   195         
       
   196         elif attr == 'ip' :
       
   197             column = Host.ip
       
   198 
       
   199             # column is IPv4 string literal format...
       
   200             if '/' in value :
       
   201                 return (db.func.inet(Host.ip).op('<<')(db.func.cidr(value)))
       
   202             else :
       
   203                 return (db.func.inet(Host.ip) == db.func.inet(value))
       
   204 
       
   205         else :
       
   206             # preprocess
       
   207             like = False
       
   208 
       
   209             if value.endswith('*') :
       
   210                 like = value.replace('*', '%')
       
   211 
       
   212             elif attr == 'mac' :
       
   213                 value = Host.normalize_mac(value)
       
   214 
       
   215             # filter
       
   216             column = self.HOST_ATTRS[attr]
       
   217 
       
   218             if like :
       
   219                 return (column.like(like))
       
   220             else :
       
   221                 return (column == value)
       
   222 
       
   223     def filter (self, hosts) :
       
   224         """
       
   225             Apply filters from request.args against given hosts.
       
   226 
       
   227             Returns (filters, hosts).
       
   228         """
       
   229 
       
   230         # filter?
       
   231         filters = {}
       
   232 
       
   233         for attr in self.HOST_ATTRS :
       
   234             values = self.request.args.getlist(attr)
       
   235 
       
   236             if not values :
       
   237                 continue
       
   238             
       
   239             filter = db.or_(*[self.filter_attr(attr, value) for value in values])
       
   240 
       
   241             log.debug("filter %s: %s", attr, filter)
       
   242 
       
   243             hosts = hosts.filter(filter)
       
   244             filters[attr] = values
       
   245 
       
   246         return filters, hosts
       
   247 
       
   248     def filters_title (self) :
       
   249         """
       
   250             Return a string representing the applied filters.
       
   251         """
       
   252 
       
   253         return ', '.join(value for values in self.filters.itervalues() for value in values)
       
   254  
   167     def render_hosts (self, hosts, title=None, filters=False, page=None, hilight=None) :
   255     def render_hosts (self, hosts, title=None, filters=False, page=None, hilight=None) :
   168         COLS = (
   256         COLS = (
   169             #title          sort        filter      class
   257             #title          sort        filter      class
   170             ('IP',          'ip',       'ip',       'ip'    ),
   258             ('IP',          'ip',       'ip',       'ip'    ),
   171             ('MAC',         'mac',      'mac',      'mac'   ),
   259             ('MAC',         'mac',      'mac',      'mac'   ),
   284         
   372         
   285         if filters is False :
   373         if filters is False :
   286             return table
   374             return table
   287         else :
   375         else :
   288             return html.form(method='get', action=self.url())(
   376             return html.form(method='get', action=self.url())(
   289                 html.input(type='hidden', name='sort', value=self.sort),
   377                 html.input(type='hidden', name='sort', value=self.sorts),
   290                 table,
   378                 table,
   291             )
   379             )
   292 
   380 
   293 class ItemHandler (BaseHandler) :
   381 class ItemHandler (BaseHandler) :
   294     def process (self, id) :
   382     def process (self, id) :
   295         self.hosts = self.query()
   383         self.hosts = self.query()
   296         self.host = self.hosts.get(id)
   384         self.host = self.hosts.get(id)
   297         
   385         
   298         if not self.host :
   386         if not self.host :
   299             raise web.NotFound("No such host: {id}".format(id=id))
   387             raise web.NotFound("No such host: {id}".format(id=id))
   300 
   388         
   301         self.hosts = self.sort(self.hosts.filter((Host.ip == self.host.ip) | (Host.mac == self.host.mac)))
   389         self.sorts, self.hosts = self.sort(self.hosts.filter((Host.ip == self.host.ip) | (Host.mac == self.host.mac)))
   302     
   390     
   303     def title (self) :
   391     def title (self) :
   304         return u"DHCP Host: {self.host}".format(self=self)
   392         return u"DHCP Host: {self.host}".format(self=self)
   305 
   393 
   306     def render_host (self, host) :
   394     def render_host (self, host) :
   360         ("Valid",       dict(state=('DHCPACK', 'DHCPRELEASE'))),
   448         ("Valid",       dict(state=('DHCPACK', 'DHCPRELEASE'))),
   361         ("Incomplete",  dict(state=('DHCPDISCOVER', 'DHCPOFFER', 'DHCPREQUEST'))),
   449         ("Incomplete",  dict(state=('DHCPDISCOVER', 'DHCPOFFER', 'DHCPREQUEST'))),
   362         ("Invalid",     dict(state=('DHCPNAK', ))),
   450         ("Invalid",     dict(state=('DHCPNAK', ))),
   363     )
   451     )
   364 
   452 
   365     def filter (self, attr, value) :
       
   366         """
       
   367             Return filter expression for given attr == value
       
   368         """
       
   369 
       
   370         if attr == 'seen' :
       
   371             column = Host.last_seen
       
   372 
       
   373             if value.isdigit() :
       
   374                 # specific date
       
   375                 date = datetime.datetime.strptime(value, Host.DATE_FMT).date()
       
   376 
       
   377                 return db.between(date.strftime(Host.DATE_FMT), 
       
   378                         db.func.strftime(Host.DATE_FMT, Host.first_seen),
       
   379                         db.func.strftime(Host.DATE_FMT, Host.last_seen)
       
   380                 )
       
   381             else :
       
   382                 # recent
       
   383                 timedelta = parse_timedelta(value)
       
   384                 
       
   385                 return ((db.func.now() - Host.last_seen) < timedelta)
       
   386 
       
   387                 # XXX: for sqlite, pgsql should handle this natively?
       
   388                 # to seconds
       
   389                 #timeout = timedelta.days * (24 * 60 * 60) + timedelta.seconds
       
   390                 
       
   391                 # WHERE strftime('%s', 'now') - strftime('%s', last_seen) < :timeout
       
   392                 #filter = (db.func.strftime('%s', 'now') - db.func.strftime('%s', Host.last_seen) < timeout)
       
   393         
       
   394         elif attr == 'ip' :
       
   395             column = Host.ip
       
   396 
       
   397             # column is IPv4 string literal format...
       
   398             if '/' in value :
       
   399                 return (db.func.inet(Host.ip).op('<<')(db.func.cidr(value)))
       
   400             else :
       
   401                 return (db.func.inet(Host.ip) == db.func.inet(value))
       
   402 
       
   403         else :
       
   404             # preprocess
       
   405             like = False
       
   406 
       
   407             if value.endswith('*') :
       
   408                 like = value.replace('*', '%')
       
   409 
       
   410             elif attr == 'mac' :
       
   411                 value = Host.normalize_mac(value)
       
   412 
       
   413             # filter
       
   414             column = self.HOST_ATTRS[attr]
       
   415 
       
   416             if like :
       
   417                 return (column.like(like))
       
   418             else :
       
   419                 return (column == value)
       
   420 
       
   421     def process (self) :
   453     def process (self) :
   422         hosts = self.query()
   454         hosts = self.query()
   423 
   455 
   424         # filter?
   456         # filter
   425         self.filters = {}
   457         self.filters, hosts = self.filter(hosts)
   426 
       
   427         for attr in self.HOST_ATTRS :
       
   428             values = self.request.args.getlist(attr)
       
   429 
       
   430             if not values :
       
   431                 continue
       
   432             
       
   433             filter = db.or_(*[self.filter(attr, value) for value in values])
       
   434 
       
   435             log.debug("filter %s: %s", attr, filter)
       
   436 
       
   437             hosts = hosts.filter(filter)
       
   438             self.filters[attr] = values
       
   439 
   458 
   440         # sort XXX: default per filter column?
   459         # sort XXX: default per filter column?
   441         hosts = self.sort(hosts)
   460         self.sorts, hosts = self.sort(hosts)
   442         
   461         
   443         # page?
   462         # page?
   444         self.page = self.request.args.get('page')
   463         self.page = self.request.args.get('page')
   445 
   464 
   446         if self.page :
   465         if self.page :
   447             self.page = int(self.page)
   466             self.page = int(self.page)
   448 
   467 
   449             hosts = hosts.offset(self.page * self.PAGE).limit(self.PAGE)
   468             hosts = hosts.offset(self.page * self.PAGE).limit(self.PAGE)
   450 
   469 
   451         self.hosts = hosts
   470         self.hosts = hosts
   452 
   471   
   453     def title (self) :
   472     def title (self) :
   454         if self.filters :
   473         if self.filters :
   455             return "DHCP Hosts: {filters}".format(filters=', '.join(value for values in self.filters.itervalues() for value in values))
   474             return "DHCP Hosts: {filters}".format(filters=self.filters_title())
   456         else :
   475         else :
   457             return "DHCP Hosts"
   476             return "DHCP Hosts"
   458     
   477     
   459     def render (self) :
   478     def render (self) :
   460         return (
   479         return (
   470     return int(time.mktime(dt.timetuple()))
   489     return int(time.mktime(dt.timetuple()))
   471 
   490 
   472 def ts2dt (ts) :
   491 def ts2dt (ts) :
   473     return datetime.datetime.fromtimestamp(ts)
   492     return datetime.datetime.fromtimestamp(ts)
   474 
   493 
   475 class RealtimeHandler (web.Handler) :
   494 class RealtimeHandler (BaseHandler) :
   476     TITLE = "Pseudo-Realtime hosts.."
   495     TITLE = "Pseudo-Realtime hosts.."
   477     CSS = web.Handler.CSS + (
   496     CSS = web.Handler.CSS + (
   478         'http://code.jquery.com/ui/1.9.0/themes/base/jquery-ui.css',
   497         'http://code.jquery.com/ui/1.9.0/themes/base/jquery-ui.css',
   479     )
   498     )
   480     JS = (
   499     JS = (
   492         ( 'gw',     'Network',      lambda host: host.gw    ),
   511         ( 'gw',     'Network',      lambda host: host.gw    ),
   493         ( 'seen',   'Seen',         Host.seen,              ),
   512         ( 'seen',   'Seen',         Host.seen,              ),
   494         ( 'state',  'State',        lambda host: host.state ),
   513         ( 'state',  'State',        lambda host: host.state ),
   495     )
   514     )
   496 
   515 
   497 
       
   498     def process (self) :
   516     def process (self) :
   499         hosts = self.db.query(Host).order_by(Host.last_seen.desc())
   517         """
       
   518             Either return JSON (if ?t=...), or fetch hosts/t for rendering.
       
   519         """
       
   520 
       
   521         hosts = self.db.query(Host)
   500         t = self.request.args.get('t')
   522         t = self.request.args.get('t')
       
   523         
       
   524         # always sorted by last_seen
       
   525         hosts = hosts.order_by(Host.last_seen.desc())
       
   526 
       
   527         # filter
       
   528         self.filters, hosts = self.filter(hosts)
   501 
   529 
   502         def host_params (host) :
   530         def host_params (host) :
   503             yield 'id', host.id
   531             yield 'id', host.id
   504 
   532 
   505             for name, title, fvalue in self.COLUMNS :
   533             for name, title, fvalue in self.COLUMNS :
   543             # XXX: testing
   571             # XXX: testing
   544             self.hosts = self.hosts.offset(1)
   572             self.hosts = self.hosts.offset(1)
   545 
   573 
   546             self.t = self.hosts[0].last_seen
   574             self.t = self.hosts[0].last_seen
   547 
   575 
       
   576     def title (self) :
       
   577         if self.filters :
       
   578             return "{title}: {filters}".format(title=self.TITLE, filters=self.filters_title())
       
   579         else :
       
   580             return self.TITLE
       
   581 
   548     def render (self) :
   582     def render (self) :
       
   583         """
       
   584             Render page HTML and initial <table>, along with bootstrap JS (t0, configuration).
       
   585         """
       
   586 
   549         def column (name, title, fvalue, host) :
   587         def column (name, title, fvalue, host) :
   550             cls = name
   588             cls = name
   551 
   589 
   552             if name == 'state' :
   590             if name == 'state' :
   553                 cls = host.state_class()
   591                 cls = host.state_class()
   554 
   592 
   555             return html.td(class_=cls)(fvalue(host))
   593             return html.td(class_=cls)(fvalue(host))
   556 
   594 
   557         params = dict(
   595         params = dict(
   558             url     = self.url(),
   596             url     = self.url(),
       
   597             filters = self.filters,
   559             t       = dt2ts(self.t),
   598             t       = dt2ts(self.t),
   560             host    = self.url(ItemHandler, id='0'),
   599             host    = self.url(ItemHandler, id='0'),
   561             columns = [name for name, title, fvalue in self.COLUMNS]
   600             columns = [name for name, title, fvalue in self.COLUMNS]
   562         )
   601         )
   563         params = json.dumps(params)
   602         params = json.dumps(params)