feat: вкладка «Безопасность» — коды УБИ, автоподбор угроз (программа/ИИ), сравнение, новый UI
- угрозы ФСТЭК теперь с кодами: «УБИ.001 Угроза …» (парсинг кода из БДУ, 227 шт) - threat_rules.py — логика программы: по характеристикам объекта (тип ИС/АСУ/ИТКС, интернет да/нет, нарушитель внутр./внешн.) фильтрует перечень по ключевым словам - verify.py: suggest_threats_ai — ИИ предлагает актуальные угрозы по описанию объекта - вкладка перестроена: компактная 2-колоночная раскладка, панель действий сверху: «⬇️ Загрузить угрозы ФСТЭК» | «⚙️ Подобрать (программа)» | «🤖 Подобрать (ИИ)» | «⚖️ Сравнить» | «🤖 Проверить безопасность через ИИ» - окно выбора из словаря: 720x680 + поле поиска (фильтр по тексту, важно для 227 угроз) - сравнение программа↔ИИ: окно с двумя списками, совпавшие подсвечены зелёным
This commit is contained in:
+224
-106
@@ -944,13 +944,37 @@ class DokoGenApp:
|
||||
# ===== Вкладка «Безопасность» =====
|
||||
sec_tab = ttk.Frame(nb2)
|
||||
nb2.add(sec_tab, text="Безопасность")
|
||||
sec_frame = ttk.Frame(sec_tab, padding=8)
|
||||
sec_frame = ttk.Frame(sec_tab, padding=6)
|
||||
sec_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# --- Категория нарушителя (словарик) ---
|
||||
ttk.Label(sec_frame, text="Категория нарушителя:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||
self.is_attacker_category_combo = ttk.Combobox(sec_frame, width=68, state='readonly')
|
||||
self.is_attacker_category_combo.grid(row=0, column=1, padx=8, sticky=tk.EW, pady=1)
|
||||
# --- Панель действий (сверху, всегда видна) ---
|
||||
sec_actions = ttk.Frame(sec_frame)
|
||||
sec_actions.pack(fill=tk.X, pady=(0, 6))
|
||||
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)
|
||||
|
||||
# --- Две колонки: слева нарушитель+угрозы, справа остальное ---
|
||||
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)
|
||||
|
||||
# ===== Левая колонка =====
|
||||
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, 4))
|
||||
try:
|
||||
import json as _json
|
||||
_ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json')
|
||||
@@ -959,25 +983,27 @@ class DokoGenApp:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# --- Характеристика нарушителя ---
|
||||
ttk.Label(sec_frame, text="Характеристика нарушителя:").grid(row=1, column=0, sticky=tk.NW, pady=2)
|
||||
self.is_attacker_characteristics_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
|
||||
self.is_attacker_characteristics_listbox.grid(row=1, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_char_btn = ttk.Frame(sec_frame)
|
||||
sec_char_btn.grid(row=1, column=2, sticky=tk.N, padx=(4, 0))
|
||||
ttk.Button(sec_char_btn, text="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_char_btn, text="- Удалить",
|
||||
command=lambda: self._del_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_char_btn, text="✏️ Редактировать",
|
||||
command=lambda: self._edit_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Label(sec_left, text="Характеристика нарушителя:").pack(anchor=tk.W)
|
||||
sec_char_row = ttk.Frame(sec_left)
|
||||
sec_char_row.pack(fill=tk.X, pady=(2, 4))
|
||||
self.is_attacker_characteristics_listbox = tk.Listbox(sec_char_row, height=2, exportselection=False)
|
||||
self.is_attacker_characteristics_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
sec_char_btn = ttk.Frame(sec_char_row)
|
||||
sec_char_btn.pack(side=tk.RIGHT, padx=(4, 0))
|
||||
ttk.Button(sec_char_btn, text="+", width=2,
|
||||
command=lambda: self._add_sw_item(self.is_attacker_characteristics_listbox)).pack(pady=1)
|
||||
ttk.Button(sec_char_btn, text="-", width=2,
|
||||
command=lambda: self._del_sw_item(self.is_attacker_characteristics_listbox)).pack(pady=1)
|
||||
ttk.Button(sec_char_btn, text="✏️", width=3,
|
||||
command=lambda: self._edit_sw_item(self.is_attacker_characteristics_listbox)).pack(pady=1)
|
||||
|
||||
# --- Основные угрозы БИ (словарик + загрузка с ФСТЭК) ---
|
||||
ttk.Label(sec_frame, text="Основные угрозы БИ:").grid(row=2, column=0, sticky=tk.W, pady=2)
|
||||
self.is_threats_listbox = tk.Listbox(sec_frame, height=5, exportselection=False)
|
||||
self.is_threats_listbox.grid(row=2, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_thr_btn = ttk.Frame(sec_frame)
|
||||
sec_thr_btn.grid(row=2, column=2, sticky=tk.N, padx=(4, 0))
|
||||
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)
|
||||
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="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_thr_btn, text="- Удалить",
|
||||
@@ -986,90 +1012,38 @@ class DokoGenApp:
|
||||
command=lambda: self._edit_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_thr_btn, text="📚 Из словаря",
|
||||
command=lambda: self._select_sw_from_dict(self.is_threats_listbox, 'threats_fstec.json')).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_thr_btn, text="⬇️ Загрузить угрозы ФСТЭК",
|
||||
command=self._download_fstec_threats).pack(fill=tk.X, pady=1)
|
||||
self._threats_date_label = ttk.Label(sec_frame, text="", foreground='gray')
|
||||
self._threats_date_label.grid(row=3, column=1, sticky=tk.W, padx=8, pady=(0, 2))
|
||||
|
||||
# --- Типы компьютерных инцидентов (словарик) ---
|
||||
ttk.Label(sec_frame, text="Типы компьютерных инцидентов:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
self.is_incidents_listbox = tk.Listbox(sec_frame, height=4, exportselection=False)
|
||||
self.is_incidents_listbox.grid(row=4, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_inc_btn = ttk.Frame(sec_frame)
|
||||
sec_inc_btn.grid(row=4, column=2, sticky=tk.N, padx=(4, 0))
|
||||
ttk.Button(sec_inc_btn, text="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_inc_btn, text="- Удалить",
|
||||
command=lambda: self._del_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_inc_btn, text="✏️ Редактировать",
|
||||
command=lambda: self._edit_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_inc_btn, text="📚 Из словаря",
|
||||
command=lambda: self._select_sw_from_dict(self.is_incidents_listbox, 'incidents.json')).pack(fill=tk.X, pady=1)
|
||||
# ===== Правая колонка =====
|
||||
def _sec_row(parent, label, attr, dict_name=None, height=2):
|
||||
"""Ряд: label + listbox + кнопки (+/-/✏️/📚)."""
|
||||
ttk.Label(parent, text=label).pack(anchor=tk.W)
|
||||
row = ttk.Frame(parent)
|
||||
row.pack(fill=tk.X, pady=(2, 4))
|
||||
lb = tk.Listbox(row, height=height, exportselection=False)
|
||||
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
setattr(self, attr, lb)
|
||||
btns = ttk.Frame(row)
|
||||
btns.pack(side=tk.RIGHT, padx=(4, 0))
|
||||
ttk.Button(btns, text="+", width=2,
|
||||
command=lambda l=lb: self._add_sw_item(l)).pack(pady=1)
|
||||
ttk.Button(btns, text="-", width=2,
|
||||
command=lambda l=lb: self._del_sw_item(l)).pack(pady=1)
|
||||
ttk.Button(btns, text="✏️", width=3,
|
||||
command=lambda l=lb: self._edit_sw_item(l)).pack(pady=1)
|
||||
if dict_name:
|
||||
ttk.Button(btns, text="📚", width=3,
|
||||
command=lambda l=lb, d=dict_name: self._select_sw_from_dict(l, d)).pack(pady=1)
|
||||
|
||||
# --- Показатели критериев значимости (словарик) ---
|
||||
ttk.Label(sec_frame, text="Показатели критериев значимости:").grid(row=5, column=0, sticky=tk.W, pady=2)
|
||||
self.is_criteria_values_listbox = tk.Listbox(sec_frame, height=4, exportselection=False)
|
||||
self.is_criteria_values_listbox.grid(row=5, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_crit_btn = ttk.Frame(sec_frame)
|
||||
sec_crit_btn.grid(row=5, column=2, sticky=tk.N, padx=(4, 0))
|
||||
ttk.Button(sec_crit_btn, text="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_crit_btn, text="- Удалить",
|
||||
command=lambda: self._del_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_crit_btn, text="✏️ Редактировать",
|
||||
command=lambda: self._edit_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_crit_btn, text="📚 Из словаря",
|
||||
command=lambda: self._select_sw_from_dict(self.is_criteria_values_listbox, 'criteria_indicators.json')).pack(fill=tk.X, pady=1)
|
||||
|
||||
# --- Обоснование значений показателей ---
|
||||
ttk.Label(sec_frame, text="Обоснование значений показателей:").grid(row=6, column=0, sticky=tk.NW, pady=2)
|
||||
self.is_criteria_justification_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
|
||||
self.is_criteria_justification_listbox.grid(row=6, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_just_btn = ttk.Frame(sec_frame)
|
||||
sec_just_btn.grid(row=6, column=2, sticky=tk.N, padx=(4, 0))
|
||||
ttk.Button(sec_just_btn, text="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_just_btn, text="- Удалить",
|
||||
command=lambda: self._del_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_just_btn, text="✏️ Редактировать",
|
||||
command=lambda: self._edit_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1)
|
||||
|
||||
# --- Организационные меры (словарик) ---
|
||||
ttk.Label(sec_frame, text="Организационные меры:").grid(row=7, column=0, sticky=tk.W, pady=2)
|
||||
self.is_org_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
|
||||
self.is_org_measures_listbox.grid(row=7, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_org_btn = ttk.Frame(sec_frame)
|
||||
sec_org_btn.grid(row=7, column=2, sticky=tk.N, padx=(4, 0))
|
||||
ttk.Button(sec_org_btn, text="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_org_btn, text="- Удалить",
|
||||
command=lambda: self._del_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_org_btn, text="✏️ Редактировать",
|
||||
command=lambda: self._edit_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_org_btn, text="📚 Из словаря",
|
||||
command=lambda: self._select_sw_from_dict(self.is_org_measures_listbox, 'org_measures.json')).pack(fill=tk.X, pady=1)
|
||||
|
||||
# --- Технические меры (словарик) ---
|
||||
ttk.Label(sec_frame, text="Технические меры:").grid(row=8, column=0, sticky=tk.W, pady=2)
|
||||
self.is_tech_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False)
|
||||
self.is_tech_measures_listbox.grid(row=8, column=1, sticky=tk.EW, padx=(8, 4), pady=1)
|
||||
sec_tech_btn = ttk.Frame(sec_frame)
|
||||
sec_tech_btn.grid(row=8, column=2, sticky=tk.N, padx=(4, 0))
|
||||
ttk.Button(sec_tech_btn, text="+ Добавить",
|
||||
command=lambda: self._add_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_tech_btn, text="- Удалить",
|
||||
command=lambda: self._del_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_tech_btn, text="✏️ Редактировать",
|
||||
command=lambda: self._edit_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1)
|
||||
ttk.Button(sec_tech_btn, text="📚 Из словаря",
|
||||
command=lambda: self._select_sw_from_dict(self.is_tech_measures_listbox, 'tech_measures.json')).pack(fill=tk.X, pady=1)
|
||||
|
||||
# --- Кнопка ИИ-проверки ---
|
||||
ttk.Button(sec_frame, text="🤖 Проверить безопасность через ИИ",
|
||||
command=self._check_security_ai).grid(
|
||||
row=9, column=0, columnspan=3, sticky=tk.W, pady=(10, 0))
|
||||
|
||||
sec_frame.columnconfigure(1, weight=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)
|
||||
_sec_row(sec_right, "Обоснование значений показателей:",
|
||||
'is_criteria_justification_listbox', None, height=2)
|
||||
_sec_row(sec_right, "Организационные меры:",
|
||||
'is_org_measures_listbox', 'org_measures.json', height=2)
|
||||
_sec_row(sec_right, "Технические меры:",
|
||||
'is_tech_measures_listbox', 'tech_measures.json', height=2)
|
||||
|
||||
# ===== Вкладка «Средства защиты» =====
|
||||
def_tab = ttk.Frame(nb2)
|
||||
@@ -1800,6 +1774,132 @@ class DokoGenApp:
|
||||
text="⚠ Список угроз ещё не скачан — нажмите «⬇️ Загрузить угрозы ФСТЭК»",
|
||||
foreground='#c62828')
|
||||
|
||||
def _suggest_threats_program_click(self):
|
||||
"""Подобрать угрозы логикой программы по характеристикам объекта."""
|
||||
if self._current_is_idx is None:
|
||||
messagebox.showinfo("Угрозы", "Сначала выберите объект КИИ в списке слева")
|
||||
return
|
||||
self._collect_is()
|
||||
isys = self.model.information_systems[self._current_is_idx]
|
||||
try:
|
||||
from .threat_rules import suggest_threats_program
|
||||
threats = suggest_threats_program(isys, limit=20)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Ошибка", str(e))
|
||||
return
|
||||
if not threats:
|
||||
messagebox.showinfo("Угрозы", "Не удалось подобрать угрозы.\n"
|
||||
"Скачайте перечень ФСТЭК: кнопка «⬇️ Загрузить угрозы ФСТЭК».")
|
||||
return
|
||||
lb = self.is_threats_listbox
|
||||
lb.delete(0, tk.END)
|
||||
for t in threats:
|
||||
lb.insert(tk.END, t)
|
||||
self.log(f"⚙️ Угрозы подобраны программой: {len(threats)} шт.")
|
||||
|
||||
def _suggest_threats_ai_click(self):
|
||||
"""Подобрать угрозы через ИИ по характеристикам объекта."""
|
||||
if self._current_is_idx is None:
|
||||
messagebox.showinfo("Угрозы", "Сначала выберите объект КИИ в списке слева")
|
||||
return
|
||||
settings = load_api_settings()
|
||||
if not settings.get('ai_api_key'):
|
||||
messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»")
|
||||
return
|
||||
self._collect_is()
|
||||
isys = self.model.information_systems[self._current_is_idx]
|
||||
from .threat_rules import _has_internet
|
||||
data = {
|
||||
'name': isys.name or '',
|
||||
'description': isys.description or '',
|
||||
'object_type': isys.object_type or '',
|
||||
'sphere': isys.sphere or '',
|
||||
'architecture': isys.architecture or '',
|
||||
'net_category': isys.net_category or '',
|
||||
'attacker_category': isys.attacker_category or '',
|
||||
'has_internet': _has_internet(isys),
|
||||
}
|
||||
self.log(f"🤖 Запрос к ИИ: подбор угроз для «{isys.name}»")
|
||||
self._verify_log("Подбор угроз через ИИ…")
|
||||
|
||||
def _run():
|
||||
try:
|
||||
from .verify import suggest_threats_ai
|
||||
threats, err = suggest_threats_ai(
|
||||
data, settings['ai_api_key'], settings.get('ai_model'), settings.get('ai_base_url'))
|
||||
self.root.after(0, lambda: self._suggest_threats_ai_done(threats, err))
|
||||
except Exception as e:
|
||||
self.root.after(0, lambda: self._suggest_threats_ai_done(None, str(e)))
|
||||
|
||||
threading.Thread(target=_run, daemon=True).start()
|
||||
|
||||
def _suggest_threats_ai_done(self, threats, err):
|
||||
if err:
|
||||
self._verify_log(f"❌ {err}")
|
||||
self.log(f"❌ Подбор угроз ИИ: {err}")
|
||||
return
|
||||
if not threats:
|
||||
self._verify_log("❌ ИИ не вернул угрозы")
|
||||
return
|
||||
lb = self.is_threats_listbox
|
||||
lb.delete(0, tk.END)
|
||||
for t in threats:
|
||||
lb.insert(tk.END, t)
|
||||
self.log(f"🤖 ИИ подобрал угроз: {len(threats)}")
|
||||
self._verify_log(f"🤖 ИИ: {len(threats)} угроз")
|
||||
|
||||
def _compare_threats(self):
|
||||
"""Сравнить угрозы, подобранные программой и ИИ."""
|
||||
if self._current_is_idx is None:
|
||||
messagebox.showinfo("Сравнение", "Сначала выберите объект КИИ в списке слева")
|
||||
return
|
||||
self._collect_is()
|
||||
isys = self.model.information_systems[self._current_is_idx]
|
||||
from .threat_rules import suggest_threats_program
|
||||
program = suggest_threats_program(isys, limit=20)
|
||||
current = [str(self.is_threats_listbox.get(i)) for i in range(self.is_threats_listbox.size())]
|
||||
if not current:
|
||||
messagebox.showinfo("Сравнение", "Сначала подберите угрозы (программой или ИИ).")
|
||||
return
|
||||
|
||||
# Показываем окно: слева подобрано программой, справа текущий список
|
||||
dlg = tk.Toplevel(self.root)
|
||||
dlg.title("Сравнение угроз: программа ↔ ИИ/текущий")
|
||||
dlg.geometry("760x420")
|
||||
dlg.transient(self.root)
|
||||
|
||||
frm = ttk.Frame(dlg, padding=8)
|
||||
frm.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
left = ttk.Frame(frm)
|
||||
left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
ttk.Label(left, text="⚙️ Программа", font=('Arial', 9, 'bold')).pack(anchor=tk.W)
|
||||
lb_p = tk.Listbox(left, height=14)
|
||||
lb_p.pack(fill=tk.BOTH, expand=True, pady=4)
|
||||
for t in program:
|
||||
lb_p.insert(tk.END, t)
|
||||
|
||||
right = ttk.Frame(frm)
|
||||
right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0))
|
||||
ttk.Label(right, text="🤖 ИИ / текущий список", font=('Arial', 9, 'bold')).pack(anchor=tk.W)
|
||||
lb_c = tk.Listbox(right, height=14)
|
||||
lb_c.pack(fill=tk.BOTH, expand=True, pady=4)
|
||||
for t in current:
|
||||
lb_c.insert(tk.END, t)
|
||||
|
||||
# Подсветка совпадений
|
||||
pset = set(program)
|
||||
for i in range(lb_p.size()):
|
||||
if lb_p.get(i) in set(current):
|
||||
lb_p.itemconfig(i, bg='#c8e6c9')
|
||||
for i in range(lb_c.size()):
|
||||
if lb_c.get(i) in pset:
|
||||
lb_c.itemconfig(i, bg='#c8e6c9')
|
||||
|
||||
info = ttk.Label(frm, text="Зелёным выделены совпавшие угрозы.",
|
||||
foreground='gray')
|
||||
info.pack(anchor=tk.W, pady=(4, 0))
|
||||
|
||||
def _check_security_ai(self):
|
||||
"""Проверка вкладки «Безопасность» через ИИ (DeepSeek/OpenAI-совместимый API)."""
|
||||
if self._current_is_idx is None:
|
||||
@@ -1943,7 +2043,7 @@ class DokoGenApp:
|
||||
"""Открывает окно с чекбоксами для выбора элементов из списка."""
|
||||
dialog = tk.Toplevel(self.root)
|
||||
dialog.title(title)
|
||||
dialog.geometry("400x500")
|
||||
dialog.geometry("720x680")
|
||||
dialog.transient(self.root)
|
||||
dialog.grab_set()
|
||||
|
||||
@@ -1952,6 +2052,13 @@ class DokoGenApp:
|
||||
|
||||
ttk.Label(frame, text=title, font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=(0, 5))
|
||||
|
||||
# Поиск (важно для большого перечня угроз ФСТЭК)
|
||||
search_var = tk.StringVar()
|
||||
search_entry = ttk.Entry(frame, textvariable=search_var)
|
||||
search_entry.pack(fill=tk.X, pady=(0, 5))
|
||||
search_entry.insert(0, "")
|
||||
search_entry.focus_set()
|
||||
|
||||
# Уже выбранные элементы
|
||||
existing = set(listbox.get(0, tk.END))
|
||||
|
||||
@@ -1973,6 +2080,17 @@ class DokoGenApp:
|
||||
vars[item] = var
|
||||
items_map[item] = (var, cb)
|
||||
|
||||
# Фильтр по поиску: скрываем неподходящие чекбоксы
|
||||
def _apply_search(*_):
|
||||
q = search_var.get().strip().lower()
|
||||
for item, (var, cb) in items_map.items():
|
||||
if q and q not in item.lower():
|
||||
cb.pack_forget()
|
||||
else:
|
||||
cb.pack(anchor=tk.W, pady=1)
|
||||
|
||||
search_var.trace_add('write', _apply_search)
|
||||
|
||||
btn_frame = ttk.Frame(dialog, padding=10)
|
||||
btn_frame.pack(fill=tk.X)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user