När jag granskade 1C-produkternas funktionalitet i en linuxmiljö upptäckte jag en brist – avsaknaden av ett användarvänligt grafiskt plattformsoberoende verktyg för att hantera 1C-serverklyngor. Så jag beslutade att åtgärda denna brist genom att skriva en GUI för konsolverktyget rac. Språket som valdes för utvecklingen var tcl/tk, eftersom det enligt min mening är det mest lämpliga för denna uppgift. Här är några intressanta aspekter av lösningen, som jag vill presentera i detta material.
För att arbetet ska fungera behövs distributioner av tcl/tk och 1C. Eftersom jag ville utnyttja de grundläggande funktionerna i tcl/tk utan att använda externa paket, behövs version 8.6.7, som inkluderar ttk – ett paket med ytterligare grafiska element. De element vi huvudsakligen behöver är ttk::TreeView, som möjliggör visning av data både som en trädstruktur och i form av en tabell (lista). Dessutom har den nya versionen förbättrats med avseende på felhantering (kommandot try, som används i projektet vid körning av externa kommandon).
Projektet består av flera filer (även om man skulle kunna göra allt i en):
rac_gui.cfg — standardkonfiguration
rac_gui.tcl — huvudstartskriptet
I katalogen lib finns filer som automatiskt laddas vid start:
function.tcl — filen med procedurer
gui.tcl — huvudgränssnittet
images.tcl — biblioteket med bilder i base64
Filen rac_gui.tcl startar faktiskt tolken, initierar variabler, laddar moduler, konfigurationer och så vidare. Innehållet i 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 att allt som behövs har laddats och kontroll av att verktyget rac finns kommer ett grafiskt fönster att startas. Programmets gränssnitt består av tre element:
Verktygsfält, träd och lista
Innehållet i "trädet" har jag gjort så likt som möjligt den inbyggda Windows-komponenten från 1C.
Huvudkoden som formar detta fönster finns 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
Algoritmen för att arbeta med programmet är följande:
1. Först måste man lägga till huvudservern i klustret (dvs. klusterhanteringsservern (i linux startas hanteringen med kommandot "/opt/1C/v8.3/x86_64/ras cluster —daemon")).
För detta trycker du på knappen " + " och i det öppna fönstret anger du serverns adress och port:

Efter detta kommer vår server att dyka upp i trädet, och om man klickar på den öppnas listan över kluster eller ett felmeddelande om anslutningen visas.
2. Genom att klicka på klusternamnet öppnas en lista med tillgängliga funktioner.
3.…
För att lägga till ett nytt kluster markerar vi valfritt tillgängligt objekt i listan och trycker på knappen «+» i verktygsfältet. En dialogruta för att lägga till en ny kommer att visas:
Knapparna i verktygsfältet utför funktioner beroende på sammanhanget, det vill säga vilken nod i trädet eller listan som har valts, så kommer den aktuella proceduren att utföras.
Låt oss ta ett exempel med tilläggsknappen («+»):
Koden för att skapa knappen:
ttk::button $frm_tool.btn_add -command Add -image add_grey_32Här ser vi att när knappen trycks kommer proceduren «Add» att utföras, dess kod:
proc Add {} {
global active_cluster host
# Bestämmer identifieraren för den valda noden
set id [.frm_tree.tree selection]
# Bestämmer värdet av denna nod
set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
set key [lindex [split $id "::"] 0]
# beroende på vad som valts kommer rätt procedur att startas
if {$key eq "" || $key eq "server"} {
set host [ Add::server ]
return
}
Add::$key .frm_tree.tree $host $values
}Här är en av fördelarna med ticklet — vi kan skicka värdet av en variabel som procedurnamn:
Add::$key .frm_tree.tree $host $valuesDet vill säga, om vi till exempel klickar på huvudservern och trycker på «+» kommer proceduren Add::server att startas. Om vi klickar på klustret startas Add::cluster och så vidare (jag kommer att skriva mer om varifrån de nödvändiga «nycklarna» kommer lite senare). De nämnda procedurerna ritar de grafiska elementen som motsvarar sammanhanget.
Som ni redan har märkt, har formulären en liknande stil — och det är inte förvånande, eftersom de visas av en och samma procedur, eller snarare den huvudsakliga ramen för formuläret (fönster, knappar, bild, etikett), namn på proceduren: 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 med ikon
ttk::label $win_name.lbl -image $img
# ram med inmatningsfält
set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
grid columnconfigure $frm 0 -weight 1
grid rowconfigure $frm 0 -weight 1
# ram och knappar
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
}
Anrop parametrar: titel, bildnamn för ikonen från biblioteket (lib/images.tcl) och en valfri parameter för fönamn (som standard .add). Således, om vi tar de ovan nämnda exemplen för att lägga till huvudservern och klustret, blir anropet respektive:
AddToplevel "Lägg till huvudserver" server_grey_64eller
AddToplevel "Lägg till kluster" cluster_grey_64Och för att fortsätta med dessa exempel visar jag procedurer som ger dialogerna för att lägga till servern eller klustret.
Add::server
proc Add::server {} {
global default
# visar huvudformuläret
set frm [AddToplevel "Lägg till huvudserver" server_grey_64]
# lägger till etiketter och inmatningsfält i detta formulär
label $frm.lbl_host -text "Serveradress"
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]
# omdefinierar knapptryckhanteraren
.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 globala 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 "Lägg till kluster" cluster_grey_64]
label $frm.lbl_host -text "Huvudserverns adress"
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 "Kluster namn"
entry $frm.ent_name
label $frm.lbl_secure_connect -text "Säker anslutning"
ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
label $frm.lbl_expiration_timeout -text "Stäng av inaktiva processer efter:"
entry $frm.ent_expiration_timeout -textvariable expiration_timeout
label $frm.lbl_session_fault_tolerance_level -text "Fel-toleransnivå"
entry $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
label $frm.lbl_load_balancing_mode -text "Lastbalanseringsläge"
ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode
-values $default(load_balancing_mode)
label $frm.lbl_errors_count_threshold -text "Tillåten felmarginal för serverfel, %"
entry $frm.ent_errors_count_threshold -textvariable errors_count_threshold
label $frm.lbl_processes -text "Arbetande processer:"
label $frm.lbl_lifetime_limit -text "Omstartperiod, sek."
entry $frm.ent_lifetime_limit -textvariable lifetime_limit
label $frm.lbl_max_memory_size -text "Tillåten minnesvolym, KB"
entry $frm.ent_max_memory_size -textvariable max_memory_size
label $frm.lbl_max_memory_time_limit -text "Överskridningstid för tillåten minnesvolym, sek."
entry $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Tvingar avslutning av problematiska processer"
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
# omdefiniera händelsehanteraren
.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
}Vid jämförelsen av koden för dessa procedurer är skillnaden tydlig även för det ovana ögat, jag fokuserar på hanteraren för knappen "Ok". I Tk kan grafiska elementegenskaper överskrivas under körning av programmet med hjälp av alternativet konfigurera. Till exempel, den ursprungliga kommandot för att visa knappen:
ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }Men i våra formulär beror kommandot på den önskade funktionaliteten:
.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 ovanstående exemplet är knappen "allokerad" för att starta proceduren för att lägga till kluster.
Här bör vi göra en avstickare till arbetet med grafiska element i Tk – för olika datainmatningselement (entry, combobox, checkbutton osv.) har ett sådant parameter införts som textvariabel (textvariable):
entry $frm.ent_lifetime_limit -textvariable lifetime_limitDenna variabel är definierad i det globala namnområdet och innehåller det aktuella inmatade värdet. Det vill säga, för att få den inmatade texten från fältet måste man helt enkelt läsa värdet av den motsvarande variabeln (under förutsättning att den är definierad vid skapandet av elementet).
Den andra metoden för att få den inmatade texten (för element av typen entry) är att använda kommandot get:
.add.frm.ent_name getBåda dessa metoder kan ses i den ovanstående koden.
Att trycka på denna knapp startar, i det här fallet, proceduren RunCommand med den formade kommandosträngen för att lägga till klustret i termer av 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:1545Här har vi nått den huvudsakliga kommandot, som hanterar starten av rac med de behövda parametrarna, analyserar också kommandoutsignalen till listor och returnerar, om det krävs:
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
# öppnar en kanal i icke-blockerande läge
# $rac - kommandot med fullständig sökväg
# $par - formade startnycklar och alternativ
set pipe [open "|$rac_cmd $par" "r"]
try {
set lst ""
set l ""
# kommandots utdata läggs till i listan
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 felhanteraren
ErrorParcing $result $options
return ""
}
}Efter att ha matat in data för huvudservern kommer den att läggas till i trädet, det är följande kod som ansvarar för detta i den ovan nämnda proceduren Add:server:
.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"Nu, genom att klicka på serverns namn i trädet, får vi en lista över kluster som hanteras av servern, och genom att klicka på klustret får vi en lista över klusterkomponenter (servrar, informationsdatabaser och så vidare). Detta har implementerats i proceduren TreePress (fil lib/function.tcl):
proc TreePress {tree} {
global host server active_cluster infobase
# bestämmer det markerade elementet
set id [$tree selection]
# sätter önskade globala variabler
SetGlobalVarFromTreeItems $tree $id
# Bestämmer nyckel och värde, det vill säga typ av valt element
set values [$tree item $id -values]
set key [lindex [split $id "::"] 0]
# och beroende på vad som valts kommer motsvarande procedur att köras
# i namnområdet Run
Run::$key $tree $host $values
}Därmed kommer Run::server att köras för huvudservern (för klustret — Run::cluster, för arbetsservern — Run::work_server och så vidare). Det vill säga värdet av variabeln $key är en del av namnet på trädelementet, som ställs in av alternativet -id.
Låt oss uppmärksamma proceduren
Run::server
proc Run::server {tree host values} {
# Hämtar listan över kluster för den begärda servern
set lst [RunCommand server::$host "cluster list $host"]
if {$lst eq ""} {return}
set l [lindex $lst 0]
#puts $lst
# Tar bort överskott från listan
.frm_work.tree_work delete [ .frm_work.tree_work children {}]
# Läser listan
foreach cluster_list $lst {
# Fyller listan med erhållna värden
InsertItemsWorkList $cluster_list
# Bearbetar utdata (lista) för att lägga till data i trädet
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]]
}
}
}
# Lägger till kluster i trädet
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"
# Lägger till element i klustret
InsertClusterItems $tree $id
}
}
if { [$tree exists "agent_admins::$id"] == 0 } {
$tree insert "server::$host" end -id "agent_admins::$id" -text "Administratörer" -values "$id"
#InsertClusterItems $tree $id
}
}Denna procedur bearbetar vad som har mottagits från servern via kommandot RunCommand och lägger till olika element i trädet — kluster, olika rot-element (databaser, arbetsservrar, sessioner, osv). Om man tittar noga kan man se anropet av proceduren InsertItemsWorkList. Den används för att lägga till element i den grafiska listan, bearbetande utdata från konsolverktyget rac, som tidigare returnerades i form av en lista till variabeln $lst. Detta är en lista av listor, som innehåller par av element separerade med kolon.
Till exempel, en lista över klusteranslutningar:
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
I grafisk form kommer det att se ut ungefär så här:
Den nämnda proceduren identifierar namnen på elementen för rubriken och data för att fylla i tabellen:
InsertItemsWorkList
proc InsertItemsWorkList {lst} {
global work_list_row_count
# ställer in färgväxling för raden
if [expr $work_list_row_count % 2] {
set tag dark
} else {
set tag light
}
# analyserar rader i par nyckel - värde
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]
}
}
# fyller i 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
# ställer in rubriker
foreach j $column_list {
.frm_work.tree_work heading $j -text $j
}
incr work_list_row_count
}Här används istället för det enkla kommandot [split $str «:»], som delar en sträng till element separerade av «:» och returnerar en lista, ett reguljärt uttryck, eftersom vissa element också innehåller kolon.
Proceduren InsertClusterItems (en av flera liknande) lägger helt enkelt till en lista med underordnade element med motsvarande identifierare i trädet för det begärda elementet cluster.
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 "Arbetsservrar" -values "$id"
$tree insert $parent end -id "admins::$id" -text "Administratörer" -values "$id"
$tree insert $parent end -id "managers::$id" -text "Klustermanagere" -values $id
$tree insert $parent end -id "processes::$id" -text "Arbetsprocesser" -values "workprocess-all"
$tree insert $parent end -id "sessions::$id" -text "Sessioner" -values "sessions-all"
$tree insert $parent end -id "locks::$id" -text "Låsningar" -values "blocks-all"
$tree insert $parent end -id "connections::$id" -text "Anslutningar" -values "connections-all"
$tree insert $parent end -id "profiles::$id" -text "Säkerhetsprofiler" -values $id
}
Man kan också överväga ytterligare två alternativ för att implementera en liknande procedur, där man tydligt kan se hur man kan optimera och eliminera upprepande kommandon:
I denna procedur hanteras tillägg och kontroll på ett direkt sätt:
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åsningar" -values "$id"
}
if { [$tree exists "connections::$id"] == 0 } {
$tree insert $parent end -id "connections::$id" -text "Anslutningar" -values "$id"
}
}
Och här är metoden mer korrekt:
InsertProfileItems
proc InsertProfileItems {tree id} {
set parent "profile::$id"
set lst {
{dir "Virtuella kataloger"}
{com "Tillåtna COM-klasser"}
{addin "Extern komponenter"}
{module "Extern rapporter och behandlingar"}
{app "Tillåtna appar"}
{inet "Internetresurser"}
}
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
}
}Skillnaden mellan dem ligger i användningen av loopen, där det upprepande kommandot (kommandona) utförs. Vilken metod som ska användas är upp till utvecklaren.
Vi har sett på tillägg av element och hämtning av data, nu är det dags att fokusera på redigering. Eftersom samma parametrar (med undantag för informationsdatabasen) vanligen används för redigering och tillägg, så används också samma dialogrutor. Algoritmen för att kalla procedurer för tillägg ser ut så här:
Add::$key->AddToplevel
Och för redigering så här:
Edit::$key->Add::$key->AddTopLevel
Som exempel tar vi redigering av klustret, dvs. genom att klicka på kluster namnet i trädet, trycker vi på redigeringsknappen i verktygsfältet (pennsymbolen) och motsvarande formulär visas 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 ""
}
# vi ritar formen för klustret
set frm [Add::cluster $tree $host $values]
# ändrar texten på etiketten
$frm configure -text "Redigera kluster"
set active_cluster $values
# hämtar data om det valda klustret
set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
# fyller i fälten
FormFieldsDataInsert $frm $lst
# stänger av fälten som inte får redigeras
$frm.ent_host configure -state disable
$frm.ent_port configure -state disable
# omdefinierar handläggaren
.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
}
}Baserat på kommentarerna i koden är i princip allt tydligt, förutom det faktum att knappens hanterare är omdefinierad och det finns en procedur FormFieldsDataInsert som fyller i fälten med data och initierar variabler:
FormFieldsDataInsert
proc FormFieldsDataInsert {frm lst} {
foreach i [lindex $lst 0] {
# hämtar listan med parametrar och värden
if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
# ändrar symbolerna
regsub -all -- "-" [string trim $param] "_" entry_name
# fyller i 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 """]
}
# för kryssrutor ändrar vi värdena
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 denna procedur kom ytterligare en fördel med tcl fram – värdena från andra variabler används som namn på variablerna. Det vill säga, för att automatisera ifyllningen av formulär och initieringen av variabler, motsvarar namn på fält och variabler nycklarna i kommandoraden för verktyget rac samt namnen på parametrar i kommandoutdata med vissa undantag – bindestreck ersätts med understreck. Till exempel scheduled-jobs-deny motsvarar fältet ent_scheduled_jobs_deny och variabeln scheduled_jobs_deny.
Formulär för tillägg och redigering kan skilja sig åt i fältens sammansättning, till exempel arbetet med informationsbasen:
Tillägg av IB

Redigering av IB

I redigeringproceduren Edit::infobase läggs de nödvändiga fälten till formuläret, koden är omfattande så jag tar inte med den här.
På motsvarande sätt har procedurer för tillägg, redigering och radering implementerats för övriga element.
Eftersom verktygets arbete förutsätter ett obegränsat antal servrar, kluster, informationsbaser osv., har flera globala variabler införts för att bestämma till vilket kluster en viss server eller IB hör. Det vill säga, proceduren går rekursivt igenom alla föräldraelement och ställer in variabler:
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-klustret möjliggör arbete både med och utan autentisering. Det finns två typer av administratörer – klusteragentens administratör och klusteradministratören. För korrekt funktion har dessutom 4 globala variabler införts som innehåller administratörens inloggningsuppgifter. Det vill säga, om det finns ett administratörskonto i klustret kommer en dialogruta för att ange inloggning och lösenord att visas, uppgifterna kommer att sparas i minnet och tillämpas i varje kommando för det aktuella klustret.
Detta hanteras av felhanteringsproceduren
ErrorParcing
proc ErrorParcing {err opt} {
global cluster_user cluster_pwd agent_user agent_pwd
switch -regexp -- $err {
"Cluster administrator is not authenticated" {
AuthorisationDialog "Klassadministratör"
.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 "Agentadministratör"
.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 "Klassadministratör"
.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 "Agentadministratör"
.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 vill säga, beroende på vad kommandot returnerar, kommer reaktionen att vara därefter.
För närvarande är funktionaliteten genomförd till cirka 95 %, det återstår att implementera arbetet med säkerhetsprofiler och testa det =). Det är allt. Jag ber om ursäkt för det abrupta berättandet.
Koden är, som vanligt, tillgänglig .
Uppdatering: Jag har slutfört arbetet med säkerhetsprofiler. Nu är funktionaliteten genomförd till 100 %.
Uppdatering 2: lokalisation till engelska och ryska har lagts till, funktionen har testats i win7

Källa: habr.com
