Mens vi dykkede ned i emnet om 1C-produkters funktionalitet i Linux-miljøer, opdagede vi en mangel — fraværet af et praktisk grafisk multiplatformværktøj til at administrere 1C-serverklusteret. Det blev besluttet at udbedre denne mangel ved at skrive et GUI til konsolværktøjet rac. Programmeringssproget valgte vi blev tcl/tk, da det, efter min opfattelse, var det mest egnede til denne opgave. Her vil jeg præsentere nogle interessante aspekter af løsningen i dette materiale.
For at arbejde med vores projekt vil vi have brug for distributioner af tcl/tk og 1C. Da jeg besluttede at maksimalt udnytte mulighederne i den grundlæggende tcl/tk-udgave uden brug af tredjeparts pakker, er version 8.6.7 nødvendig, som inkluderer ttk — en pakke med ekstra grafiske elementer, hvoraf vi primært har brug for ttk::TreeView; dette gør det muligt at vise data både som en træstruktur og som en tabel (liste). Derudover er arbejdet med undtagelser (kommandoen try, som benyttes i projektet til at køre eksterne kommandoer) blevet forbedret i den nye version.
Projektet består af flere filer (selvom der ikke er noget, der forhindrer os i at lave alt i én fil):
rac_gui.cfg — standardkonfiguration
rac_gui.tcl — hovedskript til start
I biblioteket lib ligger filerne, der automatisk indlæses ved opstart:
function.tcl — fil med procedurer
gui.tcl — hovedgrafisk interface
images.tcl — billedbibliotek i base64
Filen rac_gui.tcl starter faktisk fortolkeren, initialiserer variabler, indlæser moduler, konfigurationer osv. Indholdet af filen med kommentarer:
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"
}
}Efter at have indlæst alt, hvad der er nødvendigt, og kontrolleret, at værktøjet rac er til stede, vil det grafiske vindue blive åbnet. Programmets gr interface består af tre elementer:
Værktøjslinje, træ og liste
Indholdet af "træet" er gjort så ligesom den standard windows-modul fra 1C som muligt.
Den primære kode, der danner dette vindue, findes i filen
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
Programmets arbejdsalgorithm er som følger:
1. Først skal du tilføje hovedserveren i klyngen (dvs. klyngeadministrationsserveren (i Linux startes administrationen med kommandoen "/opt/1C/v8.3/x86_64/ras cluster —daemon")).
For at gøre dette skal du klikke på knappen " + " og indtaste serverens adresse og port i det åbne vindue:

Herefter vil vores server dukke op i træet, hvor et klik på den vil åbne listen over klynger, eller der vil blive vist en forbindelsesfejl.
2. Ved at klikke på klyngenavnet åbnes en liste over tilgængelige funktioner for den.
3.…
Og så videre, dvs. for at tilføje en ny klynge, vælger vi en hvilken som helst tilgængelig i listen og klikker på knappen «+» i værktøjslinjen, hvorefter dialogen tilføjelse af en ny klynge vises:
Knapperne i værktøjslinjen udfører funktioner afhængigt af konteksten, dvs. hvilken element i træet eller listen der er valgt, vil den bestemte procedure blive udført.
Lad os se på eksemplet med knappen tilføjelse («+»):
Koden til at danne knappen:
ttk::button $frm_tool.btn_add -command Add -image add_grey_32Her ser vi, at når knappen trykkes, vil proceduren «Add» blive udført, dens kode:
proc Add {} {
global active_cluster host
# Bestemmer ID'et for det valgte element
set id [.frm_tree.tree selection]
# Bestemmer værdien af dette element
set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
set key [lindex [split $id "::"] 0]
# afhængigt af hvad der er valgt, vil den nødvendige procedure blive udført
if {$key eq "" || $key eq "server"} {
set host [ Add::server ]
return
}
Add::$key .frm_tree.tree $host $values
}Her kigger en af fordelene ved ticklerne frem — du kan bruge værdien af en variabel som procedurenavn:
Add::$key .frm_tree.tree $host $valuesDet vil sige, hvis vi klikker på den primære server og trykker «+», så vil proceduren Add::server blive udført, hvis vi klikker på klyngen — Add::cluster og så videre (jeg vil skrive lidt senere om, hvor de nødvendige «nøgler» kommer fra), de nævnte procedurer tegner de grafiske elementer, der svarer til konteksten.
Som du allerede har bemærket, ligner formularerne i stil — og det er ikke overraskende, fordi de genereres af en enkelt procedure, specifikt den grundlæggende formramme (vindue, knapper, billede, etikette), procedurnavnet 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
# Etikette med ikon
ttk::label $win_name.lbl -image $img
# Ramme med indtastningsfelter
set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
grid columnconfigure $frm 0 -weight 1
grid rowconfigure $frm 0 -weight 1
# Ramme og knapper
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
}
Funktionsparametre: titel, billednavn til ikonen fra biblioteket (lib/images.tcl) og et valgfrit vinduesnavn (standard er .add). Så hvis vi bruger ovenstående eksempler til at tilføje hovedserveren og klyngen, vil kaldet være henholdsvis:
AddToplevel "Tilføjelse af hovedserver" server_grey_64eller
AddToplevel "Tilføjelse af klynge" cluster_grey_64Og videre med disse eksempler vil jeg vise procedurerne, der viser dialoger for at tilføje en server eller klynge.
Add::server
proc Add::server {} {
global default
# viser hovedformularen
set frm [AddToplevel "Tilføjelse af hovedserver" server_grey_64]
# tilføjer etiketter og inputfelter til denne formular
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]
# omdefinerer knaptryk handleren
.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
}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 ""
}
# sætter globale variabler ()
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 "Tilføjelse af cluster" cluster_grey_64]
label $frm.lbl_host -text "Adresse på hovedserver"
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 "Cluster navn"
entry $frm.ent_name
label $frm.lbl_secure_connect -text "Sikret forbindelse"
ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
label $frm.lbl_expiration_timeout -text "Stop nedlagte processer efter:"
entry $frm.ent_expiration_timeout -textvariable expiration_timeout
label $frm.lbl_session_fault_tolerance_level -text "Fejltolerance niveau"
entry $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
label $frm.lbl_load_balancing_mode -text "Load balancing metode"
ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode
-values $default(load_balancing_mode)
label $frm.lbl_errors_count_threshold -text "Acceptabel afvigelse i serverfejl, %"
entry $frm.ent_errors_count_threshold -textvariable errors_count_threshold
label $frm.lbl_processes -text "Arbejdsprocesser:"
label $frm.lbl_lifetime_limit -text "Genstartsperiode, sek."
entry $frm.ent_lifetime_limit -textvariable lifetime_limit
label $frm.lbl_max_memory_size -text "Maksimal hukommelsesmængde, KB"
entry $frm.ent_max_memory_size -textvariable max_memory_size
label $frm.lbl_max_memory_time_limit -text "Interval for overskridelse af maksimal hukommelse, sek."
entry $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Tving problematiske processer til at afslutte"
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
# overstyrer handler
.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
}Når man sammenligner koden for disse procedurer, er forskellen tydelig for det blotte øje; jeg vil fokusere på handleren for knappen "Ok". I Tk kan egenskaber for grafiske elementer overskrives under programmet ved hjælp af indstillingen configure. For eksempel, den oprindelige kommando for at udskrive knappen:
ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }Men i vores formularer afhænger kommandoen af den ønskede funktionalitet:
.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
}
I det ovenstående eksempel er knappen "besejret" til at starte proceduren for at tilføje et cluster.
Her er det værd at gøre en bemærkning om arbejdet med grafiske elementer i Tk - for forskellige datagivende elementer (entry, combobox, checkbutton osv.) er der indført en parameter kaldet tekstvariabel (textvariable):
entry $frm.ent_lifetime_limit -textvariable lifetime_limitDenne variabel er defineret i det globale navnerum og indeholder den aktuelt indtastede værdi. Det vil sige, for at få den indtastede tekst fra feltet, skal man blot læse værdien af den tilsvarende variabel (naturligvis forudsat at den er defineret ved oprettelsen af elementet).
Den anden metode til at hente den indtastede tekst (for entry-elementer) er at bruge get-kommandoen:
.add.frm.ent_name getBegge disse metoder kan ses i ovenstående kode.
Tryk på denne knap starter i dette tilfælde proceduren RunCommand med den sammensatte kommando-streng tilføjelse af cluster i forhold til 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:1545Og her er vi ved hovedkommandoen, der styrer start af rac med de ønskede parametre, den analyserer også output af kommandoer til lister og returnerer, hvis det er nødvendigt:
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
# åbner kanalen i ikke-blokerende tilstand
# $rac - kommandoen med den fulde sti
# $par - de dannede startnøgler og indstillinger
set pipe [open "|$rac_cmd $par" "r"]
try {
set lst ""
set l ""
# kommandoudgangen føjes til listen
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} {
# kør fejlhåndteringsproceduren
ErrorParcing $result $options
return ""
}
}Efter indtastning af data for hovedserveren vil den blive tilføjet til træet. Den følger denne kode i den ovenfor nævnte procedure Add:server:
.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"Nu, når vi klikker på serverens navn i træet, får vi en liste over de klynger, som serveren administrerer, og ved at klikke på en klynge får vi listen over klyngeelementer (servere, informationsdatabaser osv.). Dette er implementeret i proceduren TreePress (fil lib/function.tcl):
proc TreePress {tree} {
global host server active_cluster infobase
# bestemme det valgte element
set id [$tree selection]
# indstille de nødvendige globale variabler
SetGlobalVarFromTreeItems $tree $id
# Bestem nøgle og værdi, dvs. typen af det valgte element
set values [$tree item $id -values]
set key [lindex [split $id "::"] 0]
# afhængigt af hvad der er valgt, vil den relevante procedure blive kørt
# i Run-navnerummet
Run::$key $tree $host $values
}For hovedserveren vil Run::server blive kørt (for klyngen — Run::cluster, for arbejdsserveren — Run::work_server osv.). Det vil sige, at værdien af variablen $key er en del af navnene på træelementet, som angives af indstillingen -id.
Bemærk proceduren
Run::server
proc Run::server {tree host values} {
# henter listen over klynger for den ønskede server
set lst [RunCommand server::$host "cluster list $host"]
if {$lst eq ""} {return}
set l [lindex $lst 0]
#puts $lst
# fjerner unødvendige fra listen
.frm_work.tree_work delete [ .frm_work.tree_work children {}]
# læser listen
foreach cluster_list $lst {
# Fylder listen med de opnåede værdier
InsertItemsWorkList $cluster_list
# håndterer output (listen) for at tilføje data til træet
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]]
}
}
}
# tilføjer klynger til træet
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"
# tilføjer elementer til klyngen
InsertClusterItems $tree $id
}
}
if { [$tree exists "agent_admins::$id"] == 0 } {
$tree insert "server::$host" end -id "agent_admins::$id" -text "Administratorer" -values "$id"
#InsertClusterItems $tree $id
}
}Denne procedure håndterer det, der er modtaget fra serveren via kommandoen RunCommand, og tilføjer diverse til træet — klynger, forskellige rod-elementer (databaser, arbejdshosts, sessioner osv.). Ved nærmere eftersyn kan man bemærke kaldet til proceduren InsertItemsWorkList. Den bruges til at tilføje elementer til den grafiske liste, mens den behandler output fra konsolværktøjet rac, som tidligere blev returneret som en liste i variablen $lst. Dette er en liste af lister, der indeholder elementpar adskilt af kolon.
For eksempel, listen over klyngeforbindelser:
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
Visuelt ville dette se nogenlunde således ud:
Den ovennævnte procedure udpeger elementnavne til overskriften og data til udfyldning af tabellen:
InsertItemsWorkList
proc InsertItemsWorkList {lst} {
global work_list_row_count
# indstilling af farveskift for rækken
if [expr $work_list_row_count % 2] {
set tag dark
} else {
set tag light
}
# opdeler strenge i par af nøgle - værdi
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]
}
}
# udfyldning af tabellen
.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
# indstilling af overskrifter
foreach j $column_list {
.frm_work.tree_work heading $j -text $j
}
incr work_list_row_count
}Her anvendes et regulært udtryk i stedet for den enkle kommando [split $str «:»], som splitter strengen i elementer adskilt af «:» og returnerer en liste, fordi nogle elementer også indeholder kolon.
Proceduren InsertClusterItems (en af flere lignende) tilføjer simpelt hen en liste over underelementer med de relevante identifikatorer til det krævede element cluster i træet.
InsertClusterItems
proc InsertClusterItems {tree id} {
set parent "cluster::$id"
$tree insert $parent end -id "infobases::$id" -text "Informationsbaser" -values "$id"
$tree insert $parent end -id "servers::$id" -text "Arbejdsgivere" -values "$id"
$tree insert $parent end -id "admins::$id" -text "Administratorer" -values "$id"
$tree insert $parent end -id "managers::$id" -text "Klyngeledere" -values $id
$tree insert $parent end -id "processes::$id" -text "Arbejdsprocesser" -values "workprocess-all"
$tree insert $parent end -id "sessions::$id" -text "Sessioner" -values "sessions-all"
$tree insert $parent end -id "locks::$id" -text "Låse" -values "blocks-all"
$tree insert $parent end -id "connections::$id" -text "Forbindelser" -values "connections-all"
$tree insert $parent end -id "profiles::$id" -text "Sikkerhedsprofiler" -values $id
}
Man kan overveje to andre metoder til at implementere en sådan procedure, hvor det er tydeligt, hvordan man kan optimere og slippe af med gentagne kommandoer:
I denne procedure er tilføjelse og kontrol gjort direkte:
InsertBaseItems
proc InsertBaseItems {tree id} {
set parent "infobase::$id"
if { [$tree exists "sessions::$id"] == 0 } {
$tree insert $parent end -id "sessions::$id" -text "Sessioner" -values "$id"
}
if { [$tree exists "locks::$id"] == 0 } {
$tree insert $parent end -id "locks::$id" -text "Låse" -values "$id"
}
if { [$tree exists "connections::$id"] == 0 } {
$tree insert $parent end -id "connections::$id" -text "Forbindelser" -values "$id"
}
}
Og her er tilgangen mere korrekt:
InsertProfileItems
proc InsertProfileItems {tree id} {
set parent "profile::$id"
set lst {
{dir "Virtuelle kataloger"}
{com "Tilladte COM-klasser"}
{addin "Eksterne komponenter"}
{module "Eksterne rapporter og behandlinger"}
{app "Tilladte applikationer"}
{inet "Internettressourcer"}
}
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
}
}Forskellen mellem dem ligger i brugen af løkken, hvor den gentagne kommando (kommandoerne) udføres. Hvilken tilgang der skal anvendes — det er op til udvikleren.
Vi har set på tilføjelse af elementer og hentning af data, og det er på tide at fokusere på redigering. Da der primært bruges de samme parametre til redigering og tilføjelse (med undtagelse af informationsbasen), anvendes de samme dialogformer. Algoritmen til at kalde procedurer for tilføjelse ser således ud:
Add::$key->AddToplevel
Og til redigering ser det sådan ud:
Edit::$key->Add::$key->AddTopLevel
For eksempel, lad os tage redigering af en klynge, dvs. ved at klikke på navnet på klyngen i træet, trykker vi på redigeringsknappen i værktøjslinjen (blyanten), og den relevante formular vil blive vist på skærmen:

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 ""
}
# viser formen for klyngen
set frm [Add::cluster $tree $host $values]
# ændre teksten på etiketten
$frm configure -text "Redigering af klynge"
set active_cluster $values
# henter data for den valgte klynge
set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
# udfylder felterne
FormFieldsDataInsert $frm $lst
# deaktiverer felterne, hvor redigering ikke er tilladt
$frm.ent_host configure -state disable
$frm.ent_port configure -state disable
# omdefinerer handleren
.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
}
}Ifølge kommentarerne i koden er det i princippet klart, bortset fra at knap-handlerens kode er omdefineret, og der er en procedure FormFieldsDataInsert, der udfylder felter med data og initialiserer variabler:
FormFieldsDataInsert
proc FormFieldsDataInsert {frm lst} {
foreach i [lindex $lst 0] {
# henter en liste over parametre og værdier
if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
# ændrer tegnene
regsub -all -- "-" [string trim $param] "_" entry_name
# udfylder med data
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 """]
}
# for afkrydsningsfelter ændrer vi værdierne
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
}
}
}
}
}
I denne procedure er der opstået endnu en fordel ved tcl — værdierne for andre variable kan bruges som navne på variablerne. Det vil sige, at til automatisering af udfyldning af formularer og initialisering af variabler, svarer navne på felter og variabler til nøglerne i kommandolinjen til værktøjet rac og navne på parametre fra kommandoer, med nogle undtagelser — bindestreger er blevet erstattet med understregninger. For eksempel scheduled-jobs-deny svare til feltet ent_scheduled_jobs_deny og variablen scheduled_jobs_deny.
Formularerne til tilføjelse og redigering kan variere i indholdet af felter, for eksempel arbejdet med informationsdatabasen:
Tilføjelse af ID

Redigering af ID

I redigeringsproceduren Edit::infobase tilføjes de nødvendige felter til formularen; koden er omfattende, så jeg medfører ikke den her.
På tilsvarende vis er der implementeret procedurer til tilføjelse, redigering, sletning og for de andre elementer.
Da arbejdet med værktøjet indebærer et ubegribelig antal servere, klynger, informationsdatabaser osv., er der indført flere globale variabler for at bestemme, hvilken klynge der hører til hvilken server eller ID. Det vil sige, at proceduren rekursivt gennemløber alle forældrelementerne og sætter variablerne:
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
}
}
Klyngen 1C muliggør arbejde både med og uden autorisation. Der er to typer administratorer — klyngeagentadministrator og klyngeadministrator. Derfor er der indført yderligere 4 globale variabler, som indeholder administratorens brugernavn og adgangskode. Det vil sige, at hvis der findes en administratorbruger i klyngen, vil der blive præsenteret en dialog til indtastning af brugernavn og adgangskode, og oplysningerne vil blive gemt i hukommelsen og indsat i hver kommando for den tilsvarende klynge.
Dette håndteres af fejlbehandlingsproceduren
ErrorParcing
proc ErrorParcing {err opt} {
global cluster_user cluster_pwd agent_user agent_pwd
switch -regexp -- $err {
"Cluster administrator is not authenticated" {
AuthorisationDialog "Clusteradministrator"
.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 "Agentadministrator"
.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
}
}
"Administrator for cluster is not authenticated" {
AuthorisationDialog "Clusteradministrator"
.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 for central server is not authenticated" {
AuthorisationDialog "Agentadministrator"
.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"
}
}
}Det vil sige, afhængigt af hvad kommandoen returnerer, vil reaktionen være tilsvarende.
I øjeblikket er funktionaliteten implementeret cirka 95 %, det resterende arbejde er at håndtere sikkerhedsprofiler og teste =). Det var alt. Jeg beklager det sammenpressede narrativ.
Koden er, som sædvanligt, tilgængelig .
Opdatering: Jeg har færdiggjort arbejdet med sikkerhedsprofiler. Nu er funktionaliteten implementeret 100%.
Opdatering 2: tilføjet lokalisering til engelsk og russisk, arbejder bekræftet i win7

Kilde: habr.com
