Compare commits
27 Commits
136c7877d3
...
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 11253286f8 | |||
| ea432d2893 | |||
| d3f832cdbb | |||
| 7d92682482 | |||
| 0ebcf9cb6a | |||
| 99e9163760 | |||
| b573de9166 | |||
| f45f1bf556 | |||
| e6766660c6 | |||
| e0705ad6b5 | |||
| c0d8fd8d89 | |||
| ce81100150 | |||
| 48c9bd2d40 | |||
| e0f64060f5 | |||
| f5935d6b8f | |||
| 1a511ff54f | |||
| f84e20631b | |||
| 4a67e70a92 | |||
| 12562e615f | |||
| 7ebeb52808 | |||
| a140b7d8a0 | |||
| 2c9edcd859 | |||
| 5a00efd175 | |||
| 2f4b2985cd | |||
| d1a870fed7 | |||
| 2e2dd9e705 | |||
| d937042ea2 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,9 +1,6 @@
|
|||||||
app.log
|
app.log
|
||||||
Settings/settings.json
|
Settings/settings.json
|
||||||
Configs/Eltex MES2424 AC - Сеть FTTB 2G, доп.txt
|
Configs/
|
||||||
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
|
|
||||||
test.py
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
Firmware/1.jpg
|
Firmware/1.jpg
|
||||||
Firmware/2
|
Firmware/2
|
||||||
|
|||||||
1809
ComConfigCopy.py
1809
ComConfigCopy.py
File diff suppressed because it is too large
Load Diff
44
README.md
Normal file
44
README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# ComConfigCopy
|
||||||
|
|
||||||
|
Программа для копирования конфигураций на коммутаторы.
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
ComConfigCopy - это утилита, разработанная для автоматизации процесса копирования конфигураций на сетевые коммутаторы. Программа предоставляет удобный графический интерфейс для управления процессом копирования и настройки параметров подключения.
|
||||||
|
|
||||||
|
## Основные возможности
|
||||||
|
|
||||||
|
- Копирование конфигураций на коммутаторы через COM-порт
|
||||||
|
- Поддержка различных скоростей подключения
|
||||||
|
- Автоматическое определение доступных COM-портов
|
||||||
|
- Возможность сохранения и загрузки настроек
|
||||||
|
- Автоматическое обновление через GitHub
|
||||||
|
|
||||||
|
## Системные требования
|
||||||
|
|
||||||
|
- Windows 7/8/10/11
|
||||||
|
- Python 3.8 или выше
|
||||||
|
- Доступ к COM-портам
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Скачайте последнюю версию программы из [репозитория](https://gitea.filow.ru/LowaSC/ComConfigCopy/releases)
|
||||||
|
2. Распакуйте архив в удобное место
|
||||||
|
3. Запустите файл `ComConfigCopy.exe`
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
1. Выберите COM-порт из списка доступных
|
||||||
|
2. Настройте параметры подключения (скорость, биты данных и т.д.)
|
||||||
|
3. Выберите файл конфигурации для отправки
|
||||||
|
4. Нажмите кнопку "Отправить" для начала процесса копирования
|
||||||
|
|
||||||
|
## Контакты
|
||||||
|
|
||||||
|
- Email: SPRF555@gmail.com
|
||||||
|
- Telegram: [@LowaSC](https://t.me/LowaSC)
|
||||||
|
- Репозиторий: [ComConfigCopy](https://gitea.filow.ru/LowaSC/ComConfigCopy)
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
Этот проект распространяется под лицензией MIT. Подробности смотрите в файле [LICENSE](LICENSE).
|
||||||
@@ -2,19 +2,22 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, BOTH, X, BOTTOM
|
from tkinter import ttk, BOTH, X, BOTTOM, END
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
class AboutWindow(tk.Toplevel):
|
class AboutWindow(tk.Toplevel):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.title("О программе")
|
self.title("О программе")
|
||||||
self.geometry("400x300")
|
self.geometry("600x500")
|
||||||
self.resizable(False, False)
|
self.resizable(False, False)
|
||||||
|
|
||||||
# Создаем фрейм
|
# Сохраняем ссылку на родительское окно
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Создаем фрейм для содержимого
|
||||||
about_frame = ttk.Frame(self, padding="20")
|
about_frame = ttk.Frame(self, padding="20")
|
||||||
about_frame.pack(fill=BOTH, expand=True)
|
about_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
# Заголовок
|
# Заголовок
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
@@ -33,7 +36,7 @@ class AboutWindow(tk.Toplevel):
|
|||||||
# Версия
|
# Версия
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
about_frame,
|
about_frame,
|
||||||
text="Версия 1.0",
|
text=f"Версия {getattr(parent, 'VERSION', '1.0.0')}",
|
||||||
font=("Segoe UI", 10)
|
font=("Segoe UI", 10)
|
||||||
).pack(pady=(0, 20))
|
).pack(pady=(0, 20))
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ class AboutWindow(tk.Toplevel):
|
|||||||
|
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
contact_frame,
|
contact_frame,
|
||||||
text="Email: LowaWorkMail@gmail.com"
|
text="Email: SPRF555@gmail.com"
|
||||||
).pack(anchor="w")
|
).pack(anchor="w")
|
||||||
|
|
||||||
telegram_link = ttk.Label(
|
telegram_link = ttk.Label(
|
||||||
@@ -80,10 +83,10 @@ class AboutWindow(tk.Toplevel):
|
|||||||
|
|
||||||
# Кнопка закрытия
|
# Кнопка закрытия
|
||||||
ttk.Button(
|
ttk.Button(
|
||||||
about_frame,
|
self,
|
||||||
text="Закрыть",
|
text="Закрыть",
|
||||||
command=self.destroy
|
command=self.destroy
|
||||||
).pack(side=BOTTOM, pady=(20, 0))
|
).pack(side=BOTTOM, pady=10)
|
||||||
|
|
||||||
# Центрируем окно
|
# Центрируем окно
|
||||||
self.center_window()
|
self.center_window()
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1,5 @@
|
|||||||
tftpy>=0.8.0
|
tftpy>=0.8.0
|
||||||
|
pyserial>=3.5
|
||||||
|
requests>=2.31.0
|
||||||
|
packaging>=23.2
|
||||||
|
send2trash>=1.8.0
|
||||||
165
update_checker.py
Normal file
165
update_checker.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import threading
|
||||||
|
import re
|
||||||
|
from packaging import version
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import html
|
||||||
|
|
||||||
|
class UpdateCheckError(Exception):
|
||||||
|
"""Исключение для ошибок проверки обновлений"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ReleaseType:
|
||||||
|
"""Типы релизов"""
|
||||||
|
STABLE = "stable"
|
||||||
|
PRERELEASE = "prerelease"
|
||||||
|
|
||||||
|
class UpdateChecker:
|
||||||
|
"""Класс для проверки обновлений программы"""
|
||||||
|
|
||||||
|
def __init__(self, current_version, repo_url, include_prereleases=False):
|
||||||
|
self.current_version = current_version
|
||||||
|
self.repo_url = repo_url
|
||||||
|
self.include_prereleases = include_prereleases
|
||||||
|
self.rss_url = f"{repo_url}/releases.rss"
|
||||||
|
self.release_info = None
|
||||||
|
|
||||||
|
def _clean_html(self, html_text):
|
||||||
|
"""Очищает HTML-разметку и форматирует текст"""
|
||||||
|
if not html_text:
|
||||||
|
return ""
|
||||||
|
text = re.sub(r'<[^>]+>', '', html_text)
|
||||||
|
text = html.unescape(text)
|
||||||
|
text = re.sub(r'\n\s*\n', '\n\n', text)
|
||||||
|
return '\n'.join(line.strip() for line in text.splitlines()).strip()
|
||||||
|
|
||||||
|
def _parse_release_info(self, item):
|
||||||
|
"""Извлекает информацию о релизе из RSS item"""
|
||||||
|
title = item.find('title').text if item.find('title') is not None else ''
|
||||||
|
link = item.find('link').text if item.find('link') is not None else ''
|
||||||
|
description = item.find('description').text if item.find('description') is not None else ''
|
||||||
|
content = item.find('{http://purl.org/rss/1.0/modules/content/}encoded')
|
||||||
|
content_text = content.text if content is not None else ''
|
||||||
|
|
||||||
|
# Извлекаем версию и проверяем тип релиза из тега
|
||||||
|
version_match = re.search(r'/releases/tag/(?:pre-)?v?(\d+\.\d+(?:\.\d+)?)', link)
|
||||||
|
if not version_match:
|
||||||
|
return None
|
||||||
|
|
||||||
|
version_str = version_match.group(1)
|
||||||
|
# Проверяем наличие префикса pre- в теге
|
||||||
|
is_prerelease = 'pre-' in link.lower()
|
||||||
|
|
||||||
|
# Форматируем название релиза
|
||||||
|
formatted_title = title
|
||||||
|
if title == version_str or not title.strip():
|
||||||
|
# Если заголовок пустой или совпадает с версией, создаем стандартное название
|
||||||
|
release_type = "Пре-релиз" if is_prerelease else "Версия"
|
||||||
|
formatted_title = f"{release_type} {version_str}"
|
||||||
|
elif not re.search(version_str, title):
|
||||||
|
# Если версия не указана в заголовке, добавляем её
|
||||||
|
formatted_title = f"{title} ({version_str})"
|
||||||
|
|
||||||
|
# Форматируем описание
|
||||||
|
formatted_description = self._clean_html(content_text or description)
|
||||||
|
if not formatted_description.strip():
|
||||||
|
formatted_description = "Нет описания"
|
||||||
|
|
||||||
|
# Добавляем метку типа релиза в начало описания
|
||||||
|
release_type_label = "[Пре-релиз] " if is_prerelease else ""
|
||||||
|
formatted_description = f"{release_type_label}{formatted_description}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
'title': formatted_title,
|
||||||
|
'link': link,
|
||||||
|
'description': formatted_description,
|
||||||
|
'version': version_str,
|
||||||
|
'type': ReleaseType.PRERELEASE if is_prerelease else ReleaseType.STABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
def check_updates(self, callback=None):
|
||||||
|
"""Проверяет наличие обновлений в асинхронном режиме"""
|
||||||
|
def check_worker():
|
||||||
|
try:
|
||||||
|
logging.info(f"Текущая версия программы: {self.current_version}")
|
||||||
|
logging.info(f"Проверка пре-релизов: {self.include_prereleases}")
|
||||||
|
logging.info(f"Запрос RSS ленты: {self.rss_url}")
|
||||||
|
|
||||||
|
response = requests.get(self.rss_url, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
root = ET.fromstring(response.content)
|
||||||
|
items = root.findall('.//item')
|
||||||
|
if not items:
|
||||||
|
raise UpdateCheckError("Релизы не найдены")
|
||||||
|
|
||||||
|
logging.info(f"Найдено {len(items)} релизов")
|
||||||
|
|
||||||
|
latest_version = None
|
||||||
|
latest_info = None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
release_info = self._parse_release_info(item)
|
||||||
|
if not release_info:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_prerelease = release_info['type'] == ReleaseType.PRERELEASE
|
||||||
|
logging.info(
|
||||||
|
f"Проверка релиза: {release_info['title']}, "
|
||||||
|
f"версия: {release_info['version']}, "
|
||||||
|
f"тип: {'пре-релиз' if is_prerelease else 'стабильная'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Пропускаем пре-релизы если они не включены
|
||||||
|
if is_prerelease and not self.include_prereleases:
|
||||||
|
logging.info(f"Пропуск пре-релиза: {release_info['version']}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Сравниваем версии
|
||||||
|
try:
|
||||||
|
current_ver = version.parse(latest_version or "0.0.0")
|
||||||
|
new_ver = version.parse(release_info['version'].split('-')[0]) # Убираем суффикс для сравнения
|
||||||
|
|
||||||
|
if new_ver > current_ver:
|
||||||
|
latest_version = release_info['version']
|
||||||
|
latest_info = release_info
|
||||||
|
logging.info(f"Новая версия: {latest_version}")
|
||||||
|
|
||||||
|
except version.InvalidVersion as e:
|
||||||
|
logging.warning(f"Некорректный формат версии {release_info['version']}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not latest_info:
|
||||||
|
raise UpdateCheckError("Не найдены подходящие версии")
|
||||||
|
|
||||||
|
self.release_info = latest_info
|
||||||
|
|
||||||
|
# Сравниваем с текущей версией
|
||||||
|
current_ver = version.parse(self.current_version)
|
||||||
|
latest_ver = version.parse(latest_version.split('-')[0])
|
||||||
|
update_available = latest_ver > current_ver
|
||||||
|
|
||||||
|
logging.info(f"Сравнение версий: текущая {current_ver} <-> последняя {latest_ver}")
|
||||||
|
logging.info(f"Доступно обновление: {update_available}")
|
||||||
|
|
||||||
|
if callback:
|
||||||
|
callback(update_available, None)
|
||||||
|
|
||||||
|
except UpdateCheckError as e:
|
||||||
|
logging.error(str(e))
|
||||||
|
if callback:
|
||||||
|
callback(False, str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при проверке обновлений: {e}", exc_info=True)
|
||||||
|
if callback:
|
||||||
|
callback(False, str(e))
|
||||||
|
|
||||||
|
threading.Thread(target=check_worker, daemon=True).start()
|
||||||
|
|
||||||
|
def get_release_notes(self):
|
||||||
|
"""Возвращает информацию о последнем релизе"""
|
||||||
|
return self.release_info
|
||||||
Reference in New Issue
Block a user