Initial commit: рабочая версия Main_v3 (add + фильтр дублей)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
__pycache__/
|
||||
build/
|
||||
dist/
|
||||
*.spec
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
import os
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
import pyodbc
|
||||
import uuid
|
||||
import sys
|
||||
|
||||
_BANNER = ("""
|
||||
|
||||
|
||||
....:::::::::
|
||||
::::::::::-----::::::
|
||||
::::------------=--------::..
|
||||
:::--=--============---==+===-:::.
|
||||
::---========++=++++======+++++==--:.:
|
||||
:--==++++++++++++++++++========---==-::.
|
||||
--==++++++++++++++++++++=====---:::-==--::
|
||||
:-==+**++++++++++++++++++====------::-==-::
|
||||
-==+**++++++++++++++++++++=====--------=-::
|
||||
-=++*++++++++++++++++++++++=========-----::
|
||||
-=+++++++++++++++++++++++++===========----:
|
||||
:=++++++++++++++++++++++++++==========---::
|
||||
-=++++++++++++++++++===+++++===-----====--
|
||||
-=++++++++++++++++++++==+++++=====-----==-
|
||||
-++++++++++++++++++++++++++++++++====-----
|
||||
==+++++++*#####*******++++++++++++++++=--=
|
||||
+===++++++****##%%%%##*****++++**###%##*+==-
|
||||
+*++=++++****#%##%@%%%%###***++*#%%%%%###*===-
|
||||
+*++=++++**####*#%@%#%%%##**++*#%%%@@%+*%%*+=-
|
||||
*++=+++++**+****####%%##*++==+#%%#%%#+++*+==-
|
||||
+*+++++++++++****###****++=====*####*++=====-
|
||||
+*=+++++++++++++++++++++++=======+**++=-----
|
||||
+*=+++++++++++++++++***++++==-=====+++==----
|
||||
++=+++++++++++++++*****+++==---===========--
|
||||
+=+++++++++++++*****+++++===---++======--=-
|
||||
==++++++++++**********+++++==--+++++=====-
|
||||
-==+++++++++*********##*******==++++++===-
|
||||
-==++++++++*****#**##*####**+====++++++==
|
||||
#%%%%%===+++++++++***##*****####**++++++==+++=-
|
||||
*+*#%%%#*+====++++++++**#%%%##****##********+====-
|
||||
++++++*##%@%#+++**====+++++++*##############***###*+====-
|
||||
++******###%%@@@@@%####*+=+==+++++**************+++++=+*++===-
|
||||
#***+**#*###%@@@@@@@@@@@@@%###*+=+==++++*****++*********++==++++=-=+=+=-
|
||||
#*#%%%###%%@@@@@@@@@@@@@@@@%###*++++++++**+************+++========*****++==-
|
||||
%%%######%%####%@@@@@@@@@@@@@@%%**+++++++++++++++***+++=========*###******+++++==
|
||||
%%@@@@%%%%%%%%%#%%@@@@@@@@@@@@@@%#**+*+**++++++++++++++++=====+%###########***++++*
|
||||
%@@@@@@@@@%%@@%%%%%%%@@@@@@@@@@@@@@###*****++++++*+++++++===+**#%####%###%####***+***
|
||||
%%%@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@%####**++++***++++*#####*%##%####%#%%%%##*******
|
||||
%%%%@%@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@%%%%%%##%%%@%%####*%%#%####%#%%%%%#########
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%##%#*#%#%####%##%%%%%%###%#%##
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%#%%###%######%##%#%%%%%%%%%%%##
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%####%%#####%##%%%%%%%%%%%%%###
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%####%%#%%##%#%%%%%%%%%%@%@%###
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%#####%%%#%%%%#%%%%%%%@@@@@@@%%##
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%#%%%%##%%#%%%%%%%%%%%%@@@@@@@%%#**
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%@@@%####%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@@@@@@%%%#**
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%@@@@%%%%%%%#%%%%%%%##%%%%%%%%%%%%%%%%%@@@@@@%%%%##**
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%@@@@%%@@@@@@@%%%%%%##%%%%%%%###%%%%%%%%%%%%%%%%%@@%%%%%%%%##*+
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%@@@@@@@@%%%%@@@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%###%%*#
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%@@@@@@%@@@%@@@@@@@%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%
|
||||
""")
|
||||
|
||||
# В оконном режиме (сборка без консоли) sys.stdout может быть None — не роняем старт
|
||||
if getattr(sys, "stdout", None):
|
||||
try:
|
||||
print(_BANNER)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class RemoteFolderSelector(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("Remote Folder and SQL Database Selector")
|
||||
self.geometry("620x560")
|
||||
|
||||
# Создадим фреймы для лучшей компоновки элементов
|
||||
self.frame_left = tk.Frame(self)
|
||||
self.frame_left.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
|
||||
|
||||
self.frame_right = tk.Frame(self)
|
||||
self.frame_right.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
|
||||
|
||||
# Сделаем окна масштабируемыми
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
# Текущий режим подключения (локально либо по IP)
|
||||
self.ip_address = None
|
||||
|
||||
# Поле ввода IP-адреса
|
||||
self.ip_label = tk.Label(self.frame_left, text="Enter IP (optional):")
|
||||
self.ip_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.ip_entry = tk.Entry(self.frame_left, width=30)
|
||||
self.ip_entry.grid(row=1, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.connect_button = tk.Button(self.frame_left, text="Load Users", command=self.load_users)
|
||||
self.connect_button.grid(row=2, column=0, padx=5, pady=5)
|
||||
|
||||
# Панель "отметить все / снять все" для пользователей
|
||||
self.user_toolbar = tk.Frame(self.frame_left)
|
||||
self.user_toolbar.grid(row=3, column=0, padx=5, pady=(5, 0), sticky="w")
|
||||
tk.Button(self.user_toolbar, text="Отметить все",
|
||||
command=lambda: self._set_all(self.user_vars, True)).pack(side="left", padx=(0, 4))
|
||||
tk.Button(self.user_toolbar, text="Снять все",
|
||||
command=lambda: self._set_all(self.user_vars, False)).pack(side="left")
|
||||
|
||||
# Список пользователей с чекбоксами
|
||||
self.user_vars = {}
|
||||
self.user_container, self.user_inner = self._create_scrollable_checklist(self.frame_left)
|
||||
self.user_container.grid(row=4, column=0, padx=5, pady=5, sticky="nsew")
|
||||
self.frame_left.grid_rowconfigure(4, weight=1)
|
||||
self.frame_left.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Поля для ввода данных SQL
|
||||
self.server_label = tk.Label(self.frame_right, text="SQL Server:")
|
||||
self.server_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.server_entry = tk.Entry(self.frame_right, width=30)
|
||||
self.server_entry.grid(row=1, column=0, padx=5, pady=5, sticky="w")
|
||||
self.server_entry.insert(0, "SERVERSQL")
|
||||
|
||||
self.login_label = tk.Label(self.frame_right, text="SQL Login:")
|
||||
self.login_label.grid(row=2, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.login_entry = tk.Entry(self.frame_right, width=30)
|
||||
self.login_entry.grid(row=3, column=0, padx=5, pady=5, sticky="w")
|
||||
self.login_entry.insert(0, "sa")
|
||||
|
||||
self.password_label = tk.Label(self.frame_right, text="SQL Password:")
|
||||
self.password_label.grid(row=4, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.password_entry = tk.Entry(self.frame_right, show="*", width=30)
|
||||
self.password_entry.grid(row=5, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.sql_button = tk.Button(self.frame_right, text="Load Databases", command=self.load_databases)
|
||||
self.sql_button.grid(row=6, column=0, padx=5, pady=5)
|
||||
|
||||
# Панель "отметить все / снять все" для баз
|
||||
self.db_toolbar = tk.Frame(self.frame_right)
|
||||
self.db_toolbar.grid(row=7, column=0, padx=5, pady=(5, 0), sticky="w")
|
||||
tk.Button(self.db_toolbar, text="Отметить все",
|
||||
command=lambda: self._set_all(self.db_vars, True)).pack(side="left", padx=(0, 4))
|
||||
tk.Button(self.db_toolbar, text="Снять все",
|
||||
command=lambda: self._set_all(self.db_vars, False)).pack(side="left")
|
||||
|
||||
# Список баз данных с чекбоксами
|
||||
self.db_vars = {}
|
||||
self.db_container, self.db_inner = self._create_scrollable_checklist(self.frame_right)
|
||||
self.db_container.grid(row=8, column=0, padx=5, pady=5, sticky="nsew")
|
||||
self.frame_right.grid_rowconfigure(8, weight=1)
|
||||
self.frame_right.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Кнопка добавления базы
|
||||
self.add_button = tk.Button(self.frame_left, text="Добавить отмеченные базы", command=self.add_database_to_ibases)
|
||||
self.add_button.grid(row=5, column=0, padx=5, pady=5)
|
||||
|
||||
# Загрузка пользователей с локального компьютера по умолчанию
|
||||
self.load_users()
|
||||
|
||||
def _create_scrollable_checklist(self, parent, width=200, height=180):
|
||||
"""Создаём прокручиваемую область для чекбоксов. Возвращаем (container, inner)."""
|
||||
container = tk.Frame(parent, borderwidth=1, relief="sunken")
|
||||
canvas = tk.Canvas(container, width=width, height=height, highlightthickness=0)
|
||||
scrollbar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
|
||||
inner = tk.Frame(canvas)
|
||||
inner.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.create_window((0, 0), window=inner, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
scrollbar.pack(side="right", fill="y")
|
||||
|
||||
# Прокрутка колесом мыши, когда курсор над областью
|
||||
def _on_mousewheel(event):
|
||||
canvas.yview_scroll(int(-event.delta / 120), "units")
|
||||
|
||||
canvas.bind("<Enter>", lambda e: canvas.bind_all("<MouseWheel>", _on_mousewheel))
|
||||
canvas.bind("<Leave>", lambda e: canvas.unbind_all("<MouseWheel>"))
|
||||
return container, inner
|
||||
|
||||
def _populate_checklist(self, inner, vars_dict, items):
|
||||
"""Пересоздаём чекбоксы в области inner по списку items."""
|
||||
for child in inner.winfo_children():
|
||||
child.destroy()
|
||||
vars_dict.clear()
|
||||
for item in items:
|
||||
var = tk.BooleanVar(value=False)
|
||||
vars_dict[item] = var
|
||||
tk.Checkbutton(inner, text=item, variable=var, anchor="w").pack(fill="x", anchor="w")
|
||||
|
||||
def _set_all(self, vars_dict, value):
|
||||
"""Отмечаем (True) либо снимаем (False) все чекбоксы."""
|
||||
for var in vars_dict.values():
|
||||
var.set(value)
|
||||
|
||||
def _get_checked(self, vars_dict):
|
||||
"""Возвращаем список отмеченных элементов."""
|
||||
return [name for name, var in vars_dict.items() if var.get()]
|
||||
|
||||
def load_users(self):
|
||||
"""Загружаем пользователей с указанного IP-адреса или локального ПК."""
|
||||
self.ip_address = self.ip_entry.get()
|
||||
|
||||
if not self.ip_address: # Если IP не указан, используем локальный ПК
|
||||
local_path = r"C:\Users"
|
||||
self.load_local_users(local_path)
|
||||
else:
|
||||
try:
|
||||
# Используем указанный IP для подключения к удалённому ПК
|
||||
remote_path = f"\\\\{self.ip_address}\\C$\\Users"
|
||||
self.load_remote_users(remote_path)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Could not load users: {str(e)}")
|
||||
|
||||
def load_local_users(self, path):
|
||||
"""Загружаем список пользователей с локального ПК."""
|
||||
try:
|
||||
folders = [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
|
||||
self._populate_checklist(self.user_inner, self.user_vars, folders)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Could not load local users: {str(e)}")
|
||||
|
||||
def load_remote_users(self, remote_path):
|
||||
"""Загружаем список пользователей с удалённого ПК."""
|
||||
try:
|
||||
folders = [f for f in os.listdir(remote_path) if os.path.isdir(os.path.join(remote_path, f))]
|
||||
self._populate_checklist(self.user_inner, self.user_vars, folders)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Could not load remote users: {str(e)}")
|
||||
|
||||
def load_databases(self):
|
||||
"""Подключаемся к SQL серверу и загружаем список баз данных."""
|
||||
server = self.server_entry.get()
|
||||
login = self.login_entry.get()
|
||||
password = self.password_entry.get()
|
||||
|
||||
try:
|
||||
connection_string = f"DRIVER={{SQL Server}};SERVER={server};UID={login};PWD={password};"
|
||||
connection = pyodbc.connect(connection_string)
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute("SELECT name FROM sys.databases")
|
||||
databases = cursor.fetchall()
|
||||
|
||||
# Скрываем служебные базы SQL Server, они не являются базами 1С
|
||||
system_dbs = {"master", "tempdb", "model", "msdb"}
|
||||
names = [db[0] for db in databases if db[0].lower() not in system_dbs]
|
||||
self._populate_checklist(self.db_inner, self.db_vars, names)
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Could not connect to SQL server: {str(e)}")
|
||||
|
||||
def get_ibases_path(self, user):
|
||||
"""Возвращаем путь к ibases.v8i выбранного пользователя (локально либо по IP)."""
|
||||
if not self.ip_address:
|
||||
base_path = f"C:\\Users\\{user}\\AppData\\Roaming\\1C\\1CEStart"
|
||||
else:
|
||||
base_path = f"\\\\{self.ip_address}\\C$\\Users\\{user}\\AppData\\Roaming\\1C\\1CEStart"
|
||||
return os.path.join(base_path, "ibases.v8i")
|
||||
|
||||
def build_base_entry(self, base, server):
|
||||
"""Формируем запись одной базы для ibases.v8i."""
|
||||
return (
|
||||
f"\n[{base}]\n"
|
||||
f'Connect=Srvr="{server}";Ref="{base}";\n'
|
||||
f"ID={uuid.uuid4()}\n"
|
||||
"OrderInList=255\n"
|
||||
"Folder=/\n"
|
||||
"OrderInTree=16640\n"
|
||||
"External=0\n"
|
||||
"ClientConnectionSpeed=Normal\n"
|
||||
"App=Auto\n"
|
||||
"WA=1\n"
|
||||
"Version=8.3\n"
|
||||
"DefaultApp=ThickClient\n"
|
||||
"DisableLocalSpeechToText=0\n"
|
||||
)
|
||||
|
||||
def add_database_to_ibases(self):
|
||||
"""Добавляем все выбранные базы всем выбранным пользователям."""
|
||||
# Актуализируем режим (локально / по IP) на момент добавления
|
||||
self.ip_address = self.ip_entry.get().strip()
|
||||
|
||||
users = self._get_checked(self.user_vars)
|
||||
bases = self._get_checked(self.db_vars)
|
||||
server = self.server_entry.get().strip()
|
||||
|
||||
if not users:
|
||||
messagebox.showerror("Error", "Не отмечен ни один пользователь!")
|
||||
return
|
||||
if not bases:
|
||||
messagebox.showerror("Error", "Не отмечена ни одна база!")
|
||||
return
|
||||
if not server:
|
||||
messagebox.showerror("Error", "Не указан SQL-сервер!")
|
||||
return
|
||||
|
||||
added = 0
|
||||
skipped = 0
|
||||
errors = []
|
||||
|
||||
for user in users:
|
||||
try:
|
||||
ibases_path = self.get_ibases_path(user)
|
||||
base_dir = os.path.dirname(ibases_path)
|
||||
|
||||
# Создаём каталог, если его нет
|
||||
if not os.path.exists(base_dir):
|
||||
os.makedirs(base_dir)
|
||||
|
||||
# Читаем текущее содержимое файла для проверки дублей
|
||||
existing = ""
|
||||
if os.path.exists(ibases_path):
|
||||
with open(ibases_path, "r", encoding="utf-8") as f:
|
||||
existing = f.read()
|
||||
|
||||
entries_to_add = []
|
||||
for base in bases:
|
||||
# Дубль определяем по строке подключения (сервер + имя базы)
|
||||
connect_line = f'Connect=Srvr="{server}";Ref="{base}";'
|
||||
already_present = connect_line in existing or any(
|
||||
connect_line in e for e in entries_to_add
|
||||
)
|
||||
if already_present:
|
||||
skipped += 1
|
||||
continue
|
||||
entries_to_add.append(self.build_base_entry(base, server))
|
||||
added += 1
|
||||
|
||||
# Дописываем новые записи одним заходом
|
||||
if entries_to_add:
|
||||
with open(ibases_path, "a", encoding="utf-8") as f:
|
||||
for entry in entries_to_add:
|
||||
f.write(entry)
|
||||
except Exception as e:
|
||||
errors.append(f"{user}: {e}")
|
||||
|
||||
# Итоговый отчёт
|
||||
summary = (
|
||||
f"Пользователей: {len(users)}, баз: {len(bases)}\n"
|
||||
f"Добавлено записей: {added}\n"
|
||||
f"Пропущено (уже есть): {skipped}"
|
||||
)
|
||||
if errors:
|
||||
summary += "\n\nОшибки:\n" + "\n".join(errors)
|
||||
messagebox.showwarning("Готово с ошибками", summary)
|
||||
else:
|
||||
messagebox.showinfo("Готово", summary)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = RemoteFolderSelector()
|
||||
app.mainloop()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
Reference in New Issue
Block a user