diff --git a/dokogen/ui.py b/dokogen/ui.py index bc909fb..f266f96 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -1003,41 +1003,27 @@ class DokoGenApp: sw_frame.columnconfigure(0, weight=1) # ===== Вкладка «Безопасность» ===== + # ===== Вкладка «Безопасность» — с вложенными подвкладками ===== sec_tab = ttk.Frame(nb2) nb2.add(sec_tab, text="Безопасность") sec_frame = ttk.Frame(sec_tab, padding=6) sec_frame.pack(fill=tk.BOTH, expand=True) - # --- Панель действий (сверху, всегда видна) --- - sec_actions = ttk.Frame(sec_frame) - 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) + nb_sec = ttk.Notebook(sec_frame) + nb_sec.pack(fill=tk.BOTH, expand=True) - # --- Две колонки: слева нарушитель+угрозы, справа остальное --- - sec_cols = ttk.Frame(sec_frame) - sec_cols.pack(fill=tk.BOTH, expand=True) - 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) + # --- Подвкладка 1: Основные данные (нарушитель + автозаполнение) --- + sec_main = ttk.Frame(nb_sec, padding=8) + nb_sec.add(sec_main, text="Основные данные") - # ===== Левая колонка ===== - ttk.Label(sec_left, text="Категория нарушителя:").pack(anchor=tk.W) - self.is_attacker_category_combo = ttk.Combobox(sec_left, state='readonly') - self.is_attacker_category_combo.pack(fill=tk.X, pady=(2, 2)) + ttk.Label(sec_main, text="Модель нарушителя (Методика ФСТЭК от 05.02.2021):", + font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=(0, 4)) + + 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: import json as _json _ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json') @@ -1047,9 +1033,11 @@ class DokoGenApp: except Exception: pass - ttk.Label(sec_left, text="Уровень возможностей нарушителя:").pack(anchor=tk.W) - self.is_attacker_level_combo = ttk.Combobox(sec_left, state='readonly') - self.is_attacker_level_combo.pack(fill=tk.X, pady=(2, 2)) + row2 = ttk.Frame(sec_main) + row2.pack(fill=tk.X, pady=3) + 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: import json as _json _al = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_levels.json') @@ -1060,10 +1048,10 @@ class DokoGenApp: except Exception: pass - ttk.Label(sec_left, text="Виды нарушителей:").pack(anchor=tk.W) - sec_at_row = ttk.Frame(sec_left) - sec_at_row.pack(fill=tk.X, pady=(2, 4)) - self.is_attacker_types_listbox = tk.Listbox(sec_at_row, height=3, exportselection=False) + ttk.Label(sec_main, text="Виды нарушителей:").pack(anchor=tk.W, pady=(6, 2)) + sec_at_row = ttk.Frame(sec_main) + sec_at_row.pack(fill=tk.BOTH, expand=True) + 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) sec_at_btn = ttk.Frame(sec_at_row) sec_at_btn.pack(side=tk.RIGHT, padx=(4, 0)) @@ -1072,19 +1060,48 @@ class DokoGenApp: 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) - ttk.Label(sec_left, text="Основные угрозы БИ:").pack(anchor=tk.W) - sec_thr_row = ttk.Frame(sec_left) - sec_thr_row.pack(fill=tk.BOTH, expand=True, pady=(2, 4)) - self.is_threats_listbox = tk.Listbox(sec_thr_row, height=7, exportselection=False) + sec_main_btns = ttk.Frame(sec_main) + sec_main_btns.pack(fill=tk.X, pady=(8, 0)) + ttk.Button(sec_main_btns, text="⚡ Автозаполнить (нарушитель, угрозы, меры)", + 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) - sec_thr_btn = ttk.Frame(sec_thr_row) - sec_thr_btn.pack(side=tk.RIGHT, padx=(4, 0)) - ttk.Button(sec_thr_btn, text="- Удалить", + thr_btn = ttk.Frame(thr_row) + thr_btn.pack(side=tk.RIGHT, padx=(4, 0)) + ttk.Button(thr_btn, text="- Удалить", 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) - # ===== Правая колонка ===== + # --- Подвкладка 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): """Ряд: label + listbox + кнопки (+/-/✏️/📚).""" ttk.Label(parent, text=label).pack(anchor=tk.W) @@ -1096,7 +1113,6 @@ class DokoGenApp: btns = ttk.Frame(row) btns.pack(side=tk.RIGHT, padx=(4, 0)) if allow_add or not dict_name: - # ручное добавление: если разрешено или нет словаря ttk.Button(btns, text="+", width=2, command=lambda l=lb: self._add_sw_item(l)).pack(pady=1) ttk.Button(btns, text="-", width=2, @@ -1107,18 +1123,23 @@ class DokoGenApp: ttk.Button(btns, text="📚", width=3, command=lambda l=lb, d=dict_name: self._select_sw_from_dict(l, d)).pack(pady=1) - _sec_row(sec_right, "Типы компьютерных инцидентов:", - 'is_incidents_listbox', 'incidents.json', height=2) - _sec_row(sec_right, "Показатели критериев значимости:", - 'is_criteria_values_listbox', 'criteria_indicators.json', height=2) - ttk.Button(sec_right, text="📋 Заполнить по таблице ПП № 127", + _sec_row(sec_inc, "Типы компьютерных инцидентов:", + 'is_incidents_listbox', 'incidents.json', height=3) + _sec_row(sec_inc, "Показатели критериев значимости:", + 'is_criteria_values_listbox', 'criteria_indicators.json', height=3) + ttk.Button(sec_inc, text="📋 Заполнить по таблице ПП № 127", command=self._open_criteria_127_dialog).pack(anchor=tk.W, pady=(0, 4)) - _sec_row(sec_right, "Обоснование значений показателей:", - 'is_criteria_justification_listbox', None, height=2) - _sec_row(sec_right, "Организационные меры:", - 'is_org_measures_listbox', 'org_measures.json', height=2, allow_add=True) - _sec_row(sec_right, "Технические меры:", - 'is_tech_measures_listbox', 'tech_measures.json', height=2, allow_add=True) + _sec_row(sec_inc, "Обоснование значений показателей:", + 'is_criteria_justification_listbox', None, height=3) + + # --- Подвкладка 4: Организационные и технические меры --- + sec_meas = ttk.Frame(nb_sec, padding=8) + 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)