11import * as d3Interpolate from "d3-interpolate" ;
22import { Moment } from "moment" ;
33import { classModule , eventListenersModule , h , init , propsModule , styleModule , VNode } from "snabbdom" ;
4- import { DataDistributor , Filter , ObjectsLinksAndNodes } from "./datadistributor.js" ;
4+ import { DataDistributor , Filter , GenericFilter , ObjectsLinksAndNodes } from "./datadistributor.js" ;
55import { GenericNodeFilter } from "./filters/genericnode.js" ;
66import * as helper from "./utils/helper.js" ;
77import { _ } from "./utils/language.js" ;
@@ -13,6 +13,81 @@ type TableNode = {
1313 vnode ?: VNode ;
1414} ;
1515
16+ const statusFieldMapping = {
17+ "node.status" : {
18+ keys : [ "is_online" ] ,
19+ modifier : function ( d : any ) {
20+ return d ? "online" : "offline" ;
21+ } ,
22+ } ,
23+ "node.firmware" : {
24+ keys : [ "firmware" , "release" ] ,
25+ } ,
26+ "node.baseversion" : {
27+ keys : [ "firmware" , "base" ] ,
28+ } ,
29+ "node.deprecationStatus" : {
30+ keys : [ "model" ] ,
31+ modifier : function ( d : any ) {
32+ if ( window . config . deprecated && d && window . config . deprecated . includes ( d ) ) return _ . t ( "deprecation" ) ;
33+ if ( window . config . eol && d && window . config . eol . includes ( d ) ) return _ . t ( "eol" ) ;
34+ return _ . t ( "no" ) ;
35+ } ,
36+ } ,
37+ "node.hardware" : {
38+ keys : [ "model" ] ,
39+ } ,
40+ "node.visible" : {
41+ keys : [ "location" ] ,
42+ modifier : function ( d : any ) {
43+ return d && d . longitude && d . latitude ? _ . t ( "yes" ) : _ . t ( "no" ) ;
44+ } ,
45+ } ,
46+ "node.update" : {
47+ keys : [ "autoupdater" ] ,
48+ modifier : function ( d : any ) {
49+ if ( d . enabled ) {
50+ return d . branch ;
51+ }
52+ return _ . t ( "node.deactivated" ) ;
53+ } ,
54+ } ,
55+ "node.selectedGatewayIPv4" : {
56+ keys : [ "gateway" ] ,
57+ modifier : function ( nodeid : string | null , data : ObjectsLinksAndNodes ) {
58+ let gateway = data . nodeDict [ nodeid ] ;
59+ if ( gateway ) {
60+ return gateway . hostname ;
61+ }
62+ return null ;
63+ } ,
64+ } ,
65+ "node.selectedGatewayIPv6" : {
66+ keys : [ "gateway6" ] ,
67+ modifier : function ( nodeid : string | null , data : ObjectsLinksAndNodes ) {
68+ let gateway = data . nodeDict [ nodeid ] ;
69+ if ( gateway ) {
70+ return gateway . hostname ;
71+ }
72+ return null ;
73+ } ,
74+ } ,
75+ "node.domain" : {
76+ keys : [ "domain" ] ,
77+ modifier : function ( d : any ) {
78+ if ( window . config . domainNames ) {
79+ window . config . domainNames . some ( function ( t ) {
80+ if ( d === t . domain ) {
81+ d = t . name ;
82+ return true ;
83+ }
84+ } ) ;
85+ }
86+ return d ;
87+ } ,
88+ } ,
89+ } ;
90+
1691const patch = init ( [ classModule , propsModule , styleModule , eventListenersModule ] ) ;
1792
1893export const Proportions = function ( filterManager : ReturnType < typeof DataDistributor > ) {
@@ -26,6 +101,14 @@ export const Proportions = function (filterManager: ReturnType<typeof DataDistri
26101 let time : Moment ;
27102
28103 let tables : Record < string , TableNode > = { } ;
104+ // flag set while we apply filters programmatically from the URL hash
105+ let appliedUrlFilters = false ;
106+ let applyingFilter = false ;
107+
108+ function normalizeKey ( s : string | null | undefined ) {
109+ if ( ! s ) return "" ;
110+ return String ( s ) . replace ( / \s + / g, " " ) . trim ( ) ;
111+ }
29112
30113 function count ( nodes : Node [ ] , key : string [ ] , f ?: ( k : any ) => any ) {
31114 let dict = { } ;
@@ -56,6 +139,35 @@ export const Proportions = function (filterManager: ReturnType<typeof DataDistri
56139 } ;
57140 }
58141
142+ // Watch filter changes and sync the URL accordingly (but ignore when we are
143+ // programmatically applying filters from the hash).
144+ filterManager . watchFilters ( {
145+ filtersChanged : function ( filters : GenericFilter [ ] ) {
146+ const params : { [ param : string ] : string [ ] } = { } ;
147+
148+ filters . forEach ( function ( f ) {
149+ if ( ! f . getKey ) return ;
150+
151+ const name = f . getName ( ) ;
152+ const value = f . getValue ( ) ;
153+ const negate = f . getNegate ( ) ;
154+
155+ // Prefix with "!" when negated
156+ const encoded = negate ? `!${ value } ` : value ;
157+
158+ if ( ! params [ name ] ) {
159+ params [ name ] = [ encoded ] ;
160+ } else {
161+ params [ name ] . push ( encoded ) ;
162+ }
163+ } ) ;
164+
165+ if ( appliedUrlFilters ) {
166+ window . router . setParams ( params ) ;
167+ }
168+ } ,
169+ } ) ;
170+
59171 function fillTable ( name : string , table : TableNode | undefined , data : any [ ] [ ] ) : TableNode {
60172 let tableNode : TableNode = table ?? {
61173 element : document . createElement ( "table" ) ,
@@ -72,7 +184,10 @@ export const Proportions = function (filterManager: ReturnType<typeof DataDistri
72184 let items = data . map ( function ( data ) {
73185 let v = data [ 1 ] / max ;
74186
75- let filter = GenericNodeFilter ( _ . t ( name ) , data [ 2 ] , data [ 0 ] , data [ 3 ] ) ;
187+ let keys = data [ 2 ] ;
188+ let value = data [ 0 ] ;
189+ let modifierFunction = data [ 3 ] ;
190+ let filter = GenericNodeFilter ( name , keys , value , modifierFunction ) ;
76191
77192 let a = h ( "a" , { on : { click : addFilter ( filter ) } } , data [ 0 ] ) ;
78193
@@ -220,8 +335,46 @@ export const Proportions = function (filterManager: ReturnType<typeof DataDistri
220335 return b [ 1 ] - a [ 1 ] ;
221336 } ) ,
222337 ) ;
338+
339+ if ( ! appliedUrlFilters ) {
340+ applyFiltersFromHash ( ) ;
341+ }
223342 } ;
224343
344+ function applyFiltersFromHash ( ) {
345+ const params = window . router . getParams ( ) ;
346+ const keys = Object . keys ( params ) ;
347+ appliedUrlFilters = true ;
348+ if ( keys . length === 0 ) return ;
349+
350+ applyingFilter = true ;
351+ try {
352+ for ( const [ param , values ] of Object . entries ( params ) ) {
353+ if ( ! statusFieldMapping [ param ] ) {
354+ console . warn ( "unknown_filter_param" , param ) ;
355+ continue ; // continue instead of return to process other params
356+ }
357+
358+ const mapping = statusFieldMapping [ param ] ;
359+
360+ values . forEach ( function ( encodedValue ) {
361+ const negate = encodedValue . startsWith ( "!" ) ;
362+ if ( negate ) {
363+ encodedValue = encodedValue . slice ( 1 ) ;
364+ }
365+
366+ let filter = GenericNodeFilter ( param , mapping . keys , normalizeKey ( encodedValue ) , mapping . modifier ) ;
367+ if ( negate ) {
368+ filter . setNegate ( true ) ;
369+ }
370+ filterManager . addFilter ( filter ) ;
371+ } ) ;
372+ }
373+ } finally {
374+ applyingFilter = false ;
375+ }
376+ }
377+
225378 self . render = function render ( el : HTMLElement ) {
226379 self . renderSingle ( el , "node.status" , tables . status . element ) ;
227380 self . renderSingle ( el , "node.firmware" , tables . firmware . element ) ;
0 commit comments