66 QComboBox ,
77 QLineEdit ,
88 QPushButton ,
9- QListWidget ,
10- QListWidgetItem ,
9+ QTreeWidget ,
10+ QTreeWidgetItem ,
1111)
1212from models .sysmon_config import RuleFilter , SysmonConfig
1313from PySide6 .QtGui import QColor
14+ from PySide6 .QtCore import Qt
1415
1516
1617class RuleEditor (QWidget ):
@@ -20,7 +21,6 @@ def __init__(self, config: SysmonConfig) -> None:
2021 self .config = config
2122 self .current_event_id : int | None = None
2223 self .current_event_name : str = ""
23- self .displayed_rules : list [tuple [int , int ]] = []
2424
2525 self .layout = QVBoxLayout ()
2626 self .setLayout (self .layout )
@@ -44,7 +44,8 @@ def __init__(self, config: SysmonConfig) -> None:
4444 self .add_button = QPushButton ("Add Rule" )
4545 self .remove_button = QPushButton ("Remove Selected Rule" )
4646
47- self .rule_list = QListWidget ()
47+ self .rule_tree = QTreeWidget ()
48+ self .rule_tree .setHeaderHidden (True )
4849
4950 self .rule_row_1 = QHBoxLayout ()
5051 self .rule_row_1 .addWidget (self .rule_type )
@@ -60,7 +61,7 @@ def __init__(self, config: SysmonConfig) -> None:
6061 self .layout .addLayout (self .rule_row_2 )
6162 self .layout .addWidget (self .add_button )
6263 self .layout .addWidget (self .remove_button )
63- self .layout .addWidget (self .rule_list )
64+ self .layout .addWidget (self .rule_tree )
6465
6566 self .add_button .clicked .connect (self .add_rule )
6667 self .remove_button .clicked .connect (self .remove_selected_rule )
@@ -103,25 +104,54 @@ def load_value_presets_for_field(self, field_name: str) -> None:
103104 self .value_preset_box .addItems (presets )
104105
105106 def refresh_rules (self ) -> None :
106- self .rule_list .clear ()
107- self .displayed_rules .clear ()
107+ self .rule_tree .clear ()
108108
109109 for event_id , event_config in sorted (self .config .events .items ()):
110+ if not event_config .rules :
111+ continue
112+
113+ event_item = QTreeWidgetItem (
114+ [f"{ event_id } - { event_config .event_name } " ]
115+ )
116+ self .rule_tree .addTopLevelItem (event_item )
117+
118+ grouped_parents : dict [str , QTreeWidgetItem ] = {}
119+ ungrouped_parent : QTreeWidgetItem | None = None
120+
110121 for rule_index , rule in enumerate (event_config .rules ):
122+ if rule .group_id :
123+ if rule .group_id not in grouped_parents :
124+ group_name = rule .group_name or "Imported Rule"
125+ group_relation = rule .group_relation or "or"
126+ grouped_parents [rule .group_id ] = QTreeWidgetItem (
127+ [f"Rule: { group_name } ({ group_relation } )" ]
128+ )
129+ event_item .addChild (grouped_parents [rule .group_id ])
130+ parent_item = grouped_parents [rule .group_id ]
131+ else :
132+ if ungrouped_parent is None :
133+ ungrouped_parent = QTreeWidgetItem (["Ungrouped Rules" ])
134+ event_item .addChild (ungrouped_parent )
135+ parent_item = ungrouped_parent
136+
111137 rule_text = (
112138 f"{ event_id } | "
113139 f"{ rule .rule_type } | "
114140 f"{ rule .field_name } | "
115141 f"{ rule .condition } | "
116142 f"{ rule .value } "
117143 )
118- item = QListWidgetItem (rule_text )
144+ item = QTreeWidgetItem ([rule_text ])
145+ item .setData (0 , Qt .ItemDataRole .UserRole , (event_id , rule_index ))
119146
120147 if not rule .imported :
121- item .setBackground (QColor ("#ffe6cc" )) # light orange
148+ item .setBackground (0 , QColor ("#ffe6cc" )) # light orange
149+
150+ parent_item .addChild (item )
122151
123- self .rule_list .addItem (item )
124- self .displayed_rules .append ((event_id , rule_index ))
152+ event_item .setExpanded (True )
153+
154+ self .rule_tree .expandAll ()
125155
126156 def add_rule (self ) -> None :
127157 if self .current_event_id is None :
@@ -156,14 +186,15 @@ def remove_selected_rule(self) -> None:
156186 if self .current_event_id is None :
157187 return
158188
159- selected_row = self .rule_list . currentRow ()
160- if selected_row < 0 :
189+ selected_item = self .rule_tree . currentItem ()
190+ if selected_item is None :
161191 return
162192
163- if selected_row >= len (self .displayed_rules ):
193+ rule_key = selected_item .data (0 , Qt .ItemDataRole .UserRole )
194+ if not isinstance (rule_key , tuple ) or len (rule_key ) != 2 :
164195 return
165196
166- event_id , rule_index = self . displayed_rules [ selected_row ]
197+ event_id , rule_index = rule_key
167198 event_config = self .config .events .get (event_id )
168199
169200 if event_config is None :
@@ -172,4 +203,4 @@ def remove_selected_rule(self) -> None:
172203 if 0 <= rule_index < len (event_config .rules ):
173204 del event_config .rules [rule_index ]
174205
175- self .refresh_rules ()
206+ self .refresh_rules ()
0 commit comments