Während ich mich in die Arbeit mit den 1C-Produkten in der Linux-Umgebung vertiefte, stellte ich einen Nachteil fest – das Fehlen eines benutzerfreundlichen graphischen plattformübergreifenden Werkzeugs zur Verwaltung eines 1C-Serverclusters. Daher wurde beschlossen, diesen Nachteil zu beheben, indem ein GUI für das Konsolenprogramm rac entwickelt wurde. Die Programmiersprache für die Entwicklung wurde auf tcl/tk ausgewählt, da ich sie für die am besten geeignete Lösung für diese Aufgabe halte. Einige interessante Aspekte dieser Lösung möchte ich in diesem Material vorstellen.
Für die Arbeit werden die Distributionen tcl/tk und 1C benötigt. Da ich die Möglichkeiten der Standardversion von tcl/tk ohne den Einsatz externer Pakete maximal nutzen wollte, benötige ich die Version 8.6.7, die ttk – ein Paket mit zusätzlichen grafischen Elementen – enthält, von denen wir hauptsächlich ttk::TreeView benötigen. Dieses ermöglicht es, Daten sowohl in Form einer Baumstruktur als auch in Form einer Tabelle (Liste) darzustellen. Darüber hinaus wurde in der neuen Version die Verarbeitung von Ausnahmen (der Befehl try, der im Projekt beim Start externer Befehle verwendet wird) überarbeitet.
Das Projekt besteht aus mehreren Dateien (obwohl nichts dagegen spricht, alles in einer einzigen Datei zu machen):
rac_gui.cfg – die Standardkonfiguration
rac_gui.tcl – das Hauptstartskript
Im lib-Verzeichnis befinden sich Dateien, die beim Start automatisch geladen werden:
function.tcl – Datei mit Prozeduren
gui.tcl – die Hauptgrafikschnittstelle
images.tcl – Bildbibliothek in base64
Die Datei rac_gui.tcl startet den Interpreter, initialisiert Variablen, lädt Module, Konfigurationen usw. Der Inhalt der Datei mit Kommentaren:
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"
}
}Nach dem Laden aller erforderlichen Komponenten und der Überprüfung des Vorhandenseins des rac-Utilities wird ein grafisches Fenster gestartet. Die Benutzeroberfläche des Programms besteht aus drei Elementen:
Symbolleiste, Baum und Liste
Der Inhalt des "Baums" wurde so gestaltet, dass er möglichst der Standard-Windows-Komponente von 1C ähnelt.
Der Hauptcode zur Erstellung dieses Fensters befindet sich in der Datei
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
Der Arbeitsablauf mit dem Programm ist folgendermaßen:
1. Zunächst muss der Hauptserver des Clusters hinzugefügt werden (d.h. der Server zur Verwaltung des Clusters (in Linux wird die Verwaltung mit dem Befehl „/opt/1C/v8.3/x86_64/ras cluster --daemon“ gestartet)).
Dafür drücken Sie die Taste „+“ und geben im geöffneten Fenster die Adresse des Servers und den Port ein:

Nach dem Klicken erscheint unser Server im Baum, und es öffnet sich entweder die Liste der Cluster oder es wird ein Verbindungsfehler ausgegeben.
2. Ein Klick auf den Cluster-Namen öffnet die verfügbaren Funktionen für diesen.
3.…
Und so weiter, d.h. um einen neuen Cluster hinzuzufügen, wählen wir einen beliebigen verfügbaren aus der Liste aus und drücken die Schaltfläche „+“ in der Symbolleiste; es wird ein Dialog zum Hinzufügen des Neuen angezeigt:
Die Schaltflächen in der Symbolleiste führen abhängig vom Kontext Funktionen aus, d.h. je nachdem, welches Element des Baumes oder der Liste ausgewählt ist, wird die entsprechende Prozedur ausgeführt.
Lassen Sie uns dies am Beispiel der Hinzufügen-Schaltfläche („+“) betrachten:
Code zur Generierung der Schaltfläche:
ttk::button $frm_tool.btn_add -command Add -image add_grey_32Hier sehen wir, dass beim Klicken auf die Schaltfläche die Prozedur „Add“ ausgeführt wird; deren Code lautet:
proc Add {} {
global active_cluster host
# Bestimmen Sie die ID des ausgewählten Elements
set id [.frm_tree.tree selection]
# Bestimmen Sie den Wert dieses Elements
set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
set key [lindex [split $id "::"] 0]
# Abhängig von dem, was ausgewählt wurde, wird die entsprechende Prozedur gestartet
if {$key eq "" || $key eq "server"} {
set host [ Add::server ]
return
}
Add::$key .frm_tree.tree $host $values
}Hier zeigt sich einer der Vorteile von Tcl — als Namen der Prozedur kann der Wert einer Variablen übergeben werden:
Add::$key .frm_tree.tree $host $valuesDas heißt, zum Beispiel, wenn wir auf den Hauptserver klicken und „+“ drücken, wird die Prozedur Add::server aufgerufen; wenn wir auf einen Cluster klicken — Add::cluster und so weiter (woher die benötigten „Schlüssel“ stammen, werde ich gleich erläutern); die aufgelisteten Prozeduren zeichnen die grafischen Elemente entsprechend dem Kontext.
Wie Sie wahrscheinlich bereits bemerkt haben, sind die Formulare stilistisch ähnlich — das ist nicht überraschend, da sie durch eine einzige Prozedur erstellt werden, genauer gesagt durch das Hauptgerüst des Formulars (Fenster, Schaltflächen, Bild, Beschriftung), der Name der Prozedur 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
# Etikett mit Icon
ttk::label $win_name.lbl -image $img
# Rahmen mit Eingabefeldern
set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
grid columnconfigure $frm 0 -weight 1
grid rowconfigure $frm 0 -weight 1
# Rahmen und Buttons
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
}
Aufrufparameter: Titel, Bildname für das Icon aus der Bibliothek (lib/images.tcl) und optional der Fensternamen (Standard: .add). Somit lautet der Aufruf für die oben genannten Beispiele zum Hinzufügen des Hauptservers und des Clusters entsprechend:
AddToplevel "Hinzufügen des Hauptservers" server_grey_64oder
AddToplevel "Hinzufügen des Clusters" cluster_grey_64Und um mit diesen Beispielen fortzufahren, zeige ich die Prozeduren, die die Dialoge zum Hinzufügen für Server oder Cluster anzeigen.
Add::server
proc Add::server {} {
global default
# Hauptformular anzeigen
set frm [AddToplevel "Hinzufügen des Hauptservers" server_grey_64]
# Labels und Eingabefelder zu diesem Formular hinzufügen
label $frm.lbl_host -text "Serveradresse"
entry $frm.ent_host
label $frm.lbl_port -text "Port"
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]
# Handler für Button-Klick überschreiben
.add.frm_btn.btn_ok configure -command {
set host [SaveMainServer [.add.frm.ent_host get] [.add.frm.ent.ent_port get]]
.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"
destroy .add
return $host
}
return $frm
}Add::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 ""
}
# globale Variablen setzen ()
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 hinzufügen" cluster_grey_64]
label $frm.lbl_host -text "Adresse des Hauptservers"
entry $frm.ent_host
label $frm.lbl_port -text "Port"
entry $frm.ent_port
$frm.ent_port insert end $default(port)
label $frm.lbl_name -text "Clustername"
entry $frm.ent_name
label $frm.lbl_secure_connect -text "Sichere Verbindung"
ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
label $frm.lbl_expiration_timeout -text "Nicht laufende Prozesse nach:"
entry $frm.ent_expiration_timeout -textvariable expiration_timeout
label $frm.lbl_session_fault_tolerance_level -text "Fehlertoleranzlevel"
entry $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
label $frm.lbl_load_balancing_mode -text "Lastverteilung-Modus"
ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode
-values $default(load_balancing_mode)
label $frm.lbl_errors_count_threshold -text "Zulässige Abweichung der Serverfehleranzahl, %"
entry $frm.ent_errors_count_threshold -textvariable errors_count_threshold
label $frm.lbl_processes -text "Arbeitsprozesse:"
label $frm.lbl_lifetime_limit -text "Neustartintervall, Sek."
entry $frm.ent_lifetime_limit -textvariable lifetime_limit
label $frm.lbl_max_memory_size -text "Erlaubte Speichermenge, KB"
entry $frm.ent_max_memory_size -textvariable max_memory_size
label $frm.lbl_max_memory_time_limit -text "Zeitraum für Überschreitung der erlaubten Speichermenge, Sek."
entry $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Problematische Prozesse zwangsweise beenden"
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
# Handler überschreiben
.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
}Beim Vergleich des Codes dieser Prozeduren ist der Unterschied mit bloßem Auge sichtbar, ich möchte die Aufmerksamkeit auf den Handler des Buttons „Ok“ lenken. In Tk können die Eigenschaften grafischer Elemente zur Laufzeit des Programms über die Option überschrieben werden. configure. Zum Beispiel der ursprüngliche Befehl zur Ausgabe des Buttons:
ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }In unseren Formularen hängt der Befehl jedoch von der benötigten Funktionalität ab:
.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
}
Im obigen Beispiel wird beim Button „geblendet“ der Befehl zum Hinzufügen eines Clusters ausgeführt.
Hier ist es wichtig, einen kurzen Abstecher zur Arbeit mit grafischen Elementen in Tk zu machen – für verschiedene Dateneingabeelemente (Eingabefeld, Kombinationsfeld, Kontrollkästchen usw.) wurde ein Parameter wie die Textvariable (textvariable) eingeführt:
entry $frm.ent_lifetime_limit -textvariable lifetime_limitDiese Variable ist im globalen Namensraum definiert und enthält den aktuell eingegebenen Wert. Das heißt, um den eingegebenen Text aus dem Feld zu erhalten, muss man einfach den Wert der entsprechenden Variable lesen (natürlich vorausgesetzt, dass sie beim Erstellen des Elements definiert wurde).
Die zweite Methode, um den eingegebenen Text (für Eingabeelemente) zu erhalten, besteht darin, den Befehl get zu verwenden:
.add.frm.ent_name getBeide Methoden sind im oben genannten Code zu sehen.
Ein Druck auf diesen Button startet in diesem Fall die Prozedur RunCommand mit der gebildeten Befehlszeile zum Hinzufügen eines Clusters in den Begriffen von 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:1545Hier sind wir also bei dem Hauptbefehl angelangt, der den Start von rac mit den benötigten Parametern steuert, auch die Ausgaben von Befehlen in Listen analysiert und zurückgibt, wenn dies erforderlich ist:
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
# Kanal im nicht blockierenden Modus öffnen
# $rac - Befehl mit vollständigem Pfad
# $par - erzeugte Startparameter und Optionen
set pipe [open "|$rac_cmd $par" "r"]
try {
set lst ""
set l ""
# Die Ausgabe des Befehls wird zur Liste hinzugefügt
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} {
# Fehlerbehandlungsroutine starten
ErrorParcing $result $options
return ""
}
}Nach der Eingabe der Daten des Hauptservers wird dieser in den Baum hinzugefügt, dafür ist im oben genannten Verfahren Add:server der folgende Code verantwortlich:
.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"Jetzt, wenn wir mit der Maus auf den Namen des Servers im Baum klicken, erhalten wir eine Liste der Cluster, die von diesem Server verwaltet werden, und wenn wir auf einen Cluster klicken, erhalten wir eine Liste der Cluster-Elemente (Server, Informationsdatenbanken usw.). Dies ist in der Prozedur TreePress (Datei lib/function.tcl) implementiert:
proc TreePress {tree} {
global host server active_cluster infobase
# Den ausgewählten Punkt definieren
set id [$tree selection]
# Die benötigten globalen Variablen festlegen
SetGlobalVarFromTreeItems $tree $id
# Schlüssel und Wert definieren, d.h. den Typ des ausgewählten Elements
set values [$tree item $id -values]
set key [lindex [split $id "::"] 0]
# Je nachdem, was ausgewählt wurde, wird die entsprechende Prozedur im Namensraum Run gestartet
Run::$key $tree $host $values
}Entsprechend wird für den Hauptserver Run::server gestartet (für Cluster — Run::cluster, für den Arbeitsserver — Run::work_server usw.). Das bedeutet, dass der Wert der Variablen $key Teil des Namens des Baumelements ist, das durch die Option -id.
Auf die Prozedur
Run::server
proc Run::server {tree host values} {
# Wir erhalten die Liste der Cluster des benötigten Servers
set lst [RunCommand server::$host "cluster list $host"]
if {$lst eq ""} {return}
set l [lindex $lst 0]
#puts $lst
# Entfernen überflüssiger Elemente aus der Liste
.frm_work.tree_work delete [ .frm_work.tree_work children {}]
# Lesen der Liste
foreach cluster_list $lst {
# Füllen der Liste mit den erhaltenen Werten
InsertItemsWorkList $cluster_list
# Verarbeiten der Ausgabe (Liste), um Daten zum Baum hinzuzufügen
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]]
}
}
}
# Fügen Sie die Cluster zum Baum hinzu
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"
# Fügen Sie Elemente zum Cluster hinzu
InsertClusterItems $tree $id
}
}
if { [$tree exists "agent_admins::$id"] == 0 } {
$tree insert "server::$host" end -id "agent_admins::$id" -text "Administratoren" -values "$id"
#InsertClusterItems $tree $id
}
}Dieses Verfahren verarbeitet die Daten, die vom Server über den Befehl RunCommand erhalten wurden, und fügt verschiedene Elemente in den Baum ein – Cluster, verschiedene Wurzeln (Datenbanken, Arbeitsserver, Sitzungen usw.). Bei genauerem Hinsehen kann man den Aufruf des Verfahrens InsertItemsWorkList erkennen. Es wird verwendet, um Elemente in die grafische Liste einzufügen, indem die Ausgabe des Konsolenwerkzeugs rac verarbeitet wird, das zuvor in Form einer Liste in die Variable $lst zurückgegeben wurde. Dies ist eine Liste von Listen, die Elementpaare enthalten, die durch einen Doppelpunkt getrennt sind.
Zum Beispiel die Liste der Clusterverbindungen:
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
Grafisch sieht es etwa so aus:
Das genannte Verfahren extrahiert die Namen der Elemente für die Überschrift und die Daten zur Befüllung der Tabelle:
InsertItemsWorkList
proc InsertItemsWorkList {lst} {
global work_list_row_count
# Farbwechsel für die Zeile einstellen
if [expr $work_list_row_count % 2] {
set tag dunkel
} else {
set tag hell
}
# Aufteilen der Zeilen in Schlüssel-Wert-Paare
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]
}
}
# Tabelle füllen
.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
# Kopfzeilen einstellen
foreach j $column_list {
.frm_work.tree_work heading $j -text $j
}
incr work_list_row_count
}Hier wird anstelle des einfachen Befehls [split $str «:»], der einen String an den durch „:“ getrennten Elementen aufteilt und eine Liste zurückgibt, ein regulärer Ausdruck verwendet, da einige Elemente ebenfalls einen Doppelpunkt enthalten.
Die Prozedur InsertClusterItems (eine von mehreren ähnlichen) fügt einfach der Baumstruktur für das erforderliche Element cluster eine Liste von untergeordneten Elementen mit den entsprechenden Identifikatoren hinzu.
InsertClusterItems
proc InsertClusterItems {tree id} {
set parent "cluster::$id"
$tree insert $parent end -id "infobases::$id" -text "Informationsdatenbanken" -values "$id"
$tree insert $parent end -id "servers::$id" -text "Arbeitsserver" -values "$id"
$tree insert $parent end -id "admins::$id" -text "Administratoren" -values "$id"
$tree insert $parent end -id "managers::$id" -text "Cluster-Manager" -values $id
$tree insert $parent end -id "processes::$id" -text "Arbeitsprozesse" -values "workprocess-all"
$tree insert $parent end -id "sessions::$id" -text "Sitzungen" -values "sessions-all"
$tree insert $parent end -id "locks::$id" -text "Sperren" -values "blocks-all"
$tree insert $parent end -id "connections::$id" -text "Verbindungen" -values "connections-all"
$tree insert $parent end -id "profiles::$id" -text "Sicherheitsprofile" -values $id
}
Es können noch zwei Alternativen zur Implementierung einer ähnlichen Prozedur betrachtet werden, bei denen anschaulich zu sehen ist, wie man optimieren und sich wiederholende Befehle vermeiden kann:
In dieser Prozedur sind Hinzufügen und Überprüfen direkt umgesetzt:
InsertBaseItems
proc InsertBaseItems {tree id} {
set parent "infobase::$id"
if { [$tree exists "sessions::$id"] == 0 } {
$tree insert $parent end -id "sessions::$id" -text "Sitzungen" -values "$id"
}
if { [$tree exists "locks::$id"] == 0 } {
$tree insert $parent end -id "locks::$id" -text "Sperren" -values "$id"
}
if { [$tree exists "connections::$id"] == 0 } {
$tree insert $parent end -id "connections::$id" -text "Verbindungen" -values "$id"
}
}
Hier ist der Ansatz weitaus passender:
InsertProfileItems
proc InsertProfileItems {tree id} {
set parent "profile::$id"
set lst {
{dir "Virtuelle Verzeichnisse"}
{com "Erlaubte COM-Klassen"}
{addin "Externe Komponenten"}
{module "Externe Berichte und Verarbeitung"}
{app "Erlaubte Anwendungen"}
{inet "Internetressourcen"}
}
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
}
}Der Unterschied zwischen ihnen liegt in der Anwendung der Schleife, in der der sich wiederholende Befehl (Befehle) ausgeführt wird. Welcher Ansatz angewendet wird, liegt im Ermessen des Entwicklers.
Wir haben die Hinzufügung von Elementen und das Abrufen von Daten betrachtet, jetzt ist es an der Zeit, uns mit der Bearbeitung zu befassen. Da in der Regel beim Bearbeiten und Hinzufügen dieselben Parameter verwendet werden (mit Ausnahme der Informationsdatenbank), werden auch dieselben Dialogformulare verwendet. Der Algorithmus zum Aufrufen der Prozeduren zum Hinzufügen sieht folgendermaßen aus:
Add::$key->AddToplevel
Und für die Bearbeitung so:
Edit::$key->Add::$key->AddTopLevel
Zum Beispiel nehmen wir die Bearbeitung eines Clusters, d.h. wenn wir im Baum auf den Namen des Clusters klicken, drücken wir die Schaltfläche zum Bearbeiten in der Symbolleiste (Bleistift) und das entsprechende Formular wird auf dem Bildschirm angezeigt:

Edit::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 ""
}
# erstellen des Formulars für den Cluster
set frm [Add::cluster $tree $host $values]
# ändern des Textes auf dem Label
$frm configure -text "Cluster bearbeiten"
set active_cluster $values
# erhalten der Daten zum ausgewählten Cluster
set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
# füllen der Felder
FormFieldsDataInsert $frm $lst
# deaktivieren der Felder, deren Bearbeitung verboten ist
$frm.ent_host configure -state disable
$frm.ent_port configure -state disable
# neu zuweisen des Handlers
.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
}
}Die Kommentare im Code machen im Grunde alles klar, außer dass der Handler für die Schaltfläche umdefiniert wurde und die Prozedur FormFieldsDataInsert vorhanden ist, die die Felder mit Daten füllt und die Variablen initialisiert:
FormFieldsDataInsert
proc FormFieldsDataInsert {frm lst} {
foreach i [lindex $lst 0] {
# erhalten der Liste von Parametern und Werten
if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
# ändern der Zeichen
regsub -all -- "-" [string trim $param] "_" entry_name
# füllen mit Daten
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 """]
}
# für Checkboxen Werte ändern
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
}
}
}
}
}
In diesem Verfahren zeigt sich ein weiterer Vorteil von tcl – als Variablennamen werden die Werte anderer Variablen eingesetzt. Das heißt, zur Automatisierung der Formularbefüllung und der Initialisierung von Variablen entsprechen die Bezeichnungen von Feldern und Variablen den Schlüsseln der Befehlszeile des Tools rac sowie den Bezeichnungen der Ausgabeparameter von Befehlen mit einer gewissen Ausnahme – das Minuszeichen wird durch einen Unterstrich ersetzt. Zum Beispiel scheduled-jobs-deny entspricht dem Feld ent_scheduled_jobs_deny und der Variablen scheduled_jobs_deny.
Die Formulare zum Hinzufügen und Bearbeiten können sich in der Zusammensetzung der Felder unterscheiden, zum Beispiel die Arbeit mit der Informationsdatenbank:
Hinzufügen von IB

Bearbeiten von IB

Im Bearbeitungsverfahren Edit::infobase werden die erforderlichen Felder zum Formular hinzugefügt, der Code ist umfangreich, daher lasse ich ihn hier weg.
Analog wurden die Verfahren für Hinzufügen, Bearbeiten, Löschen und für andere Elemente implementiert.
Da die Arbeit des Tools eine unbegrenzte Anzahl von Servern, Clustern, Informationsdatenbanken usw. umfasst, wurden mehrere globale Variablen eingeführt, deren Werte bei jedem Klicken auf die Baumelemente festgelegt werden. Das heißt, das Verfahren durchläuft rekursiv alle übergeordneten Elemente und setzt die Variablen:
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
}
}
Der Cluster 1C ermöglicht die Arbeit sowohl mit als auch ohne Autorisierung. Es gibt zwei Arten von Administratoren – den Cluster-Agent-Administrator und den Cluster-Administrator. Entsprechend wurden weitere 4 globale Variablen eingeführt, die den Login und das Passwort des Administrators enthalten. Das heißt, wenn im Cluster ein Administrator-Konto vorhanden ist, wird ein Dialog zur Eingabe von Login und Passwort angezeigt, die Daten werden im Speicher gespeichert und in jeden Befehl für den entsprechenden Cluster eingesetzt.
Dafür ist die Fehlerbehandlungsprozedur verantwortlich
ErrorParcing
proc ErrorParcing {err opt} {
global cluster_user cluster_pwd agent_user agent_pwd
switch -regexp -- $err {
"Cluster-Administrator ist nicht authentifiziert" {
AuthorisationDialog "Administrator des Clusters"
.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
}
"Administrator des zentralen Servers ist nicht authentifiziert" {
AuthorisationDialog "Administrator des Cluster-Agenten"
.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 "Administrator des Clusters"
.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
}
"Administrator des zentralen Servers ist nicht authentifiziert" {
AuthorisationDialog "Administrator des Cluster-Agenten"
.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"
}
}
}Das heißt, je nachdem, was der Befehl zurückgibt, wird entsprechend reagiert.
Im Moment ist die Funktionalität zu etwa 95 % umgesetzt, es fehlt nur noch die Arbeit mit Sicherheitsprofilen und die Testung =). Das ist alles. Ich bitte um Entschuldigung für die zusammenhanglose Erzählung.
Der Code ist, wie üblich, verfügbar .
Update: Die Arbeit mit den Sicherheitsprofilen wurde abgeschlossen. Jetzt ist die Funktionalität zu 100 % umgesetzt.
Update 2: Englisch und Russisch wurden lokalisiert, die Funktionalität unter Win7 wurde überprüft

Quelle: habr.com
