Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Khi chúng tôi đi sâu vào chủ đề về cách các sản phẩm 1C hoạt động trong môi trường Linux, chúng tôi đã phát hiện ra một nhược điểm - thiếu công cụ đa nền tảng đồ họa thuận tiện để quản lý cụm máy chủ 1C. Và người ta đã quyết định khắc phục nhược điểm này bằng cách viết GUI cho tiện ích rac console. Theo tôi, Tcl/tk được chọn làm ngôn ngữ phát triển vì phù hợp nhất cho nhiệm vụ này. Và vì vậy, tôi muốn trình bày một số khía cạnh thú vị của giải pháp trong tài liệu này.

Để làm việc, bạn sẽ cần các bản phân phối tcl/tk và 1C. Và vì tôi đã quyết định tận dụng tối đa khả năng phân phối tcl/tk cơ bản mà không sử dụng các gói của bên thứ ba, nên tôi sẽ cần phiên bản 8.6.7, bao gồm ttk - một gói có các yếu tố đồ họa bổ sung, trong đó chúng tôi chủ yếu cần ttk ::TreeView, nó cho phép hiển thị dữ liệu ở cả dạng cấu trúc cây và dạng bảng (danh sách). Ngoài ra, trong phiên bản mới, công việc có ngoại lệ đã được làm lại (lệnh try, được sử dụng trong dự án khi chạy các lệnh bên ngoài).

Dự án bao gồm một số tệp (mặc dù không có gì ngăn cản bạn thực hiện mọi thứ trong một):

rac_gui.cfg - cấu hình mặc định
rac_gui.tcl - tập lệnh khởi chạy chính
Thư mục lib chứa các tệp được tải tự động khi khởi động:
function.tcl - tệp có thủ tục
gui.tcl - giao diện đồ họa chính
hình ảnh.tcl - thư viện hình ảnh base64

Trên thực tế, tệp rac_gui.tcl khởi động trình thông dịch, khởi tạo các biến, tải mô-đun, cấu hình, v.v. Nội dung của tập tin với ý kiến:

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"
    }    
}

Sau khi tải xuống mọi thứ được yêu cầu và kiểm tra sự hiện diện của tiện ích rac, một cửa sổ đồ họa sẽ được khởi chạy. Giao diện chương trình bao gồm ba thành phần:

Thanh công cụ, cây và danh sách

Tôi đã làm cho nội dung của “cây” giống nhất có thể với thiết bị Windows tiêu chuẩn từ 1C.

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Mã chính tạo nên cửa sổ này được chứa trong tệp
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

Thuật toán làm việc với chương trình như sau:

1. Trước tiên, bạn cần thêm máy chủ cụm chính (tức là máy chủ quản lý cụm (trong Linux, quá trình quản lý được khởi chạy bằng lệnh “/opt/1C/v8.3/x86_64/ras cluster —daemon”)).

Để thực hiện việc này, hãy nhấp vào nút “+” và trong cửa sổ mở ra, nhập địa chỉ máy chủ và cổng:

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Sau đó, máy chủ của chúng tôi sẽ xuất hiện trên cây bằng cách nhấp vào nó, danh sách các cụm sẽ mở ra hoặc lỗi kết nối sẽ được hiển thị.

2. Nhấp vào tên cụm sẽ mở ra danh sách các chức năng có sẵn cho cụm đó.

3.

Và vân vân, tức là để thêm một cụm mới, chọn bất kỳ cụm nào có sẵn trong danh sách và nhấn nút “+” trên thanh công cụ và hộp thoại thêm mới sẽ được hiển thị:

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Các nút trên thanh công cụ thực hiện các chức năng tùy thuộc vào ngữ cảnh, tức là. Tùy thuộc vào phần tử nào của cây hoặc danh sách được chọn, một hoặc một quy trình khác sẽ được thực hiện.

Hãy xem ví dụ về nút thêm (“+”):

Mã tạo nút:

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

Ở đây chúng ta thấy rằng khi nhấn nút, quy trình “Thêm” sẽ được thực thi, mã của nó:

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
}

Đây là một trong những ưu điểm của cù lét: bạn có thể chuyển giá trị của một biến làm tên thủ tục:

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

Tức là, ví dụ: nếu chúng ta trỏ vào máy chủ chính và nhấn “+”, thì thủ tục Add::server sẽ được khởi chạy, nếu tại cụm - Add::cluster, v.v. (Tôi sẽ viết về nơi những “chìa khóa” cần thiết sẽ xuất hiện ở phần bên dưới), các quy trình được liệt kê sẽ vẽ các yếu tố đồ họa phù hợp với ngữ cảnh.

Như bạn có thể đã nhận thấy, các biểu mẫu có kiểu dáng tương tự nhau - điều này không có gì đáng ngạc nhiên, vì chúng được hiển thị theo một quy trình, chính xác hơn là khung chính của biểu mẫu (cửa sổ, nút, hình ảnh, nhãn), tên của quy trình ThêmTopLevel

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
}

Tham số cuộc gọi: tiêu đề, tên hình ảnh cho biểu tượng từ thư viện (lib/images.tcl) và tham số tên cửa sổ tùy chọn (mặc định .add). Do đó, nếu chúng ta lấy các ví dụ trên để thêm máy chủ chính và cụm, lệnh gọi sẽ tương ứng:

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

hoặc

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

Chà, tiếp tục với những ví dụ này, tôi sẽ trình bày các quy trình hiển thị hộp thoại thêm cho máy chủ hoặc cụm.

Thêm::máy chủ

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
}

Thêm :: cụm

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
}

Khi so sánh mã của các thủ tục này, có thể thấy sự khác biệt bằng mắt thường, tôi sẽ tập trung vào trình xử lý nút “Ok”. Trong Tk, các thuộc tính của thành phần đồ họa có thể bị ghi đè trong quá trình thực hiện chương trình bằng tùy chọn cấu hình. Ví dụ: lệnh ban đầu để hiển thị nút:

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

Nhưng trong biểu mẫu của chúng tôi, lệnh phụ thuộc vào chức năng được yêu cầu:

  .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
    }

Trong ví dụ trên, nút “bị tắc” sẽ bắt đầu quy trình thêm cụm.

Ở đây cần thực hiện lạc đề để làm việc với các phần tử đồ họa trong Tk - đối với các phần tử nhập dữ liệu khác nhau (mục nhập, hộp tổ hợp, nút kiểm tra, v.v.), một tham số đã được giới thiệu dưới dạng biến văn bản:

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

Biến này được xác định trong không gian tên chung và chứa giá trị hiện được nhập. Những thứ kia. để lấy văn bản đã nhập từ trường, bạn chỉ cần đọc giá trị tương ứng với biến (tất nhiên, với điều kiện là nó được xác định khi tạo phần tử).

Phương pháp thứ hai để truy xuất văn bản đã nhập (đối với các thành phần thuộc loại mục nhập) là sử dụng lệnh get:

.add.frm.ent_name get

Cả hai phương pháp này có thể được nhìn thấy trong đoạn mã trên.

Trong trường hợp này, việc nhấp vào nút này sẽ khởi chạy thủ tục RunCommand với dòng lệnh được tạo để thêm một cụm theo chủng tộc:

/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

Bây giờ chúng ta đến với lệnh chính, lệnh này điều khiển việc khởi chạy rac với các tham số chúng ta cần, đồng thời phân tích đầu ra của lệnh thành danh sách và trả về, nếu được yêu cầu:

Lệnh Chạy

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 ""
    }
}

Sau khi nhập dữ liệu máy chủ chính, nó sẽ được thêm vào cây, đối với điều này, trong quy trình Thêm: máy chủ ở trên, đoạn mã sau chịu trách nhiệm:

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

Bây giờ, bằng cách nhấp vào tên máy chủ trong cây, chúng ta sẽ nhận được danh sách các cụm do máy chủ đó quản lý và bằng cách nhấp vào một cụm, chúng ta sẽ nhận được danh sách các thành phần cụm (máy chủ, cơ sở thông tin, v.v.). Điều này được triển khai trong quy trình TreePress (tệp 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
}

Theo đó, Run::server sẽ được khởi chạy cho máy chủ chính (đối với cluster - Run::cluster, đối với máy chủ đang hoạt động - Run::work_server, v.v.). Những thứ kia. giá trị của biến $key là một phần tên của phần tử cây được chỉ định bởi tùy chọn -Tôi.

Hãy chú ý đến thủ tục

Chạy::máy chủ

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
    }
}

Quy trình này xử lý những gì nhận được từ máy chủ thông qua lệnh RunCommand và thêm tất cả mọi thứ vào cây - cụm, các phần tử gốc khác nhau (cơ sở, máy chủ đang hoạt động, phiên, v.v.). Nếu quan sát kỹ, bạn sẽ nhận thấy có lệnh gọi thủ tục InsertItemsWorkList bên trong. Nó được sử dụng để thêm các thành phần vào danh sách đồ họa bằng cách xử lý đầu ra của tiện ích bảng điều khiển rac, tiện ích này trước đây được trả về dưới dạng danh sách cho biến $lst. Đây là danh sách các danh sách chứa các cặp phần tử được phân tách bằng dấu hai chấm.

Ví dụ: danh sách các kết nối cụm:

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

Ở dạng đồ họa, nó sẽ trông giống như thế này:

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Quy trình trên chọn tên các phần tử cho tiêu đề và dữ liệu để điền vào bảng:

ChènItemsDanh sách công việc

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
}

Ở đây, thay vì sử dụng lệnh đơn giản [split $str ://: //] để chia chuỗi thành các phần tử được phân tách bằng dấu :// và trả về một danh sách, một biểu thức chính quy sẽ được sử dụng vì một số phần tử cũng chứa dấu hai chấm.

Thủ tục InsertClusterItems (một trong nhiều thủ tục tương tự) chỉ cần thêm danh sách các phần tử con có mã định danh tương ứng vào cây của phần tử cụm được yêu cầu
ChènCụmMục

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
}

Bạn có thể xem xét thêm hai tùy chọn để thực hiện một quy trình tương tự, trong đó sẽ hiển thị rõ ràng cách bạn có thể tối ưu hóa và loại bỏ các lệnh lặp đi lặp lại:

Trong thủ tục này, việc thêm và kiểm tra được giải quyết trực tiếp:

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"
    }
}

Đây là một cách tiếp cận đúng hơn:

Chèn các mục hồ sơ

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 
    }
}

Sự khác biệt giữa chúng là việc sử dụng vòng lặp trong đó (các) lệnh lặp lại được thực thi. Cách tiếp cận nào để sử dụng là tùy theo quyết định của nhà phát triển.

Chúng ta đã đề cập đến việc thêm các phần tử và truy xuất dữ liệu, giờ là lúc tập trung vào việc chỉnh sửa. Vì về cơ bản, các tham số giống nhau được sử dụng để chỉnh sửa và thêm (ngoại trừ cơ sở thông tin), nên các biểu mẫu hộp thoại giống nhau được sử dụng. Thuật toán gọi thủ tục cộng trông như thế này:

Thêm::$key->AddToplevel

Và để chỉnh sửa như thế này:

Chỉnh sửa::$key->Thêm::$key->AddTopLevel

Ví dụ: hãy chỉnh sửa một cụm, tức là Sau khi click vào tên cụm trên cây, nhấn nút chỉnh sửa trên thanh công cụ (bút chì) và trên màn hình sẽ hiển thị biểu mẫu tương ứng:

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk
Chỉnh sửa::cụm

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
    }
}

Dựa trên các nhận xét trong mã, về nguyên tắc, mọi thứ đều rõ ràng, ngoại trừ mã xử lý nút bị ghi đè và có thủ tục FormFieldsDataInsert giúp điền dữ liệu vào các trường và khởi tạo các biến:

FormFieldsDataChèn

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
                }
            }
        }
    }
}

Trong quy trình này, một ưu điểm khác của tcl đã xuất hiện - giá trị của các biến khác được thay thế dưới dạng tên biến. Những thứ kia. để tự động điền biểu mẫu và khởi tạo biến, tên của các trường và biến tương ứng với các chuyển đổi dòng lệnh của tiện ích rac và tên của các tham số đầu ra lệnh, ngoại trừ một số ngoại lệ - dấu gạch ngang được thay thế bằng dấu gạch dưới. Ví dụ theo lịch trình-công việc-từ chối phù hợp với trường ent_scheduled_jobs_deny và biến đã lên lịch_jobs_deny.

Các biểu mẫu để thêm và chỉnh sửa có thể khác nhau về thành phần của các trường, ví dụ: làm việc với cơ sở thông tin:

Thêm bảo mật thông tin

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Chỉnh sửa bảo mật thông tin

Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Trong quy trình chỉnh sửa Edit::infobase, các trường bắt buộc sẽ được thêm vào biểu mẫu; mã rất lớn nên tôi không trình bày ở đây.

Bằng cách tương tự, các thủ tục thêm, chỉnh sửa, xóa được thực hiện cho các phần tử khác.

Do hoạt động của tiện ích bao hàm số lượng máy chủ, cụm, cơ sở thông tin không giới hạn, v.v., để xác định cụm nào thuộc về máy chủ hoặc hệ thống bảo mật thông tin nào, một số biến toàn cục đã được đưa ra, mỗi giá trị được đặt thời điểm bạn nhấp chuột vào các phần tử của cây. Những thứ kia. thủ tục chạy đệ quy qua tất cả các phần tử cha và đặt các biến:

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
    }
}

Cụm 1C cho phép bạn làm việc có hoặc không có sự cho phép. Có hai loại quản trị viên—quản trị viên đại lý cụm và quản trị viên cụm. Theo đó, để hoạt động chính xác, 4 biến toàn cục nữa đã được giới thiệu chứa thông tin đăng nhập và mật khẩu của quản trị viên. Những thứ kia. nếu có tài khoản quản trị viên trong cụm, một hộp thoại sẽ hiển thị để nhập thông tin đăng nhập và mật khẩu của bạn, dữ liệu sẽ được lưu vào bộ nhớ và chèn vào từng lệnh cho cụm tương ứng.

Đây là trách nhiệm của thủ tục xử lý lỗi.

LỗiĐang giao hàng

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"
        }
    }
}

Những thứ kia. tùy thuộc vào những gì lệnh trả về mà phản ứng sẽ tương ứng.

Hiện tại, khoảng 95% chức năng đã được triển khai, tất cả những gì còn lại là triển khai công việc với hồ sơ bảo mật và kiểm tra nó =). Đó là tất cả. Tôi xin lỗi vì câu chuyện nhàu nát.

Mã này có sẵn theo truyền thống đây.

Cập nhật: Tôi đã làm việc xong với hồ sơ bảo mật. Bây giờ chức năng đã được thực hiện 100%.

Cập nhật 2: Đã thêm bản địa hóa sang tiếng Anh và tiếng Nga, hoạt động trong win7 đã được thử nghiệm
Viết GUI cho 1C RAC hoặc viết lại về Tcl/Tk

Nguồn: www.habr.com

Thêm một lời nhận xét