Γράψτε ένα γραφικό περιβάλλον για 1C RAC ή ξανά για το Tcl/Tk

Καθώς εμβαθύναμε στο θέμα του πώς λειτουργούν τα προϊόντα 1C στο περιβάλλον Linux, ανακαλύφθηκε ένα μειονέκτημα - η έλλειψη ενός βολικού γραφικού εργαλείου πολλαπλών πλατφορμών για τη διαχείριση ενός συμπλέγματος διακομιστών 1C. Και αποφασίστηκε να διορθωθεί αυτό το μειονέκτημα γράφοντας ένα GUI για το βοηθητικό πρόγραμμα κονσόλας rac. Το 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, θα ανοίξει ένα γραφικό παράθυρο. Η διεπαφή προγράμματος αποτελείται από τρία στοιχεία:

Γραμμή εργαλείων, δέντρο και λίστα

Έκανα τα περιεχόμενα του "δέντρου" όσο το δυνατόν πιο παρόμοια με τον τυπικό εξοπλισμό των Windows από το 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. Πρώτα, πρέπει να προσθέσετε τον κύριο διακομιστή συμπλέγματος (δηλαδή, τον διακομιστή διαχείρισης συμπλέγματος (στο Linux, η διαχείριση εκκινείται με την εντολή "/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
}

Εδώ είναι ένα από τα πλεονεκτήματα του tickle: μπορείτε να μεταβιβάσετε την τιμή μιας μεταβλητής ως όνομα διαδικασίας:

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
}

Προσθήκη::cluster

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
    }

Στο παραπάνω παράδειγμα, το κουμπί "φραγμένο" ξεκινά τη διαδικασία για την προσθήκη ενός συμπλέγματος.

Εδώ αξίζει να κάνετε μια παρέκβαση προς την εργασία με γραφικά στοιχεία σε 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 με τις παραμέτρους που χρειαζόμαστε, αναλύει επίσης την έξοδο των εντολών σε λίστες και επιστρέφει, εάν απαιτείται:

RunCommand

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

Η διαφορά μεταξύ τους είναι η χρήση ενός βρόχου, στον οποίο εκτελούνται οι επαναλαμβανόμενες εντολές. Ποια προσέγγιση θα χρησιμοποιηθεί είναι στη διακριτική ευχέρεια του προγραμματιστή.

Καλύψαμε την προσθήκη στοιχείων και την ανάκτηση δεδομένων, τώρα ήρθε η ώρα να εστιάσουμε στην επεξεργασία. Δεδομένου ότι, βασικά, χρησιμοποιούνται οι ίδιες παράμετροι για την επεξεργασία και την προσθήκη (με εξαίρεση τη βάση πληροφοριών), χρησιμοποιούνται οι ίδιες φόρμες διαλόγου. Ο αλγόριθμος για την κλήση διαδικασιών για την προσθήκη μοιάζει με αυτό:

Προσθήκη::$key->AddToplevel

Και για επεξεργασία όπως αυτό:

Επεξεργασία::$key->Add::$key->AddTopLevel

Για παράδειγμα, ας πάρουμε την επεξεργασία ενός συμπλέγματος, π.χ. Αφού κάνετε κλικ στο όνομα του συμπλέγματος στο δέντρο, πατήστε το κουμπί επεξεργασίας στη γραμμή εργαλείων (μολύβι) και η αντίστοιχη φόρμα θα εμφανιστεί στην οθόνη:

Γράψτε ένα γραφικό περιβάλλον για 1C RAC ή ξανά για το Tcl/Tk
Επεξεργασία::cluster

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 και μεταβλητή προγραμματισμένες_δουλειές_άρνηση.

Οι φόρμες για προσθήκη και επεξεργασία ενδέχεται να διαφέρουν ως προς τη σύνθεση των πεδίων, για παράδειγμα, η εργασία με μια βάση πληροφοριών:

Προσθήκη ασφάλειας πληροφοριών

Γράψτε ένα γραφικό περιβάλλον για 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

Προσθέστε ένα σχόλιο