Compare commits

...

3 Commits

Author SHA1 Message Date
853a441eb9 Update .gitignore and remove Visual Studio project file
- Add 'output/' directory to .gitignore
- Remove ComConfigCopy.pyproj Visual Studio project file
2025-02-16 02:25:51 +03:00
ece18aea4d Add About window to application menu
- Import and integrate AboutWindow class
- Create new "Help" menu with "About Program" option
- Add `open_about()` method to display About window
- Enhance application's help and information accessibility
2025-02-16 02:24:40 +03:00
625b6d5558 Improve first-run detection and settings initialization
- Enhance `settings_load()` function to handle missing settings and parameters
- Improve first-run detection in `check_first_run()` method
- Add more robust settings file validation
- Remove unused imports
- Simplify and clean up code comments
2025-02-16 02:04:04 +03:00
4 changed files with 160 additions and 53 deletions

3
.gitignore vendored
View File

@@ -4,6 +4,7 @@ Configs/Eltex MES2424 AC - Сеть FTTB 2G, доп.txt
Configs/конфиг доп 3750-52 с айпи 172.17.141.133 .txt Configs/конфиг доп 3750-52 с айпи 172.17.141.133 .txt
DALL·E 2024-12-29 01.01.02 - Square vector logo_ A clean and minimalistic app icon for serial port management software. The design prominently features a simplified rectangular CO.ico DALL·E 2024-12-29 01.01.02 - Square vector logo_ A clean and minimalistic app icon for serial port management software. The design prominently features a simplified rectangular CO.ico
test.py test.py
__pycache__/TFTPServer.cpython-312.pyc __pycache__/
Firmware/1.jpg Firmware/1.jpg
Firmware/2 Firmware/2
output/

View File

@@ -1,23 +1,19 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ------------------------------------------------------------
# Это программа для копирования конфигураций на коммутаторы # Это программа для копирования конфигураций на коммутаторы
# Программа использует библиотеку PySerial для работы с последовательными портами # ------------------------------------------------------------
# Программа использует библиотеку PyQt5 для создания графического интерфейса
# Программа использует библиотеку PyQt5.QtWidgets для создания графического интерфейса
# Программа использует библиотеку PyQt5.QtCore для создания графического интерфейса
# Программа использует библиотеку PyQt5.QtGui для создания графического интерфейса
# import argparse Использовался для получения аргументов из командной строки (не используется)
# import argparse Использовался для получения аргументов из командной строки # import platform Использовался для получения списка сетевых адаптеров (не используется)
# import platform Использовался для получения списка сетевых адаптеров # import subprocess Использовался для получения списка сетевых адаптеров (не используется)
# import subprocess Использовался для получения списка сетевых адаптеров # import socket не используется
import json import json
import logging import logging
import os import os
import re import re
import socket
import sys import sys
import threading import threading
import time import time
@@ -25,8 +21,6 @@ from getpass import getpass
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import tkinter as tk import tkinter as tk
from tkinter import ( from tkinter import (
Tk,
Frame,
StringVar, StringVar,
END, END,
BOTH, BOTH,
@@ -42,6 +36,7 @@ from tkinter import ttk
import serial import serial
import serial.tools.list_ports import serial.tools.list_ports
from serial.serialutil import SerialException from serial.serialutil import SerialException
from about_window import AboutWindow
# from TFTPServer import TFTPServerThread # from TFTPServer import TFTPServerThread
# Создаем необходимые папки # Создаем необходимые папки
@@ -82,21 +77,42 @@ def settings_load():
"block_size": 15, # Размер блока команд "block_size": 15, # Размер блока команд
"prompt": ">", # Используется для определения приглашения "prompt": ">", # Используется для определения приглашения
} }
# Создаем папку Settings, если её нет
os.makedirs("Settings", exist_ok=True)
if not os.path.exists(SETTINGS_FILE): if not os.path.exists(SETTINGS_FILE):
try: try:
# При первом запуске создаем новый файл настроек
with open(SETTINGS_FILE, "w", encoding="utf-8") as f: with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(default_settings, f, indent=4, ensure_ascii=False) json.dump(default_settings, f, indent=4, ensure_ascii=False)
logging.info("Файл настроек создан с настройками по умолчанию.") logging.info("Файл настроек создан с настройками по умолчанию.")
return default_settings
except Exception as e: except Exception as e:
logging.error(f"Ошибка при создании файла настроек: {e}", exc_info=True) logging.error(f"Ошибка при создании файла настроек: {e}", exc_info=True)
return default_settings return default_settings
try: try:
# Пытаемся загрузить существующие настройки
with open(SETTINGS_FILE, "r", encoding="utf-8") as f: with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
settings = json.load(f) settings = json.load(f)
# Проверяем наличие всех необходимых параметров
settings_changed = False
for key, value in default_settings.items(): for key, value in default_settings.items():
if key not in settings: if key not in settings:
settings[key] = value settings[key] = value
settings_changed = True
# Если были добавлены новые параметры, сохраняем обновленные настройки
if settings_changed:
try:
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(settings, f, indent=4, ensure_ascii=False)
logging.info("Файл настроек обновлен с новыми параметрами.")
except Exception as e:
logging.error(f"Ошибка при обновлении файла настроек: {e}", exc_info=True)
return settings return settings
except Exception as e: except Exception as e:
logging.error(f"Ошибка загрузки файла настроек: {e}", exc_info=True) logging.error(f"Ошибка загрузки файла настроек: {e}", exc_info=True)
@@ -535,8 +551,25 @@ class SerialAppGUI(tk.Tk):
self.check_first_run() self.check_first_run()
def check_first_run(self): def check_first_run(self):
# Проверяем, существует ли папка Settings и файл settings.json """Проверка первого запуска программы"""
if not os.path.exists("Settings") or not os.path.exists(SETTINGS_FILE): show_settings = False
# Проверяем существование файла настроек
if not os.path.exists(SETTINGS_FILE):
show_settings = True
else:
# Проверяем содержимое файла настроек
try:
with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
settings = json.load(f)
# Если порт не настроен, считаем это первым запуском
if settings.get("port") is None:
show_settings = True
except Exception:
# Если файл поврежден или не читается, тоже показываем настройки
show_settings = True
if show_settings:
# Создаем папку Settings, если её нет # Создаем папку Settings, если её нет
os.makedirs("Settings", exist_ok=True) os.makedirs("Settings", exist_ok=True)
@@ -558,6 +591,11 @@ class SerialAppGUI(tk.Tk):
file_menu.add_separator() file_menu.add_separator()
file_menu.add_command(label="Выход", command=self.quit) file_menu.add_command(label="Выход", command=self.quit)
# Меню "Справка"
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Справка", menu=help_menu)
help_menu.add_command(label="О программе", command=self.open_about)
def open_settings(self): def open_settings(self):
settings_window = SettingsWindow(self, self.settings, self.on_settings_changed) settings_window = SettingsWindow(self, self.settings, self.on_settings_changed)
settings_window.transient(self) settings_window.transient(self)
@@ -780,6 +818,11 @@ class SerialAppGUI(tk.Tk):
logging.error(f"Ошибка сохранения файла: {e}", exc_info=True) logging.error(f"Ошибка сохранения файла: {e}", exc_info=True)
messagebox.showerror("Ошибка", f"Не удалось сохранить файл:\n{e}") messagebox.showerror("Ошибка", f"Не удалось сохранить файл:\n{e}")
def open_about(self):
about_window = AboutWindow(self)
about_window.transient(self)
about_window.grab_set()
# ========================== # ==========================
# Парсер аргументов (не используется) # Парсер аргументов (не используется)
# ========================== # ==========================

View File

@@ -1,37 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>5a5ba42b-743e-401d-ac33-9abfe8f095a1</ProjectGuid>
<ProjectHome>.</ProjectHome>
<StartupFile>ComConfigCopy.py</StartupFile>
<SearchPath>
</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<Name>ComConfigCopy</Name>
<RootNamespace>ComConfigCopy</RootNamespace>
<TestFramework>Pytest</TestFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<ItemGroup>
<Compile Include="ComConfigCopy.py" />
<Compile Include="test.py" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. -->
<!--<Target Name="CoreCompile" />-->
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
</Project>

100
about_window.py Normal file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk, BOTH, X, BOTTOM
import webbrowser
class AboutWindow(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("О программе")
self.geometry("400x300")
self.resizable(False, False)
# Создаем фрейм
about_frame = ttk.Frame(self, padding="20")
about_frame.pack(fill=BOTH, expand=True)
# Заголовок
ttk.Label(
about_frame,
text="ComConfigCopy",
font=("Segoe UI", 16, "bold")
).pack(pady=(0, 10))
# Описание
ttk.Label(
about_frame,
text="Программа для копирования конфигураций на коммутаторы",
wraplength=350
).pack(pady=(0, 20))
# Версия
ttk.Label(
about_frame,
text="Версия 1.0",
font=("Segoe UI", 10)
).pack(pady=(0, 20))
# Контактная информация
contact_frame = ttk.Frame(about_frame)
contact_frame.pack(fill=X, pady=(0, 20))
# Исходный код
ttk.Label(
contact_frame,
text="Исходный код:",
font=("Segoe UI", 10, "bold")
).pack(anchor="w")
source_link = ttk.Label(
contact_frame,
text="https://gitea.filow.ru/LowaSC/ComConfigCopy",
cursor="hand2",
foreground="blue"
)
source_link.pack(anchor="w")
source_link.bind("<Button-1>", lambda e: self.open_url("https://gitea.filow.ru/LowaSC/ComConfigCopy"))
# Контакты
ttk.Label(
contact_frame,
text="\nКонтакты:",
font=("Segoe UI", 10, "bold")
).pack(anchor="w")
ttk.Label(
contact_frame,
text="Email: LowaWorkMail@gmail.com"
).pack(anchor="w")
telegram_link = ttk.Label(
contact_frame,
text="Telegram: @LowaSC",
cursor="hand2",
foreground="blue"
)
telegram_link.pack(anchor="w")
telegram_link.bind("<Button-1>", lambda e: self.open_url("https://t.me/LowaSC"))
# Кнопка закрытия
ttk.Button(
about_frame,
text="Закрыть",
command=self.destroy
).pack(side=BOTTOM, pady=(20, 0))
# Центрируем окно
self.center_window()
def center_window(self):
self.update_idletasks()
width = self.winfo_width()
height = self.winfo_height()
x = (self.winfo_screenwidth() // 2) - (width // 2)
y = (self.winfo_screenheight() // 2) - (height // 2)
self.geometry(f"{width}x{height}+{x}+{y}")
def open_url(self, url):
webbrowser.open(url)