Ndërsa po thellohej në temën e produkteve 1C në mjedisin Linux, u zbulua një disavantazh — mungesa e një instrumenti të rehatshëm grafikor për menaxhimin e klastereve të servers 1C. Prandaj, u vendos të rregullohej ky disavantazh duke shkruar një GUI për utilitarin konsol rac. Gjuha e zhvillimit u zgjodh tcl/tk, siç më duket, më e përshtatshme për këtë detyrë. Ja, disa aspekte interesante të zgjidhjes do të paraqiten në këtë material.
Për të punuar do të nevojiten shpërndarjet tcl/tk dhe 1C. Dhe për shkak se vendosa të shfrytëzoj maksimalisht mundësitë e shpërndarjes bazë tcl/tk pa përdorur paketa të jashtme, do të nevojitet versioni 8.6.7, që përfshin ttk — paketë me elemente grafike shtesë, nga të cilat do të na nevojitet kryesisht ttk::TreeView, i cili lejon shfaqjen e të dhënave si në formën e një strukture të pemës ashtu edhe në formën e një tabele (listë). Po ashtu, në versionin e ri është rregulluar puna me përjashtimet (komanda try, e cila përdoret në projekt për të nisur komandat e jashtme).
Projekti përbëhet nga disa skedarë (edhe pse nuk ka asgjë që ndalon të gjitha të bëhen një):
rac_gui.cfg — konfigurimi defolt
rac_gui.tcl — skedari kryesor i nisjes
Në katalogun lib ndodhen skedarët që ngarkohen automatikisht gjatë startit:
function.tcl — skedari me procedurat
gui.tcl — ndërfaqja kryesore grafike
images.tcl — biblioteka e imazheve në base64
Skedari rac_gui.tcl, në fakt, nis interpretuesin, inicializon variablat, ngarkon modulët, konfigurimet dhe kështu me radhë. Përmbajtja e skedarit me komente:
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"
}
}Pas ngarkimit të të gjitha që nevojitet dhe verifikimit të pranisë së utilitarit rac, do të nisë dritarja grafike. Ndërfaqja e programit përbëhet nga tri elementë:
Paneli i mjeteve, pema dhe lista
Përmbajtja e "pemës" e kam bërë sa më të ngjashme me komponentin standard të Windows nga 1C.
Kodi kryesor që formon këtë dritare ndodhet në skedarin
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
Algoritmi i punës me programin është si në vijim:
1. Në fillim, duhet të shtoni serverin kryesor të klasterit (dmth, serveri i menaxhimit të klasterit (në Linux menaxhimi nis me komandën “/opt/1C/v8.3/x86_64/ras cluster —daemon”).
Për këtë, klikoni mbi butonin “+” dhe në dritaren që hapet, futni adresën e serverit dhe portin:

Më pas, në pemë do të shfaqet serveri ynë, për të cilin, me një klikim do të hapet lista e klastereve ose do të shfaqet një gabim lidhjeje.
2. Klikimi mbi emrin e grupit do të hapë një listë funksionesh të disponueshme për të.
3.…
Pra, për ta shtuar një grup të ri, zgjidhni çfarëdo që është në listë dhe klikoni butonin «+» në panelin e shërbimeve dhe do të hapet një dialog për shtimin e të riut:
Butonat në panelin e shërbimeve kryejnë funksione në varësi të kontekstit, dmth, çfarëdo elementi në pemë ose listë që është zgjedhur, do të kryhet procedura e caktuar.
Të shohim me shembullin e butonit të shtimit («+»):
Kodi i krijimit të butonit:
ttk::button $frm_tool.btn_add -command Add -image add_grey_32Këtu shohim se kur klikohet butoni do të ekzekutohet procedura «Add», kodi i saj:
proc Add {} {
global active_cluster host
# Përcaktojmë identifikuesin e elementit të zgjedhur
set id [.frm_tree.tree selection]
# Përcaktojmë vlerën e këtij elementi
set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
set key [lindex [split $id "::"] 0]
# në varësi të asaj që zgjodhëm do të niset procedura e nevojshme
if {$key eq "" || $key eq "server"} {
set host [ Add::server ]
return
}
Add::$key .frm_tree.tree $host $values
}Këtu dallohet një nga avantazhet e tikllit — si emër procedure mund të kalojmë vlerën e variablit:
Add::$key .frm_tree.tree $host $valuesDmth, për shembull, nëse klikohet mbi serverin kryesor dhe klikoni «+», do të nisë procedura Add::server, nëse mbi grupin — Add::cluster dhe kështu me radhë (për mënyrën se si erdhën «çelësat» e nevojshëm do të shkruaj më poshtë), procedurat e përmendura vizatojnë elemente grafike përkatëse kontekstit.
Siç e keni vënë re, formularët janë të ngjashëm në stil — kjo nuk është befasuese, pasi ato gjenerohen nga një procedurë e vetme, saktësisht nga skeleti kryesor i formularit (dritarja, butonat, imazhi, etiketa), emri i procedurës është 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
# etiketa me ikonë
ttk::label $win_name.lbl -image $img
# kornizë me fushat e hyrjes
set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
grid columnconfigure $frm 0 -weight 1
grid rowconfigure $frm 0 -weight 1
# korniza dhe butonat
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
}
Parametrat e thirrjes: titulli, emri i imazhit për ikonën nga biblioteka (lib/images.tcl) dhe parametri opsional i emrit të dritares (default .add). Kështu, nëse marrim shembujt e mësipërm për shtimin e serverit kryesor dhe të klasterit, thirrja do të jetë përkatësisht:
AddToplevel "Shtimi i serverit kryesor" server_grey_64ose
AddToplevel "Shtimi i klasterit" cluster_grey_64Dhe vazhdoj me këto shembuj për të treguar procedurat që shfaqin dialogët e shtimit për serverin ose klasterin.
Add::server
proc Add::server {} {
global default
# shfaqim formën kryesore
set frm [AddToplevel "Shtimi i serverit kryesor" server_grey_64]
# shtojmë etiketat dhe fushat e hyrjes në këtë formë
label $frm.lbl_host -text "Adresa e serverit"
entry $frm.ent_host
label $frm.lbl_port -text "Porti"
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]
# ripërcaktojmë trajtuesin e klikimit të butonit
.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 ""
}
# vendosim variablat globale ()
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 "Shtimi i klasterit" cluster_grey_64]
label $frm.lbl_host -text "Adresa e serverit kryesor"
entry $frm.ent_host
label $frm.lbl_port -text "Porti"
entry $frm.ent_port
$frm.ent_port insert end $default(port)
label $frm.lbl_name -text "Emri i klasterit"
entry $frm.ent_name
label $frm.lbl_secure_connect -text "Koneksion i sigurt"
ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
label $frm.lbl_expiration_timeout -text "Ndalo proceset e fikura pas:"
entry $frm.ent_expiration_timeout -textvariable expiration_timeout
label $frm.lbl_session_fault_tolerance_level -text "Niveli i tolerancës për dështim"
entry $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
label $frm.lbl_load_balancing_mode -text "Režimi i shpërndarjes së ngarkesës"
ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode
-values $default(load_balancing_mode)
label $frm.lbl_errors_count_threshold -text "Kufiri i pranuar i numrit të gabimeve të serverit, %"
entry $frm.ent_errors_count_threshold -textvariable errors_count_threshold
label $frm.lbl_processes -text "Proceset e punës:"
label $frm.lbl_lifetime_limit -text "Periudha e riparimit, sek."
entry $frm.ent_lifetime_limit -textvariable lifetime_limit
label $frm.lbl_max_memory_size -text "Kufiri i pranuar i kujtesës, KB"
entry $frm.ent_max_memory_size -textvariable max_memory_size
label $frm.lbl_max_memory_time_limit -text "Intervali i tepricës së kujtesës së pranuar, sek."
entry $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Mbylle me zor proceset problematike"
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
# ripërcaktojmë trajtimin
.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
}Kur krahasohet kodi i këtyre procedurave, ndryshimi është i dukshëm me sy të zakonshëm. Do ta theksoj trajtuesin e butonit "Ok". Në Tk, pronat e elementeve grafike mund të tejkalohen gjatë ekzekutimit të programit me anë të opsionit konfiguro.. Për shembull, komandën fillestare për të shfaqur butonin:
ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }Por në format tona, komanda varet nga funksionaliteti i kërkuar:
.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
}
Në shembullin e mësipërm, butoni "ka" fillimin e procedurës për shtimin e një klasteri.
Këtu është e rëndësishme të bëhet një shpjegim për punën me elementet grafike në Tk — për elemente të ndryshme për hyrje të dhënash (entry, combobox, checkbutton etj.) është futur një parametrin si variabla tekstuale (textvariable):
entry $frm.ent_lifetime_limit -textvariable lifetime_limitKjo variablë është e definuar në hapësirën globale të emrave dhe përmban vlerën aktuale të hyrë. Pra, në mënyrë që të marrësh tekstin e futur nga fusha, duhet thjesht të lexosh vlerën e variablës përkatëse (sigurisht me kushtin që ajo të jetë e definuar në krijimin e elementit).
Metoda e dytë për të marrë tekstin e futur (për elemente të tipit entry) është përdorimi i komandës get:
.add.frm.ent_name getTë dyja këto metoda mund të shihen në kodin e mësipërm.
Shtypja e këtij butoni, në këtë rast, aktivizon procedurën RunCommand me një varg të formuar për komandën për të shtuar klasterin në terma 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:1545Këtu arrijmë te komanda kryesore, e cila menaxhon ekzekutimin e rac me parametrat që na duhen, gjithashtu analizon daljen e komandave në lista dhe kthen, nëse është e nevojshme:
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
# hap shkrim kanali në mënyrë që nuk bllokohet
# $rac - komandë me rrugë të plotë
# $par - çelësat dhe opsionet e formuara për nisje
set pipe [open "|$rac_cmd $par" "r"]
try {
set lst ""
set l ""
# dalja e komandës e shtohet në listë
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} {
# Aktivizimi i trajtuesit të gabimeve
ErrorParcing $result $options
return ""
}
}Pas futjes së të dhënave të serverit kryesor, do të shtohet në pemën, për këtë, në procedurën e mësipërme Add:server, përgjigjet kodi i mëposhtëm:
.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"Tani, duke klikuar mbi emrin e serverit në pemë, ne do të marrim një listë të klasterëve që menaxhohen nga ai server, dhe duke klikuar mbi klasterin, do të marrim një listë të elementeve të klasterit (serverësh, baza të dhënash etj.). Kjo është realizuar në procedurën TreePress (skedari lib/function.tcl):
proc TreePress {tree} {
global host server active_cluster infobase
# përcaktojmë elementin e zgjedhur
set id [$tree selection]
# vendosim variablat globale të nevojshme
SetGlobalVarFromTreeItems $tree $id
# Përcaktojmë çelësin dhe vlerën, pra lloji i elementit të zgjedhur
set values [$tree item $id -values]
set key [lindex [split $id "::"] 0]
# dhe në varësi të asaj që përzgjidhim do të aktivizohet procedura përkatëse
# në hapësirën e emrave Run
Run::$key $tree $host $values
}Sipas kësaj, për serverin kryesor do të aktivizohet Run::server (për klasterin — Run::cluster, për serverin punues — Run::work_server etj.). Pra, vlera e variablës $key është pjesë e emrit të elementit të pemës, e cila përcaktohet nga opsioni -id.
Le të Vëmë Rënie në Procedurë
Run::server
proc Run::server {tree host values} {
# marrim lista e klasterëve të serverit të kërkuar
set lst [RunCommand server::$host "cluster list $host"]
if {$lst eq ""} {return}
set l [lindex $lst 0]
#puts $lst
# heqim të tepërt nga lista
.frm_work.tree_work delete [ .frm_work.tree_work children {}]
# lexojmë listën
foreach cluster_list $lst {
# Populloni listën me vlerat e marra
InsertItemsWorkList $cluster_list
# procesojmë daljen (listën) për të shtuar të dhënat në pemë
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]]
}
}
}
# shtojmë klasterët në pemë
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"
# shtojmë elementet në klaster
InsertClusterItems $tree $id
}
}
if { [$tree exists "agent_admins::$id"] == 0 } {
$tree insert "server::$host" end -id "agent_admins::$id" -text "Administratorët" -values "$id"
#InsertClusterItems $tree $id
}
}Kjo procedurë përpunon atë që është marrë nga serveri përmes komandës RunCommand dhe shton gjëra të ndryshme në pemë — klastere, elemente të ndryshme rrënjësore (bazat, serverët e punës, sesionet dhe kështu me radhë). Nëse shikoni me kujdes, brenda mund të vëreni thirrjen e procedurës InsertItemsWorkList. Kjo përdoret për të shtuar elemente në listën grafike, duke përpunuar daljen e utilitarit të konsolës rac, i cili më parë ishte në formën e një liste që u kthye në variablin $lst. Ky është një listë e listave, që përmban çifte elementësh të ndarë me dy pika.
Për shembull, lista e lidhjeve të klasterit:
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
Vizualisht, kjo do të duket kështu:
Procedura e përmendur lartë përzgjedh emrat e elementeve për titullin dhe të dhënat për mbushjen e tabelës:
InsertItemsWorkList
proc InsertItemsWorkList {lst} {
global work_list_row_count
# përcaktimi i alternimit të ngjyrave për rresht
if [expr $work_list_row_count % 2] {
set tag dark
} else {
set tag light
}
# analizimi i rreshtave në çifte çelës - vlerë
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]
}
}
# mbushja e tabelës
.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
# përcaktimi i titujve
foreach j $column_list {
.frm_work.tree_work heading $j -text $j
}
incr work_list_row_count
}Këtu në vend të komandës së thjeshtë [split $str «:»], e cila ndan stringun në elemente të ndara me «:» dhe kthen një listë, është përdorur një shprehje rregull, pasi disa elemente gjithashtu përmbajnë dy pika.
Procedura InsertClusterItems (një nga disa të ngjashme) thjesht i shton pemës elementin e kërkuar cluster një listë të elementeve fëmijë me identifikuesit përkatës.
InsertClusterItems
proc InsertClusterItems {tree id} {
set parent "cluster::$id"
$tree insert $parent end -id "infobases::$id" -text "Baza Informative" -values "$id"
$tree insert $parent end -id "servers::$id" -text "Serverat e Punës" -values "$id"
$tree insert $parent end -id "admins::$id" -text "Administratoret" -values "$id"
$tree insert $parent end -id "managers::$id" -text "Menaxherët e Klasterit" -values $id
$tree insert $parent end -id "processes::$id" -text "Proceset e Punës" -values "workprocess-all"
$tree insert $parent end -id "sessions::$id" -text "Seancat" -values "sessions-all"
$tree insert $parent end -id "locks::$id" -text "Bllokimet" -values "blocks-all"
$tree insert $parent end -id "connections::$id" -text "Konnections" -values "connections-all"
$tree insert $parent end -id "profiles::$id" -text "Profilat e Sigurisë" -values $id
}
Mund të shqyrtohen edhe dy varianta të tjera të realizimit të një procedure të tillë, ku do të jetë e qartë se si mund të optimizohet dhe të hiqen komandat e përsëritura:
Në këtë procedurë shtimi dhe verifikimi janë zgjidhur direkt:
InsertBaseItems
proc InsertBaseItems {tree id} {
set parent "infobase::$id"
if { [$tree exists "sessions::$id"] == 0 } {
$tree insert $parent end -id "sessions::$id" -text "Seancat" -values "$id"
}
if { [$tree exists "locks::$id"] == 0 } {
$tree insert $parent end -id "locks::$id" -text "Bllokimet" -values "$id"
}
if { [$tree exists "connections::$id"] == 0 } {
$tree insert $parent end -id "connections::$id" -text "Konnections" -values "$id"
}
}
Dhe këtu qasja është më e saktë:
InsertProfileItems
proc InsertProfileItems {tree id} {
set parent "profile::$id"
set lst {
{dir "Katalogët Virtual"}
{com "Klasa COM të Lejuara"}
{addin "Komponentë të Jashtëm"}
{module "Raportet dhe Përpunimet e Jashtme"}
{app "Aplikacione të Lejuara"}
{inet "Burimet e Internetit"}
}
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
}
}Dallimi midis tyre është në përdorimin e ciklit, ku ekzekutohet komandë (komandat) e përsëritur. Cila qasje të përdoret — kjo varet nga zhvilluesi.
Shtimi i elementeve dhe marrja e të dhënave e kemi trajtuar, tani është koha të ndalemi në redaktimin. Duke qenë se, kryesisht, për redaktimin dhe shtimin përdoren të njëjtat parametra (përjashtim bën baza informative), atëherë formato dialogu përdoren të njëjtat. Algoritmi i thirrjes së procedurave për shtim duket kështu:
Add::$key->AddToplevel
Dhe për redaktim kështu:
Edit::$key->Add::$key->AddTopLevel
Si shembull, le të marrim redaktimin e grumbullit, dmth me një klik në pemë mbi emrin e grumbullit, ne klikojmë butonin e redaktimit në panelin e veglave (piktura e lapsit) dhe në ekran do të shfaqet forma përkatëse:

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 ""
}
# krijojmë formën për klasterin
set frm [Add::cluster $tree $host $values]
# ndryshojmë tekstin në etiketë
$frm configure -text "Modifikimi i klasterit"
set active_cluster $values
# marrim të dhënat për klasterin e përzgjedhur
set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
# plotësojmë fushat
FormFieldsDataInsert $frm $lst
# ndalojmë fushat, redaktimi i të cilave është i ndaluar
$frm.ent_host configure -state disable
$frm.ent_port configure -state disable
# ripërcaktojmë menaxherin
.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
}
}Nga komentet në kod, në thelb, gjithçka është e qartë, përveç faktit se kodi i menaxherit të butonit është ripërcaktuar dhe ekziston procedura FormFieldsDataInsert, e cila plotëson fushat me të dhëna dhe inicializon variablat:
FormFieldsDataInsert
proc FormFieldsDataInsert {frm lst} {
foreach i [lindex $lst 0] {
# marrim listën e parametrave dhe vlerave
if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
# ndryshojmë simbolet
regsub -all -- "-" [string trim $param] "_" entry_name
# plotësojmë me të dhëna
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 """]
}
# për kutitë kontrolluese ndryshojmë vlerat
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
}
}
}
}
}
Në këtë procedurë doli një avantazh tjetër i tcl - emrat e variablave zëvendësohen me vlerat e variablave të tjerë. Kështu, për automatizimin e plotësimit të formularëve dhe iniciimin e variablave, emrat e fushave dhe variablave përkojnë me çelësat e komandës së utilitetit rac dhe emrat e parametrave të daljes së komandave me disa përjashtime - minusi zëvendësohet me nënvizim. Për shembull scheduled-jobs-deny përkon me fushën ent_scheduled_jobs_deny dhe variablën scheduled_jobs_deny.
Format e shtimit dhe editimit mund të ndryshojnë në përbërjen e fushave, për shembull, puna me bazën e informacionit:
Shtimi i IB

Editimi i IB

Në procedurën e editimit Edit::infobase, në formular shtohen fushat e nevojshme, kodi është i gjerë, prandaj nuk e jap këtu.
Me të njëjtin parim janë realizuar procedurat e shtimit, editimit, fshirjes dhe për elementet e tjera.
Duke qenë se puna e utilitetit nënkupton një numër të paçmuar serverësh, klasterësh, bazash informacioni etj., për të përcaktuar se cili server ose IB i përkasin cilit klaster, janë futur disa variabla globalë, vlerat e të cilëve vendosen me çdo klikim mbi elementët e pemës. Kështu, procedura kalon në mënyrë rekursive nëpër të gjithë elementët prind dhe vendos variablat:
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
}
}
Klasteri 1C lejon punën si me autorizim ashtu edhe pa. Ekzistojnë dy lloje administratorësh - administratori i agjenteve të klasterit dhe administratori i klasterit. Përkatësisht, për funksionimin e saktë janë futur edhe 4 variabla global, të cilat përmbajnë emrin dhe fjalëkalimin e administratorit. Kështu, nëse në klaster ekziston një llogari administratori, do të shfaqet një dialog për futjen e emrit dhe fjalëkalimit, të dhënat do të ruhen në memories dhe do të vendosen në çdo komandë për përkatësin klaster.
Për këtë përgjigjet procedura e trajtimit të gabimeve
ErrorParcing
proc ErrorParcing {err opt} {
global cluster_user cluster_pwd agent_user agent_pwd
switch -regexp -- $err {
"Cluster administrator is not authenticated" {
AuthorisationDialog "Administrator i grupit"
.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 "Administrator i agentit të grupit"
.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 i grupit nuk është autentikuar" {
AuthorisationDialog "Administrator i grupit"
.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 i serverit qendror nuk është autentikuar" {
AuthorisationDialog "Administrator i agentit të grupit"
.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"
}
}
}Pra, në varësi të asaj që kthehet nga komanda, do të jetë dhe reagimi përkatës.
Aktualisht, funksionaliteti është realizuar rreth 95%, mbetet të implementohet funksionimi me profilin e sigurisë dhe të testojmë =). Kjo është gjithçka. Më vjen keq për tregimin e shpejtuar.
Kodi, si zakonisht, është i disponueshëm .
Përditësim: E kam përfunduar punën me profilin e sigurisë. Tani funksionaliteti është realizuar në 100%.
Përditësim 2: është shtuar lokalizimi në anglisht dhe rusisht, është verifikuar funksionimi në win7

Burimi: habr.com
