نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

همانطور که ما به موضوع نحوه عملکرد محصولات 1C در محیط لینوکس پرداختیم، یک اشکال کشف شد - فقدان یک ابزار گرافیکی چند پلتفرمی مناسب برای مدیریت خوشه ای از سرورهای 1C. و تصمیم گرفته شد که این اشکال را با نوشتن یک رابط کاربری گرافیکی برای ابزار rac console اصلاح کنیم. Tcl/tk به عنوان زبان توسعه به نظر من مناسب ترین زبان برای این کار انتخاب شد. و بنابراین، من می خواهم برخی از جنبه های جالب راه حل را در این مطالب ارائه دهم.

برای کار به توزیع های tcl/tk و 1C نیاز دارید. و از آنجایی که تصمیم گرفتم بدون استفاده از بسته های شخص ثالث از قابلیت های تحویل اولیه tcl/tk حداکثر استفاده را ببرم، به نسخه 8.6.7 نیاز دارم که شامل ttk است - بسته ای با عناصر گرافیکی اضافی که ما عمدتاً به ttk نیاز داریم. ::TreeView، امکان نمایش داده ها را هم در قالب یک ساختار درختی و هم به صورت جدول (فهرست) می دهد. همچنین در نسخه جدید، کار با استثناها مجدداً کار شده است (دستور try که در پروژه هنگام اجرای دستورات خارجی استفاده می شود).

این پروژه از چندین فایل تشکیل شده است (اگرچه هیچ چیز مانع از انجام همه کارها در یک فایل نمی شود):

rac_gui.cfg - پیکربندی پیش فرض
rac_gui.tcl - اسکریپت راه اندازی اصلی
دایرکتوری lib حاوی فایل هایی است که در هنگام راه اندازی به طور خودکار بارگذاری می شوند:
function.tcl - فایل با رویه ها
gui.tcl - رابط گرافیکی اصلی
images.tcl - کتابخانه تصویر base64

فایل rac_gui.tcl در واقع مفسر را راه اندازی می کند، متغیرها را مقداردهی اولیه می کند، ماژول ها را بارگذاری می کند، پیکربندی ها و غیره. محتویات فایل با نظرات:

rac_gui.tcl

#!/bin/sh
exec wish "$0" -- "$@"

# Устанавливаем текущий каталог
set dir(root) [pwd]
# Устанавливаем рабочий каталог, если его нет то создаём
set dir(work) [file join $env(HOME) .rac_gui]
if {[file exists $dir(work)] == 0 } {
    file mkdir $dir(work)    
}
# каталог с модулями
set dir(lib) "[file join $dir(root) lib]"

# загружаем пользовательский конфиг, если он отсутствует, то копируем дефолтный
if {[file exists [file join $dir(work) rac_gui.cfg]] ==0} {
    file copy [file join [pwd] rac_gui.cfg] [file join $dir(work) rac_gui.cfg]
} 
source [file join $dir(work) rac_gui.cfg]
# Код проверки наличия rac и правильности указания пути в конфиге
# если программа не найдена то будет выведен диалог для указания корректного пути
# и этот путь будет записан в пользовательский конфиг
if {[file exists $rac_cmd] == 0} {
    set rac_cmd [tk_getOpenFile -initialdir $env(HOME) -parent . -title "Укажите путь до rac" -initialfile rac]
    file copy [file join $dir(work) rac_gui.cfg] [file join $dir(work) rac_gui.cfg.bak] 
    set orig_file [open [file join $dir(work) rac_gui.cfg.bak] "r"]
    set file [open [file join $dir(work) rac_gui.cfg] "w"]
    while {[gets $orig_file line] >=0 } {
        if {[string match "set rac_cmd*" $line]} {
            puts $file "set rac_cmd $rac_cmd"
        } else {
            puts $file $line
        }
    }
    close $file
    close $orig_file
    #return "$host:$port"
    file delete [file join $dir(work) 1c_srv.cfg.bak] 
} else {
    puts "Found $rac_cmd"
}

set cluster_user ""
set cluster_pwd ""
set agent_user ""
set agent_pwd ""
## LOAD FILE ##
# Загружаем модули кроме gui.tcl так как его надо загрузить последним
foreach modFile [lsort [glob -nocomplain [file join $dir(lib) *.tcl]]] {
    if {[file tail $modFile] ne "gui.tcl"} {
        source $modFile
        puts "Loaded module $modFile"
    }
}
source [file join $dir(lib) gui.tcl]
source [file join $dir(work) rac_gui.cfg]

# Читаем файл со списком серверов 1С
# и добавляем в дерево
if [file exists [file join $dir(work) 1c_srv.cfg]] {
    set f [open [file join $dir(work) 1c_srv.cfg] "RDONLY"]
    while {[gets $f line] >=0} {
        .frm_tree.tree insert {} end -id "server::$line" -text "$line" -values "$line"
    }    
}

پس از دانلود همه موارد مورد نیاز و بررسی وجود ابزار rac، یک پنجره گرافیکی راه اندازی می شود. رابط برنامه از سه عنصر تشکیل شده است:

نوار ابزار، درخت و فهرست

من محتویات "درخت" را تا حد امکان شبیه به تجهیزات استاندارد ویندوز از 1C کردم.

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

کد اصلی که این پنجره را تشکیل می دهد در فایل موجود است
lib/gui.tcl

# установка размера и положения основного окна
# можно установить в переменную topLevelGeometry в конфиг программы
if {[info exists topLevelGeometry]} {
    wm geometry . $topLevelGeometry
} else {
    wm geometry . 1024x768
}
# Заголовок окна
wm title . "1C Rac GUI"
wm iconname . "1C Rac Gui"
# иконка окна (берется из файла lib/imges.tcl)
wm iconphoto . tcl
wm protocol . WM_DELETE_WINDOW Quit
wm overrideredirect . 0
wm positionfrom . user

ttk::style theme use clam

# Панель инсрументов
set frm_tool [frame .frm_tool]
pack $frm_tool -side left -fill y 
ttk::panedwindow .panel -orient horizontal -style TPanedwindow
pack .panel -expand true -fill both
pack propagate .panel false

ttk::button $frm_tool.btn_add  -command Add  -image add_grey_32
ttk::button $frm_tool.btn_del  -command Del -image del_grey_32
ttk::button $frm_tool.btn_edit  -command Edit -image edit_grey_32
ttk::button $frm_tool.btn_quit -command Quit -image quit_grey_32

pack $frm_tool.btn_add $frm_tool.btn_del $frm_tool.btn_edit -side top -padx 5 -pady 5
pack $frm_tool.btn_quit  -side bottom -padx 5 -pady 5

# Дерево с полосами прокрутки
set frm_tree [frame .frm_tree]

ttk::scrollbar $frm_tree.hsb1 -orient horizontal -command [list $frm_tree.tree xview]
ttk::scrollbar $frm_tree.vsb1 -orient vertical -command [list $frm_tree.tree yview]
set tree [ttk::treeview $frm_tree.tree -show tree 
-xscrollcommand [list $frm_tree.hsb1 set] -yscrollcommand [list $frm_tree.vsb1 set]]

grid $tree -row 0 -column 0 -sticky nsew
grid $frm_tree.vsb1 -row 0 -column 1 -sticky nsew
grid $frm_tree.hsb1 -row 1 -column 0 -sticky nsew
grid columnconfigure $frm_tree 0 -weight 1
grid rowconfigure $frm_tree 0 -weight 1

# назначение обработчика нажатия кнопкой мыши
bind $frm_tree.tree <ButtonRelease> "TreePress $frm_tree.tree"

# Список для данных (таблица)
set frm_work [frame .frm_work]
ttk::scrollbar $frm_work.hsb -orient horizontal -command [list $frm_work.tree_work xview]
ttk::scrollbar $frm_work.vsb -orient vertical -command [list $frm_work.tree_work yview]
set tree_work [
    ttk::treeview $frm_work.tree_work 
    -show headings  -columns "par val" -displaycolumns "par val"
    -xscrollcommand [list $frm_work.hsb set] 
    -yscrollcommand [list $frm_work.vsb set]
]
# Установка цветов для чередования в таблице
$tree_work tag configure dark -background $color(dark_table_bg)
$tree_work tag configure light -background $color(light_table_bg)

# Размещение элементов на форме
grid $tree_work -row 0 -column 0 -sticky nsew
grid $frm_work.vsb -row 0 -column 1 -sticky nsew
grid $frm_work.hsb -row 1 -column 0 -sticky nsew
grid columnconfigure $frm_work 0 -weight 1
grid rowconfigure $frm_work 0 -weight 1
pack $frm_tree $frm_work -side left -expand true -fill both

#.panel add $frm_tool -weight 1
.panel add $frm_tree -weight 1 
.panel add $frm_work -weight 1

الگوریتم کار با برنامه به شرح زیر است:

1. ابتدا باید سرور کلاستر اصلی را اضافه کنید (یعنی سرور مدیریت خوشه (در لینوکس مدیریت با دستور "/opt/1C/v8.3/x86_64/ras cluster —daemon" راه اندازی می شود).

برای این کار روی دکمه + کلیک کنید و در پنجره باز شده آدرس سرور و پورت را وارد کنید:

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

پس از آن سرور ما با کلیک روی آن در درخت ظاهر می شود، لیستی از خوشه ها باز می شود یا خطای اتصال نمایش داده می شود.

2. با کلیک بر روی نام خوشه، لیستی از توابع موجود برای آن باز می شود.

3. …

و غیره، یعنی. برای افزودن یک خوشه جدید، هر یک از موارد موجود در لیست را انتخاب کنید و دکمه "+" را در نوار ابزار فشار دهید و کادر گفتگوی افزودن جدید نمایش داده می شود:

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

دکمه‌های نوار ابزار بسته به زمینه، عملکردهایی را انجام می‌دهند. بسته به اینکه کدام عنصر درخت یا لیست انتخاب شده باشد، یک یا روش دیگری انجام می شود.

بیایید به مثال دکمه افزودن ("+") نگاه کنیم:

کد تولید دکمه:

ttk::button $frm_tool.btn_add  -command Add  -image add_grey_32

در اینجا می بینیم که با فشار دادن دکمه، رویه "افزودن" اجرا می شود، کد آن:

proc Add {} {
    global active_cluster host
    # Определяем идентификатор выделенного элемента
    set id  [.frm_tree.tree selection] 
    # Определяем значение этого элемента
    set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
    set key [lindex [split $id "::"] 0]
    # в зависимости от того что выделили будет запущена нужная процедура
    if {$key eq "" || $key eq "server"} {
        set host [ Add::server ]
        return
    }
    Add::$key .frm_tree.tree $host $values
}

در اینجا یکی از مزایای تیکل وجود دارد: می توانید مقدار یک متغیر را به عنوان نام رویه ارسال کنید:

Add::$key .frm_tree.tree $host $values

به عنوان مثال، اگر به سرور اصلی اشاره کنیم و "+" را فشار دهیم، رویه Add::server راه اندازی می شود، اگر در کلاستر - Add::cluster و غیره (در مورد جایی که "کلیدهای" ضروری در کمی زیر آمده است)، رویه های ذکر شده عناصر گرافیکی متناسب با زمینه را ترسیم می کنند.

همانطور که قبلاً متوجه شده اید، فرم ها از نظر سبک مشابه هستند - این تعجب آور نیست، زیرا آنها با یک روش نمایش داده می شوند، دقیقاً قاب اصلی فرم (پنجره، دکمه ها، تصویر، برچسب)، نام رویه AddTopLevel

proc AddToplevel {lbl img {win_name .add}} {
    set cmd "destroy $win_name"
    if [winfo exists $win_name] {destroy $win_name}
    toplevel $win_name
    wm title $win_name $lbl
    wm iconphoto $win_name tcl
    # метка с иконкой
    ttk::label $win_name.lbl -image $img
    # фрейм с полями ввода
    set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
    
    grid columnconfigure $frm 0 -weight 1
    grid rowconfigure $frm 0 -weight 1
    # фрейм и кнопки
    set frm_btn [frame $win_name.frm_btn -border 0]
    ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }
    ttk::button $frm_btn.btn_cancel -command $cmd -image quit_grey_24 
    grid $win_name.lbl -row 0 -column 0 -sticky nw -padx 5 -pady 10
    grid $frm -row 0 -column 1 -sticky nw -padx 5 -pady 5
    grid $frm_btn -row 1 -column 1 -sticky se -padx 5 -pady 5
    pack  $frm_btn.btn_cancel  -side right
    pack  $frm_btn.btn_ok  -side right -padx 10
    return $frm
}

پارامترهای تماس: عنوان، نام تصویر برای نماد از کتابخانه (lib/images.tcl) و یک پارامتر نام پنجره اختیاری (پیش‌فرض .add). بنابراین، اگر مثال های بالا را برای افزودن سرور و خوشه اصلی در نظر بگیریم، تماس به این صورت خواهد بود:

AddToplevel "Добавление основного сервера" server_grey_64

یا

AddToplevel "Добавление кластера" cluster_grey_64

خوب، در ادامه با این مثال‌ها، رویه‌هایی را نشان خواهم داد که دیالوگ‌های افزودن را برای یک سرور یا کلاستر نمایش می‌دهند.

اضافه کردن سرور

proc Add::server {} {
    global default
    # выводим основную форму
    set frm [AddToplevel "Добавление основного сервера" server_grey_64]
    # добавляем етки и поля ввода на эту форму
    label $frm.lbl_host -text "Адрес сервера"
    entry  $frm.ent_host
    label $frm.lbl_port -text "Порт"
    entry $frm.ent_port 
    $frm.ent_port  insert end $default(port)
    grid $frm.lbl_host -row 0 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_host -row 0 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_port -row 1 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_port -row 1 -column 1 -sticky nsew -padx 5 -pady 5
    grid columnconfigure $frm 0 -weight 1
    grid rowconfigure $frm 0 -weight 1
    #set frm_btn [frame .add.frm_btn -border 0]
    # переопределяем обработчик нажатия кнопки
    .add.frm_btn.btn_ok configure -command {
        set host [SaveMainServer [.add.frm.ent_host get] [.add.frm.ent_port get]]
        .frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"
        destroy .add
        return $host
    }
    return $frm
}

افزودن::خوشه

proc Add::cluster {tree host values} {
    global default lifetime_limit expiration_timeout session_fault_tolerance_level
    global max_memory_size max_memory_time_limit errors_count_threshold security_level
    global load_balancing_mode kill_problem_processes 
    agent_user agent_pwd cluster_user cluster_pwd auth_agent
    if {$agent_user ne "" && $agent_pwd ne ""} {
        set auth_agent "--agent-user=$agent_user --agent-pwd=$agent_pwd"
    } else {
        set auth_agent ""
    }
    # устанавливаем глобальные переменные ()
    set lifetime_limit $default(lifetime_limit)
    set expiration_timeout $default(expiration_timeout)
    set session_fault_tolerance_level $default(session_fault_tolerance_level)
    set max_memory_size $default(max_memory_size)
    set max_memory_time_limit $default(max_memory_time_limit)
    set errors_count_threshold $default(errors_count_threshold)
    set security_level [lindex $default(security_level) 0]
    set load_balancing_mode [lindex $default(load_balancing_mode) 0]
    
    set frm [AddToplevel "Добавление кластера" cluster_grey_64]
    
    label $frm.lbl_host -text "Адрес основного сервера"
    entry  $frm.ent_host
    label $frm.lbl_port -text "Порт"
    entry $frm.ent_port 
    $frm.ent_port  insert end $default(port)
    label $frm.lbl_name -text "Название кластера"
    entry  $frm.ent_name
    label $frm.lbl_secure_connect -text "Защищённое соединение"
    ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
    label $frm.lbl_expiration_timeout -text "Останавливать выключенные процессы через:"
    entry  $frm.ent_expiration_timeout -textvariable expiration_timeout
    label $frm.lbl_session_fault_tolerance_level -text "Уровень отказоустойчивости"
    entry  $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
    label $frm.lbl_load_balancing_mode -text "Режим распределения нагрузки"
    ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode 
    -values $default(load_balancing_mode)
    label $frm.lbl_errors_count_threshold -text "Допустимое отклонение количества ошибок сервера, %"
    entry  $frm.ent_errors_count_threshold -textvariable errors_count_threshold
    label $frm.lbl_processes -text "Рабочие процессы:"
    label $frm.lbl_lifetime_limit -text "Период перезапуска, сек."
    entry  $frm.ent_lifetime_limit -textvariable lifetime_limit
    label $frm.lbl_max_memory_size -text "Допустимый объём памяти, КБ"
    entry  $frm.ent_max_memory_size -textvariable max_memory_size
    label $frm.lbl_max_memory_time_limit -text "Интервал превышения допустимого объёма памяти, сек."
    entry  $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
    label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Принудительно завершать проблемные процессы"
    checkbutton $frm.check_kill_problem_processes -variable kill_problem_processes -onvalue yes -offvalue no    
    
    grid $frm.lbl_host -row 0 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_host -row 0 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_port -row 1 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_port -row 1 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_name -row 2 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_name -row 2 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_secure_connect -row 3 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.cb_security_level -row 3 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_expiration_timeout -row 4 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_expiration_timeout -row 4 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_session_fault_tolerance_level -row 5 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_session_fault_tolerance_level -row 5 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_load_balancing_mode -row 6 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.cb_load_balancing_mode -row 6 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_errors_count_threshold -row 7 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_errors_count_threshold -row 7 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_processes -row 8 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.lbl_lifetime_limit -row 9 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_lifetime_limit -row 9 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_max_memory_size -row 10 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_max_memory_size -row 10 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_max_memory_time_limit -row 11 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.ent_max_memory_time_limit -row 11 -column 1 -sticky nsew -padx 5 -pady 5
    grid $frm.lbl_kill_problem_processes -row 12 -column 0 -sticky nw -padx 5 -pady 5
    grid $frm.check_kill_problem_processes -row 12 -column 1 -sticky nw -padx 5 -pady 5
    # переопределяем обработчик
    .add.frm_btn.btn_ok configure -command {
        RunCommand "" "cluster insert 
        --host=[.add.frm.ent_host get] 
        --port=[.add.frm.ent_port get] 
        --name=[.add.frm.ent_name get] 
        --expiration-timeout=$expiration_timeout 
        --lifetime-limit=$lifetime_limit 
        --max-memory-size=$max_memory_size 
        --max-memory-time-limit=$max_memory_time_limit 
        --security-level=$security_level 
        --session-fault-tolerance-level=$session_fault_tolerance_level 
        --load-balancing-mode=$load_balancing_mode 
        --errors-count-threshold=$errors_count_threshold 
        --kill-problem-processes=$kill_problem_processes 
        $auth_agent $host"
        Run::server $tree $host ""
        destroy .add
    }
    return $frm
}

هنگام مقایسه کد این رویه ها، تفاوت با چشم غیرمسلح قابل مشاهده است؛ من روی کنترل کننده دکمه "Ok" تمرکز می کنم. در Tk، ویژگی های عناصر گرافیکی را می توان در طول اجرای برنامه با استفاده از گزینه لغو کرد پیکربندی. به عنوان مثال، دستور اولیه برای نمایش دکمه:

ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }

اما در فرم های ما، دستور به عملکرد مورد نیاز بستگی دارد:

  .add.frm_btn.btn_ok configure -command {
        RunCommand "" "cluster insert 
        --host=[.add.frm.ent_host get] 
        --port=[.add.frm.ent_port get] 
        --name=[.add.frm.ent_name get] 
        --expiration-timeout=$expiration_timeout 
        --lifetime-limit=$lifetime_limit 
        --max-memory-size=$max_memory_size 
        --max-memory-time-limit=$max_memory_time_limit 
        --security-level=$security_level 
        --session-fault-tolerance-level=$session_fault_tolerance_level 
        --load-balancing-mode=$load_balancing_mode 
        --errors-count-threshold=$errors_count_threshold 
        --kill-problem-processes=$kill_problem_processes 
        $auth_agent $host"
        Run::server $tree $host ""
        destroy .add
    }

در مثال بالا، دکمه "clogged" روند اضافه کردن یک خوشه را شروع می کند.

در اینجا ارزش یک انحراف به سمت کار با عناصر گرافیکی در Tk دارد - برای عناصر ورودی داده های مختلف (ورودی، جعبه ترکیبی، دکمه چک و غیره) یک پارامتر به عنوان متغیر متنی معرفی شده است:

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

این متغیر در فضای نام جهانی تعریف شده و حاوی مقدار وارد شده فعلی است. آن ها برای دریافت متن وارد شده از فیلد، فقط باید مقدار مربوط به متغیر را بخوانید (البته به شرطی که هنگام ایجاد عنصر تعریف شده باشد).

روش دوم برای بازیابی متن وارد شده (برای عناصر نوع ورودی) استفاده از دستور get است:

.add.frm.ent_name get

هر دوی این روش ها در کد بالا قابل مشاهده هستند.

با کلیک بر روی این دکمه، در این مورد، رویه RunCommand با خط فرمان تولید شده برای اضافه کردن یک کلاستر از نظر rac راه اندازی می شود:

/opt/1C/v8.3/x86_64/rac cluster insert  --host=localhost  --port=1540  --name=dsdsds  --expiration-timeout=0  --lifetime-limit=0  --max-memory-size=0  --max-memory-time-limit=0  --security-level=0  --session-fault-tolerance-level=0  --load-balancing-mode=performance  --errors-count-threshold=0  --kill-problem-processes=no   localhost:1545

اکنون به دستور اصلی می رسیم که راه اندازی rac را با پارامترهای مورد نیاز ما کنترل می کند، همچنین خروجی دستورات را به لیست ها تجزیه می کند و در صورت نیاز برمی گرداند:

اجرای دستور

proc RunCommand {root par} {
    global dir rac_cmd cluster work_list_row_count agent_user agent_pwd cluster_user cluster_pwd
    puts "$rac_cmd $par"
    set work_list_row_count 0
    # открываем канал в неблокирующем режиме
    # $rac - команда с полным путём
    # $par - сформированные ключи запуска и опции    
    set pipe [open "|$rac_cmd $par" "r"]
    try {
        set lst ""
        set l ""
        # вывод команды добавляем в список списков
        while {[gets $pipe line]>=0} {
            #puts $line
            if {$line eq ""} {
                lappend l $lst
                set lst ""
            } else {
                lappend lst [string trim $line]
            }
        }
        close $pipe
        return $l
    } on error {result options} {
        # Запуск обработчика ошибок
        ErrorParcing $result $options
        return ""
    }
}

پس از وارد کردن داده های سرور اصلی، به درخت اضافه می شود، برای این کار در رویه Add:server بالا، کد زیر مسئول است:

.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"

حال با کلیک بر روی نام سرور در درخت، لیستی از خوشه های مدیریت شده توسط آن سرور و با کلیک بر روی یک خوشه، لیستی از عناصر خوشه (سرورها، پایگاه های اطلاعاتی و ...) را دریافت می کنیم. این در رویه TreePress (فایل lib/function.tcl) پیاده سازی می شود:

proc TreePress {tree} {
   global host server active_cluster infobase
   # определяем выделенный элемент
    set id  [$tree selection]
   # устанавливаем нужные глобальные переменные
    SetGlobalVarFromTreeItems $tree $id
   # Определяем ключ и значение, т.е. именно тип выбранного элемента
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
   # и в зависимости от того что выбрали будет запущена соответствующая процедура 
   # в пространстве имён Run
    Run::$key $tree $host $values
}

بر این اساس، Run::server برای سرور اصلی (برای یک کلاستر - Run::cluster، برای یک سرور فعال - Run::work_server و غیره) راه اندازی می شود. آن ها مقدار متغیر $key بخشی از نام عنصر درختی است که توسط گزینه مشخص شده است -شناسه.

بیایید به رویه توجه کنیم

اجرا::سرور

proc Run::server {tree host values} {
    # получаем список кластеров требуемого сервера
    set lst [RunCommand server::$host "cluster list $host"]
    if {$lst eq ""} {return}
    set l [lindex $lst 0]
    #puts $lst
    # удаляем лишнее из списка
    .frm_work.tree_work delete  [ .frm_work.tree_work children {}]
    # читаем список
    foreach cluster_list $lst {
        # Заполняем список полученными значениями
        InsertItemsWorkList $cluster_list
        # обрабатываем вывод (список) для добавления данных в дерево
        foreach i $cluster_list {
            #puts $i
            set cluster_list [split $i ":"]
            if  {[string trim [lindex $cluster_list 0]] eq "cluster"} {
                set cluster_id [string trim [lindex $cluster_list 1]]
                lappend cluster($cluster_id) $cluster_id
            }
            if  {[string trim [lindex $cluster_list 0]] eq "name"} {
                lappend  cluster($cluster_id) [string trim [lindex $cluster_list 1]]
            }
        }
    }
    # добавляем кластеры в дерево
    foreach x [array names cluster] {
        set id [lindex $cluster($x) 0]
        if { [$tree exists "cluster::$id"] == 0 } {
            $tree insert "server::$host" end -id "cluster::$id" -text "[lindex $cluster($x) 1]" -values "$id"
            # добавляем элементы в кластер
            InsertClusterItems $tree $id
        }
    }
    if { [$tree exists "agent_admins::$id"] == 0 } {
        $tree insert "server::$host" end -id "agent_admins::$id" -text "Администраторы" -values "$id"
        #InsertClusterItems $tree $id
    }
}

این رویه آنچه را که از سرور از طریق دستور RunCommand دریافت می‌شود پردازش می‌کند و انواع چیزها را به درخت اضافه می‌کند - خوشه‌ها، عناصر مختلف ریشه (پایه‌ها، سرورهای کار، جلسات و غیره). اگر دقت کنید، متوجه فراخوانی رویه InsertItemsWorkList در داخل خواهید شد. برای افزودن عناصر به یک لیست گرافیکی با پردازش خروجی ابزار کنسول rac که قبلاً به عنوان یک لیست به متغیر $lst برگردانده شده بود استفاده می شود. این فهرستی از لیست‌هایی است که شامل جفت‌هایی از عناصر است که با دو نقطه از هم جدا شده‌اند.

به عنوان مثال، لیستی از اتصالات خوشه:

svk@svk ~]$ /opt/1C/v8.3/x86_64/rac connection list --cluster=783d2170-56c3-11e8-c586-fc75165efbb2 localhost:1545
connection     : dcf5991c-7d24-11e8-1690-fc75165efbb2
conn-id        : 0
host           : svk.home
process        : 79de2e16-56c3-11e8-c586-fc75165efbb2
infobase       : 00000000-0000-0000-0000-000000000000
application    : "JobScheduler"
connected-at   : 2018-07-01T14:49:51
session-number : 0
blocked-by-ls  : 0

connection     : b993293a-7d24-11e8-1690-fc75165efbb2
conn-id        : 0
host           : svk.home
process        : 79de2e16-56c3-11e8-c586-fc75165efbb2
infobase       : 00000000-0000-0000-0000-000000000000
application    : "JobScheduler"
connected-at   : 2018-07-01T14:48:52
session-number : 0
blocked-by-ls  : 0

در شکل گرافیکی چیزی شبیه به این خواهد بود:

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

رویه بالا نام عناصر را برای هدر و داده ها برای پر کردن جدول انتخاب می کند:

InsertItemsWorkList

proc InsertItemsWorkList {lst} {
    global work_list_row_count
    # установка чередования цвета для строки
    if [expr $work_list_row_count % 2] {
        set tag dark
    } else {
        set tag light
    }
    # разбор строк на пары ключ - значение
    foreach i $lst {
        if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
            lappend column_list [string trim $param]
            lappend value_list [string trim $value]
        }
    }
     # заполнение таблицы
    .frm_work.tree_work configure -columns $column_list -displaycolumns $column_list
    .frm_work.tree_work insert {} end  -values $value_list -tags $tag
    .frm_work.tree_work column #0 -stretch
    # установка заголовков
    foreach j $column_list {
        .frm_work.tree_work heading $j -text $j
    }
    incr work_list_row_count
}

در اینجا، به جای دستور ساده [split $str ":"]، که رشته را به عناصر جدا شده با ":" تقسیم می‌کند و فهرستی را برمی‌گرداند، از یک عبارت منظم استفاده می‌شود، زیرا برخی از عناصر نیز حاوی دو نقطه هستند.

روش InsertClusterItems (یکی از چندین مورد مشابه) به سادگی لیستی از عناصر فرزند با شناسه های مربوطه را به درخت عنصر خوشه مورد نیاز اضافه می کند.
InsertClusterItems

proc InsertClusterItems {tree id} {
    set parent "cluster::$id"
    $tree insert $parent end -id "infobases::$id" -text "Информационные базы" -values "$id"
    $tree insert $parent end -id "servers::$id" -text "Рабочие серверы" -values "$id"
    $tree insert $parent end -id "admins::$id" -text "Администраторы" -values "$id"
    $tree insert $parent end -id "managers::$id" -text "Менеджеры кластера" -values $id
    $tree insert $parent end -id "processes::$id" -text "Рабочие процессы" -values "workprocess-all"
    $tree insert $parent end -id "sessions::$id" -text "Сеансы" -values "sessions-all"
    $tree insert $parent end -id "locks::$id" -text "Блокировки" -values "blocks-all"
    $tree insert $parent end -id "connections::$id" -text "Соединения" -values "connections-all"
    $tree insert $parent end -id "profiles::$id" -text "Профили безопасности" -values $id
}

می توانید دو گزینه دیگر را برای اجرای یک رویه مشابه در نظر بگیرید، جایی که به وضوح قابل مشاهده است که چگونه می توانید دستورات تکراری را بهینه کنید و از شر آن خلاص شوید:

در این روش، افزودن و بررسی به طور مستقیم حل می شود:

InsertBaseItems

proc InsertBaseItems {tree id} {
    set parent "infobase::$id"
    if { [$tree exists "sessions::$id"] == 0 } {
        $tree insert $parent end -id "sessions::$id" -text "Сеансы" -values "$id"
    }
    if { [$tree exists "locks::$id"] == 0 } {
        $tree insert $parent end -id "locks::$id" -text "Блокировки" -values "$id"
    }
    if { [$tree exists "connections::$id"] == 0 } {
        $tree insert $parent end -id "connections::$id" -text "Соединения" -values "$id"
    }
}

در اینجا یک رویکرد صحیح تر وجود دارد:

InsertProfileItems

proc InsertProfileItems {tree id} {
    set parent "profile::$id"
    set lst {
        {dir "Виртуальные каталоги"}
        {com "Разрешённые COM-классы"}
        {addin "Внешние компоненты"}
        {module "Внешние отчёты и обработки"}
        {app "Разрешённые приложения"}
        {inet "Ресурсы интернет"}
    }
    foreach i $lst {
        append item [lindex $i 0] "::$id"
        if { [$tree exists $item] == 0 } {
            $tree insert $parent end -id $item -text [lindex $i 1] -values "$id"
        }
        unset item 
    }
}

تفاوت بین آنها استفاده از یک حلقه است که در آن دستور(های) تکراری اجرا می شود. اینکه چه رویکردی برای استفاده از آن به صلاحدید توسعه دهنده است.

ما اضافه کردن عناصر و بازیابی داده ها را پوشش داده ایم، اکنون زمان تمرکز بر ویرایش است. از آنجایی که اساساً از پارامترهای یکسانی برای ویرایش و افزودن (به استثنای پایگاه اطلاعاتی) استفاده می شود، از همان فرم های گفتگو استفاده می شود. الگوریتم فراخوانی رویه ها برای افزودن به این صورت است:

Add::$key->AddToplevel

و برای ویرایش به این صورت:

ویرایش::$key->Add::$key->AddTopLevel

به عنوان مثال، اجازه دهید ویرایش یک خوشه، i.e. پس از کلیک بر روی نام خوشه در درخت، دکمه ویرایش را در نوار ابزار (مداد) فشار دهید و فرم مربوطه روی صفحه نمایش داده می شود:

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk
ویرایش:: خوشه

proc Edit::cluster {tree host values} {
    global default lifetime_limit expiration_timeout session_fault_tolerance_level
    global max_memory_size max_memory_time_limit errors_count_threshold security_level
    global load_balancing_mode kill_problem_processes active_cluster 
    agent_user agent_pwd cluster_user cluster_pwd auth
    if {$cluster_user ne "" && $cluster_pwd ne ""} {
        set auth "--cluster-user=$cluster_user --cluster-pwd=$cluster_pwd"
    } else {
        set auth ""
    }
    # рисуем форму для кластера
    set frm [Add::cluster $tree $host $values]
    # меняем текст на метке
    $frm configure -text "Редактирование кластера"
    
    set active_cluster $values
    # получаем данные по выделенному кластеру
    set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
    # заполняем поля
    FormFieldsDataInsert $frm $lst
    # выключаем поля, редактирование которых запрещено
    $frm.ent_host configure -state disable
    $frm.ent_port configure -state disable
    # переназначаем обработчик
    .add.frm_btn.btn_ok configure -command {
        RunCommand "" "cluster update 
        --cluster=$active_cluster $auth 
        --name=[.add.frm.ent_name get] 
        --expiration-timeout=$expiration_timeout 
        --lifetime-limit=$lifetime_limit 
        --max-memory-size=$max_memory_size 
        --max-memory-time-limit=$max_memory_time_limit 
        --security-level=$security_level 
        --session-fault-tolerance-level=$session_fault_tolerance_level 
        --load-balancing-mode=$load_balancing_mode 
        --errors-count-threshold=$errors_count_threshold 
        --kill-problem-processes=$kill_problem_processes 
        $auth $host"
        $tree delete "cluster::$active_cluster"
        Run::server $tree $host ""
        destroy .add
    }
}

بر اساس نظرات در کد، در اصل، همه چیز واضح است، به جز اینکه کد کنترل کننده دکمه لغو شده است و یک رویه FormFieldsDataInsert وجود دارد که فیلدها را با داده پر می کند و متغیرها را مقداردهی اولیه می کند:

FormFieldsDataInsert

proc FormFieldsDataInsert {frm lst} {
    foreach i [lindex $lst 0] {
        # получаем список параметров и значений
        if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
            # меняем символы
            regsub -all -- "-" [string trim $param] "_" entry_name
            # заполняем данными
            if [winfo exists $frm.ent_$entry_name] {
                $frm.ent_$entry_name delete 0 end
                $frm.ent_$entry_name insert end [string trim $value """]
            }
            if [winfo exists $frm.cb_$entry_name] {
                global $entry_name
                set $entry_name [string trim $value """]
            }
            # для чекбоксов меняем значения
            if [winfo exists $frm.check_$entry_name] {
                global $entry_name
                if {$value eq "0"} {
                    set $entry_name no
                } elseif {$value eq "1"} {
                    set $entry_name yes
                } else {
                    set $entry_name $value
                }
            }
        }
    }
}

در این روش، یکی دیگر از مزایای tcl ظاهر شد - مقادیر سایر متغیرها به عنوان نام متغیرها جایگزین می شوند. آن ها برای خودکار کردن پر کردن فرم ها و مقداردهی اولیه متغیرها، نام فیلدها و متغیرها با سوئیچ های خط فرمان ابزار rac و نام پارامترهای خروجی فرمان با برخی استثناها مطابقت دارد - خط تیره با زیرخط جایگزین می شود. به عنوان مثال برنامه ریزی شده-شغل-انکار با میدان مطابقت دارد ent_scheduled_jobs_deny و متغیر Scheded_jobs_deny.

فرم های افزودن و ویرایش ممکن است در ترکیب فیلدها متفاوت باشد، به عنوان مثال، کار با یک پایگاه اطلاعاتی:

افزودن امنیت اطلاعات

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

ویرایش امنیت اطلاعات

نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

در مراحل ویرایش Edit::infobase، فیلدهای مورد نیاز به فرم اضافه می شود؛ کد حجیم است، بنابراین من آن را در اینجا ارائه نمی کنم.

بر اساس قیاس، رویه هایی برای افزودن، ویرایش، حذف برای عناصر دیگر اجرا می شود.

از آنجایی که عملکرد این ابزار مستلزم تعداد نامحدودی از سرورها، خوشه‌ها، پایگاه‌های اطلاعاتی و غیره است، برای اینکه مشخص شود کدام خوشه متعلق به کدام سرور یا سیستم امنیت اطلاعات است، چندین متغیر سراسری معرفی شده‌اند که مقادیر هر کدام تنظیم می‌شوند. زمانی که روی عناصر درخت کلیک می کنید. آن ها این رویه به صورت بازگشتی در تمام عناصر والد اجرا می شود و متغیرها را تنظیم می کند:

SetGlobalVarFromTreeItems

proc SetGlobalVarFromTreeItems {tree id} {
    global host server active_cluster infobase
    set parent [$tree parent $id]
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
    switch -- $key {
        server {set host $values}
        work_server {set server $values}
        cluster {set active_cluster $values}
        infobase {set infobase $values}
    }
    if {$parent eq ""} {
        return
    } else {
        SetGlobalVarFromTreeItems $tree $parent
    }
}

خوشه 1C به شما امکان می دهد با یا بدون مجوز کار کنید. دو نوع مدیر وجود دارد - مدیر عامل خوشه و مدیر خوشه. بر این اساس، برای عملکرد صحیح، 4 متغیر جهانی دیگر شامل لاگین و رمز عبور مدیر معرفی شد. آن ها اگر یک حساب مدیر در خوشه وجود داشته باشد، یک گفتگو برای وارد کردن لاگین و رمز عبور شما نمایش داده می شود، داده ها در حافظه ذخیره می شوند و در هر دستور برای خوشه مربوطه وارد می شوند.

این مسئولیت رویه رسیدگی به خطا است.

ErrorParcing

proc ErrorParcing {err opt} {
    global cluster_user cluster_pwd agent_user agent_pwd
        switch -regexp -- $err {
        "Cluster administrator is not authenticated" {
            AuthorisationDialog "Администратор кластера"
            .auth_win.frm_btn.btn_ok configure -command {
                set cluster_user [.auth_win.frm.ent_name get]
                set cluster_pwd [.auth_win.frm.ent_pwd get]
                destroy .auth_win
            }
            #RunCommand $root $par
        }
        "Central server administrator is not authenticated" {
            AuthorisationDialog "Администратор агента кластера"
            .auth_win.frm_btn.btn_ok configure -command {
                set agent_user [.auth_win.frm.ent_name get]
                set agent_pwd [.auth_win.frm.ent_pwd get]
                destroy .auth_win
            }
        }
        "Администратор кластера не аутентифицирован" {
            AuthorisationDialog "Администратор кластера"
            .auth_win.frm_btn.btn_ok configure -command {
                set cluster_user [.auth_win.frm.ent_name get]
                set cluster_pwd [.auth_win.frm.ent_pwd get]
                destroy .auth_win
            }
            #RunCommand $root $par
        }
        "Администратор центрального сервера не аутентифицирован" {
            AuthorisationDialog "Администратор агента кластера"
            .auth_win.frm_btn.btn_ok configure -command {
                set agent_user [.auth_win.frm.ent_name get]
                set agent_pwd [.auth_win.frm.ent_pwd get]
                destroy .auth_win
            }
        }
        (.+) {
            tk_messageBox -type ok -icon error -message "$err"
        }
    }
}

آن ها بسته به آنچه که دستور برمی گرداند، عکس العمل بر این اساس خواهد بود.

در حال حاضر، حدود 95 درصد از عملکرد پیاده سازی شده است، تنها چیزی که باقی می ماند پیاده سازی کار با پروفایل های امنیتی و آزمایش آن است =). همین. بابت داستان مچاله شده عذرخواهی می کنم.

کد به طور سنتی در دسترس است اینجا.

به روز رسانی: کار با نمایه های امنیتی را به پایان رساندم. اکنون عملکرد 100٪ پیاده سازی شده است.

به روز رسانی 2: بومی سازی به انگلیسی و روسی اضافه شده است، کار در win7 تست شده است
نوشتن یک رابط کاربری گرافیکی برای 1C RAC یا دوباره در مورد Tcl/Tk

منبع: www.habr.com

اضافه کردن نظر