diff --git a/dokogen/threat_rules.py b/dokogen/threat_rules.py index b1561dd..5eb929f 100644 --- a/dokogen/threat_rules.py +++ b/dokogen/threat_rules.py @@ -11,6 +11,24 @@ import os import json +import re + + +def _ubi_num(text): + """Код УБИ из строки → int: «УБИ.069 Название» → 69. None, если кода нет.""" + m = re.search(r'уби\.?\s*(\d+)', str(text), re.IGNORECASE) + return int(m.group(1)) if m else None + + +def sort_threats_by_ubi(items): + """Сортировка угроз по возрастанию кода УБИ (УБИ.001, УБИ.002, …). + + Строки без кода УБИ уходят в конец (по алфавиту). + """ + def _key(t): + n = _ubi_num(t) + return (0, n) if n is not None else (1, str(t).lower()) + return sorted(items, key=_key) def load_threats(dict_dir=None): @@ -244,9 +262,9 @@ def suggest_threats_program(isys, threats=None, limit=None): sc = sum(1 for kw in pos if kw in tl) scored.append((sc, t)) - # Сортируем по релевантности (сначала — с наибольшим совпадением) - scored.sort(key=lambda x: -x[0]) - result = [t for sc, t in scored if sc > 0] + # Сортируем по возрастанию кода УБИ (УБИ.001 → УБИ.211…) + scored = [t for sc, t in scored if sc > 0] + result = sort_threats_by_ubi(scored) if limit and len(result) > limit: result = result[:limit] diff --git a/dokogen/ui.py b/dokogen/ui.py index 8779e94..3e1a166 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -1908,9 +1908,12 @@ class DokoGenApp: w.set(val) elif isinstance(w, tk.Listbox): w.delete(0, tk.END) - for line in str(val).split('\n'): - if line.strip(): - w.insert(tk.END, line.strip()) + lines = [ln.strip() for ln in str(val).split('\n') if ln.strip()] + if model_field == 'threats': + from .threat_rules import sort_threats_by_ubi + lines = sort_threats_by_ubi(lines) + for line in lines: + w.insert(tk.END, line) elif isinstance(w, tk.Text): w.delete('1.0', tk.END) w.insert('1.0', val) @@ -2873,9 +2876,12 @@ class DokoGenApp: if key in fill and hasattr(self, attr): lb = getattr(self, attr) lb.delete(0, tk.END) - for line in fill[key].split('\n'): - if line.strip(): - lb.insert(tk.END, line.strip()) + lines = [ln.strip() for ln in fill[key].split('\n') if ln.strip()] + if key == 'threats': + from .threat_rules import sort_threats_by_ubi + lines = sort_threats_by_ubi(lines) + for line in lines: + lb.insert(tk.END, line) self._collect_is() # сохранить в модель filled = ', '.join({ @@ -2911,7 +2917,8 @@ class DokoGenApp: return lb = self.is_threats_listbox lb.delete(0, tk.END) - for t in threats: + from .threat_rules import sort_threats_by_ubi + for t in sort_threats_by_ubi(threats): lb.insert(tk.END, t) self._collect_is() # сохранить в модель — галочки в словаре будут стоять self.log(f"⚙️ Угрозы подобраны программой: {len(threats)} шт.") @@ -3286,6 +3293,12 @@ class DokoGenApp: existing.add(key) added += 1 if added: + # сортируем по возрастанию кода УБИ + from .threat_rules import sort_threats_by_ubi + items = [str(lb.get(i)) for i in range(lb.size())] + lb.delete(0, tk.END) + for t in sort_threats_by_ubi(items): + lb.insert(tk.END, t) self._collect_is() self.log(f"➕ Добавлено недостающих угроз (по словарю): {added}") else: @@ -3462,15 +3475,21 @@ class DokoGenApp: e = entries.get(key) return e.get('1.0', 'end-1c').strip() if e else '' - def _fill_listbox(lb, text): + def _fill_listbox(lb, text, sort_ubi=False): if not lb: return lb.delete(0, tk.END) import re as _re3 + lines = [] for line in _re3.split(r'[\n,;]+', text or ''): line = line.strip().lstrip('•-–— ') if line: - lb.insert(tk.END, line) + lines.append(line) + if sort_ubi: + from .threat_rules import sort_threats_by_ubi + lines = sort_threats_by_ubi(lines) + for line in lines: + lb.insert(tk.END, line) try: if _wanted('attacker_category'): @@ -3480,7 +3499,7 @@ class DokoGenApp: exact = next((v for v in values if v.lower() == val.lower()), None) self.is_attacker_category_combo.set(exact or val) if _wanted('threats'): - _fill_listbox(getattr(self, 'is_threats_listbox', None), _text('threats')) + _fill_listbox(getattr(self, 'is_threats_listbox', None), _text('threats'), sort_ubi=True) if _wanted('incidents'): _fill_listbox(getattr(self, 'is_incidents_listbox', None), _text('incidents')) if _wanted('criteria_values'):