feat: вкладка «Безопасность» — вложенные подвкладки (нормальное отображение)

Вместо тесной двухколоночной раскладки — ряд подвкладок:
1) Основные данные — модель нарушителя (категория, уровень Н1-Н4, виды)
   + кнопки « Автозаполнить» и «🤖 Проверить безопасность через ИИ»
2) Угрозы — панель действий (⬇️ ФСТЭК / ⚙️ программа / 🤖 ИИ / ⚖️ сравнить)
   + большой список угроз на всю ширину
3) Инциденты и критерии — инциденты, показатели критериев,
   кнопка «📋 ПП № 127», обоснование
4) Орг. и тех. меры — оба списка с нормальной высотой

Все виджеты и маппинги сохранены (is_*_listbox/combo на месте)
This commit is contained in:
2026-08-04 23:54:26 +04:00
parent 220b923e76
commit 5544941469
+77 -56
View File
@@ -1003,41 +1003,27 @@ class DokoGenApp:
sw_frame.columnconfigure(0, weight=1) sw_frame.columnconfigure(0, weight=1)
# ===== Вкладка «Безопасность» ===== # ===== Вкладка «Безопасность» =====
# ===== Вкладка «Безопасность» — с вложенными подвкладками =====
sec_tab = ttk.Frame(nb2) sec_tab = ttk.Frame(nb2)
nb2.add(sec_tab, text="Безопасность") nb2.add(sec_tab, text="Безопасность")
sec_frame = ttk.Frame(sec_tab, padding=6) sec_frame = ttk.Frame(sec_tab, padding=6)
sec_frame.pack(fill=tk.BOTH, expand=True) sec_frame.pack(fill=tk.BOTH, expand=True)
# --- Панель действий (сверху, всегда видна) --- nb_sec = ttk.Notebook(sec_frame)
sec_actions = ttk.Frame(sec_frame) nb_sec.pack(fill=tk.BOTH, expand=True)
sec_actions.pack(fill=tk.X, pady=(0, 6))
ttk.Button(sec_actions, text="⚡ Автозаполнить",
command=self._auto_fill_security_click).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_actions, text="⬇️ Загрузить угрозы ФСТЭК",
command=self._download_fstec_threats).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_actions, text="⚙️ Подобрать угрозы (программа)",
command=self._suggest_threats_program_click).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_actions, text="🤖 Подобрать угрозы (ИИ)",
command=self._suggest_threats_ai_click).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_actions, text="⚖️ Сравнить",
command=self._compare_threats).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_actions, text="🤖 Проверить безопасность через ИИ",
command=self._check_security_ai).pack(side=tk.LEFT, padx=2)
self._threats_date_label = ttk.Label(sec_actions, text="", foreground='gray')
self._threats_date_label.pack(side=tk.LEFT, padx=8)
# --- Две колонки: слева нарушитель+угрозы, справа остальное --- # --- Подвкладка 1: Основные данные (нарушитель + автозаполнение) ---
sec_cols = ttk.Frame(sec_frame) sec_main = ttk.Frame(nb_sec, padding=8)
sec_cols.pack(fill=tk.BOTH, expand=True) nb_sec.add(sec_main, text="Основные данные")
sec_left = ttk.Frame(sec_cols)
sec_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 6))
sec_right = ttk.Frame(sec_cols)
sec_right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# ===== Левая колонка ===== ttk.Label(sec_main, text="Модель нарушителя (Методика ФСТЭК от 05.02.2021):",
ttk.Label(sec_left, text="Категория нарушителя:").pack(anchor=tk.W) font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=(0, 4))
self.is_attacker_category_combo = ttk.Combobox(sec_left, state='readonly')
self.is_attacker_category_combo.pack(fill=tk.X, pady=(2, 2)) row1 = ttk.Frame(sec_main)
row1.pack(fill=tk.X, pady=3)
ttk.Label(row1, text="Категория нарушителя:", width=26).pack(side=tk.LEFT)
self.is_attacker_category_combo = ttk.Combobox(row1, state='readonly', width=50)
self.is_attacker_category_combo.pack(side=tk.LEFT, fill=tk.X, expand=True)
try: try:
import json as _json import json as _json
_ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json') _ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json')
@@ -1047,9 +1033,11 @@ class DokoGenApp:
except Exception: except Exception:
pass pass
ttk.Label(sec_left, text="Уровень возможностей нарушителя:").pack(anchor=tk.W) row2 = ttk.Frame(sec_main)
self.is_attacker_level_combo = ttk.Combobox(sec_left, state='readonly') row2.pack(fill=tk.X, pady=3)
self.is_attacker_level_combo.pack(fill=tk.X, pady=(2, 2)) ttk.Label(row2, text="Уровень возможностей нарушителя:", width=26).pack(side=tk.LEFT)
self.is_attacker_level_combo = ttk.Combobox(row2, state='readonly', width=50)
self.is_attacker_level_combo.pack(side=tk.LEFT, fill=tk.X, expand=True)
try: try:
import json as _json import json as _json
_al = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_levels.json') _al = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_levels.json')
@@ -1060,10 +1048,10 @@ class DokoGenApp:
except Exception: except Exception:
pass pass
ttk.Label(sec_left, text="Виды нарушителей:").pack(anchor=tk.W) ttk.Label(sec_main, text="Виды нарушителей:").pack(anchor=tk.W, pady=(6, 2))
sec_at_row = ttk.Frame(sec_left) sec_at_row = ttk.Frame(sec_main)
sec_at_row.pack(fill=tk.X, pady=(2, 4)) sec_at_row.pack(fill=tk.BOTH, expand=True)
self.is_attacker_types_listbox = tk.Listbox(sec_at_row, height=3, exportselection=False) self.is_attacker_types_listbox = tk.Listbox(sec_at_row, height=6, exportselection=False)
self.is_attacker_types_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.is_attacker_types_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sec_at_btn = ttk.Frame(sec_at_row) sec_at_btn = ttk.Frame(sec_at_row)
sec_at_btn.pack(side=tk.RIGHT, padx=(4, 0)) sec_at_btn.pack(side=tk.RIGHT, padx=(4, 0))
@@ -1072,19 +1060,48 @@ class DokoGenApp:
ttk.Button(sec_at_btn, text="📚", width=3, ttk.Button(sec_at_btn, text="📚", width=3,
command=lambda: self._select_sw_from_dict(self.is_attacker_types_listbox, 'attacker_types.json')).pack(pady=1) command=lambda: self._select_sw_from_dict(self.is_attacker_types_listbox, 'attacker_types.json')).pack(pady=1)
ttk.Label(sec_left, text="Основные угрозы БИ:").pack(anchor=tk.W) sec_main_btns = ttk.Frame(sec_main)
sec_thr_row = ttk.Frame(sec_left) sec_main_btns.pack(fill=tk.X, pady=(8, 0))
sec_thr_row.pack(fill=tk.BOTH, expand=True, pady=(2, 4)) ttk.Button(sec_main_btns, text="⚡ Автозаполнить (нарушитель, угрозы, меры)",
self.is_threats_listbox = tk.Listbox(sec_thr_row, height=7, exportselection=False) command=self._auto_fill_security_click).pack(side=tk.LEFT, padx=2)
ttk.Button(sec_main_btns, text="🤖 Проверить безопасность через ИИ",
command=self._check_security_ai).pack(side=tk.LEFT, padx=2)
ttk.Label(sec_main_btns,
text="Подбор по данным объекта — как рекомендация, оператор подтверждает.",
foreground='gray').pack(side=tk.LEFT, padx=8)
# --- Подвкладка 2: Угрозы ---
sec_threats = ttk.Frame(nb_sec, padding=8)
nb_sec.add(sec_threats, text="Угрозы")
thr_actions = ttk.Frame(sec_threats)
thr_actions.pack(fill=tk.X, pady=(0, 6))
ttk.Button(thr_actions, text="⬇️ Загрузить угрозы ФСТЭК",
command=self._download_fstec_threats).pack(side=tk.LEFT, padx=2)
ttk.Button(thr_actions, text="⚙️ Подобрать (программа)",
command=self._suggest_threats_program_click).pack(side=tk.LEFT, padx=2)
ttk.Button(thr_actions, text="🤖 Подобрать (ИИ)",
command=self._suggest_threats_ai_click).pack(side=tk.LEFT, padx=2)
ttk.Button(thr_actions, text="⚖️ Сравнить программа ↔ ИИ",
command=self._compare_threats).pack(side=tk.LEFT, padx=2)
self._threats_date_label = ttk.Label(thr_actions, text="", foreground='gray')
self._threats_date_label.pack(side=tk.LEFT, padx=8)
thr_row = ttk.Frame(sec_threats)
thr_row.pack(fill=tk.BOTH, expand=True)
self.is_threats_listbox = tk.Listbox(thr_row, height=10, exportselection=False)
self.is_threats_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.is_threats_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
sec_thr_btn = ttk.Frame(sec_thr_row) thr_btn = ttk.Frame(thr_row)
sec_thr_btn.pack(side=tk.RIGHT, padx=(4, 0)) thr_btn.pack(side=tk.RIGHT, padx=(4, 0))
ttk.Button(sec_thr_btn, text="- Удалить", ttk.Button(thr_btn, text="- Удалить",
command=lambda: self._del_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) command=lambda: self._del_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1)
ttk.Button(sec_thr_btn, text="📚 Из словаря", ttk.Button(thr_btn, text="📚 Из словаря",
command=lambda: self._select_sw_from_dict(self.is_threats_listbox, 'threats_fstec.json')).pack(fill=tk.X, pady=1) command=lambda: self._select_sw_from_dict(self.is_threats_listbox, 'threats_fstec.json')).pack(fill=tk.X, pady=1)
# ===== Правая колонка ===== # --- Подвкладка 3: Инциденты и критерии ---
sec_inc = ttk.Frame(nb_sec, padding=8)
nb_sec.add(sec_inc, text="Инциденты и критерии")
def _sec_row(parent, label, attr, dict_name=None, height=2, allow_add=False): def _sec_row(parent, label, attr, dict_name=None, height=2, allow_add=False):
"""Ряд: label + listbox + кнопки (+/-/✏️/📚).""" """Ряд: label + listbox + кнопки (+/-/✏️/📚)."""
ttk.Label(parent, text=label).pack(anchor=tk.W) ttk.Label(parent, text=label).pack(anchor=tk.W)
@@ -1096,7 +1113,6 @@ class DokoGenApp:
btns = ttk.Frame(row) btns = ttk.Frame(row)
btns.pack(side=tk.RIGHT, padx=(4, 0)) btns.pack(side=tk.RIGHT, padx=(4, 0))
if allow_add or not dict_name: if allow_add or not dict_name:
# ручное добавление: если разрешено или нет словаря
ttk.Button(btns, text="+", width=2, ttk.Button(btns, text="+", width=2,
command=lambda l=lb: self._add_sw_item(l)).pack(pady=1) command=lambda l=lb: self._add_sw_item(l)).pack(pady=1)
ttk.Button(btns, text="-", width=2, ttk.Button(btns, text="-", width=2,
@@ -1107,18 +1123,23 @@ class DokoGenApp:
ttk.Button(btns, text="📚", width=3, ttk.Button(btns, text="📚", width=3,
command=lambda l=lb, d=dict_name: self._select_sw_from_dict(l, d)).pack(pady=1) command=lambda l=lb, d=dict_name: self._select_sw_from_dict(l, d)).pack(pady=1)
_sec_row(sec_right, "Типы компьютерных инцидентов:", _sec_row(sec_inc, "Типы компьютерных инцидентов:",
'is_incidents_listbox', 'incidents.json', height=2) 'is_incidents_listbox', 'incidents.json', height=3)
_sec_row(sec_right, "Показатели критериев значимости:", _sec_row(sec_inc, "Показатели критериев значимости:",
'is_criteria_values_listbox', 'criteria_indicators.json', height=2) 'is_criteria_values_listbox', 'criteria_indicators.json', height=3)
ttk.Button(sec_right, text="📋 Заполнить по таблице ПП № 127", ttk.Button(sec_inc, text="📋 Заполнить по таблице ПП № 127",
command=self._open_criteria_127_dialog).pack(anchor=tk.W, pady=(0, 4)) command=self._open_criteria_127_dialog).pack(anchor=tk.W, pady=(0, 4))
_sec_row(sec_right, "Обоснование значений показателей:", _sec_row(sec_inc, "Обоснование значений показателей:",
'is_criteria_justification_listbox', None, height=2) 'is_criteria_justification_listbox', None, height=3)
_sec_row(sec_right, "Организационные меры:",
'is_org_measures_listbox', 'org_measures.json', height=2, allow_add=True) # --- Подвкладка 4: Организационные и технические меры ---
_sec_row(sec_right, "Технические меры:", sec_meas = ttk.Frame(nb_sec, padding=8)
'is_tech_measures_listbox', 'tech_measures.json', height=2, allow_add=True) nb_sec.add(sec_meas, text="Орг. и тех. меры")
_sec_row(sec_meas, "Организационные меры:",
'is_org_measures_listbox', 'org_measures.json', height=5, allow_add=True)
_sec_row(sec_meas, "Технические меры:",
'is_tech_measures_listbox', 'tech_measures.json', height=5, allow_add=True)
# ===== Вкладка «Средства защиты» ===== # ===== Вкладка «Средства защиты» =====
def_tab = ttk.Frame(nb2) def_tab = ttk.Frame(nb2)