Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Ndërsa thellohem në temën e produkteve të 1C në mjedisin linux, u zbulua një mangësi — mungesa e një mjeti grafikor shumëplatformatik komod për menaxhimin e grupeve të serverëve 1C. Prandaj, vendosa ta riparoj këtë mangësi duke shkruar një GUI për utilitarin konsolar rac. Gjuha e zhvillimit u zgjodh Tcl/Tk, pasi mendoj se është më e përshtatshme për këtë detyrë. Dhe këtu, disa aspekte interesante të zgjidhjes dua t'i paraqes në këtë material.

Për funksionimin do të nevojiten distribuera Tcl/Tk dhe 1C. Dhe pasi vendosa të shfrytëzoj sa më shumë mundësitë e paketës bazë Tcl/Tk pa përdorur paketa të jashtme, do të nevojitet versi 8.6.7, e cila përfshin ttk — paketë me elemente grafike shtesë, nga të cilat do të kemi nevojë kryesisht për ttk::TreeView, që lejon shfaqjen e të dhënave si në formën e një strukture pemore ashtu edhe në formën e një tabele (listë). Gjithashtu, në versionin e ri është ristrukturuar puna me përjashtimet (komanda try, e cila përdoret në projekt gjatë ekzekutimit të komandave të jashtme).

Projekti përbëhet nga disa skedinja (ndonëse asgjë nuk e pengon të bësh gjithçka një herë):

rac_gui.cfg — konfigurimi default
rac_gui.tcl — skripti kryesor i nisjes
Në katalogun lib ndodhen skedarët që ngarkohen automatikisht gjatë nisjes:
function.tcl — skedari me procedure
gui.tcl — ndërfaqja kryesore grafike
images.tcl — biblioteka e imazheve në base64

Skedari rac_gui.tcl në të vërtetë nis interpretorin, inicializon variablat, ngarkon modulët, konfigurimet etj. Përmbajtja e skedarit me komentet:

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ë gjithçkaje që nevojitet dhe verifikimit të pranishmërisë së utilitarit rac, do të hapet një dritare grafike. Ndërfaqja e programit përbëhet nga tre elemente:

Bara e instrumenteve, pema dhe lista

Përmbajtja e 'pemës' e kam bërë sa më të ngjashme me komponentin standard të Windows nga 1C.

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

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 për të punuar me programin është si më poshtë:

1. Në fillim, duhet të shtoni serverin kryesor të grumbullit (dmth. serverin e menaxhimit të grumbullit (në linux menaxhimi fillon me komandën '/opt/1C/v8.3/x86_64/ras cluster —daemon')).

Për këtë klikoni butonin '+' dhe në dritaren e hapur, futni adresën e serverit dhe portin:

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Më pas, në pemë do të shfaqet serveri ynë, duke e klikur mbi të, do të hapet lista e klasterëve ose do të shfaqet një gabim lidhjeje.

2. Duke klikuar mbi emrin e klasterit do të hapet lista e funksioneve të disponueshme për të.

3.…

Pra, për të shtuar një klaster të ri, zgjidhim një nga ata të disponueshëm në listë dhe klikojmë butonin «+» në panelin e mjetit, dhe do të shfaqet një dialog për shtimin e të riut:

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Butonat në panelin e mjetit kryejnë funksione në varësi të kontekstit, pra, në varësi të asaj që është zgjedhur në pemë ose listë, do të kryhet procedura përkatëse.

Le të shqyrtojmë me shembullin e butonit të shtimit («+»):

Kodi që formon butonin:

ttk::button $frm_tool.btn_add  -command Add  -image add_grey_32

Këtu shohim se kur të klikojmë butonin 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ë kemi zgjedhur do të nisë procedura e duhur
    if {$key eq "" || $key eq "server"} {
        set host [ Add::server ]
        return
    }
    Add::$key .frm_tree.tree $host $values
}

Këtu shfaqet një nga avantazhet e Tikly - si emër procedure mund të kaloni vlerën e një variabli:

Add::$key .frm_tree.tree $host $values

Pra, për shembull, nëse klikojmë mbi serverin kryesor dhe shtypim «+», do të aktivizohet procedura Add::server; nëse është klasteri - Add::cluster, e kështu me radhë (për nga ku vijnë «çelësat» e nevojshëm do të shkruaj më poshtë); procedurat e përmendura vizatojnë elementet grafike përkatëse kontekstit.

Siç mund ta keni vënë re, format janë të ngjashme në stil - kjo nuk është e çuditshme, pasi ato shfaqen nga një procedurë, konkretisht skeleti kryesor i formës (dritarja, butonat, imazhi, etiketa), emri i procedurës 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
    # etiketë me ikonë
    ttk::label $win_name.lbl -image $img
    # kornizë me fusha hyrëse
    set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
    
    grid columnconfigure $frm 0 -weight 1
    grid rowconfigure $frm 0 -weight 1
    # kornizë dhe butona
    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 parametrin opsional të emrit të dritares (në mënyrë që të paracaktuar është .add). Pra, nëse marrim si shembuj të mësipërm për shtimin e serverit kryesor dhe klasterit, thirrja do të jetë përkatësisht:

AddToplevel "Shtimi i serverit kryesor" server_grey_64

ose

AddToplevel "Shtimi i klasterit" cluster_grey_64

Dhe duke vazhduar me këto shembuj, do të tregoj procedurat që nxjerrin dialogët për shtimin e serverit ose klasterit.

Add::server

proc Add::server {} {
    global default
    # shfaqim formularin kryesor
    set frm [AddToplevel "Shtimi i serverit kryesor" server_grey_64]
    # shtojmë etiketat dhe fushat e inputit në këtë formular
    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ërdorim menaxherin 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 "Lidhje e sigurtë"
    ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
    label $frm.lbl_expiration_timeout -text "Prisni proceset e fikura për:"
    entry  $frm.ent_expiration_timeout -textvariable expiration_timeout
    label $frm.lbl_session_fault_tolerance_level -text "Niveli i tolerancës për dështimin e seancave"
    entry  $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
    label $frm.lbl_load_balancing_mode -text "Mënyra e 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 "Pragu i pranueshëm për numrin e gabimeve të serverit, %"
    entry  $frm.ent_errors_count_threshold -textvariable errors_count_threshold
    label $frm.lbl_processes -text "Proceset punuese:"
    label $frm.lbl_lifetime_limit -text "Periudha e ri-përgjimit, sekondë."
    entry  $frm.ent_lifetime_limit -textvariable lifetime_limit
    label $frm.lbl_max_memory_size -text "Kapaciteti maksimal i memories, KB"
    entry  $frm.ent_max_memory_size -textvariable max_memory_size
    label $frm.lbl_max_memory_time_limit -text "Koha e tejkalimit të kapacitetit maksimal të memories, sekondë."
    entry  $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
    label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Mbaroni me forcë 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
    # rikonstruktojmë handler-in
    .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 krahasoni kodin e këtyre procedurave, ndryshimi është i dukshëm me sy të zakonshëm, do të fokusohem tek trajtuesi i butonit «Ok». Në Tk, karakteristikat e elementeve grafikë mund të rregullohen gjatë ekzekutimit të programit përmes opsionit konfiguro. Për shembull, urdhri fillestar për të prodhuar butonin:

ttk::button $frm_btn.btn_ok -image ok_grey_24 -command { }

Por në format tona, komanda varet nga funksionaliteti që kërkohet:

  .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 «zbaton» nisjen e procedurës për shtimin e klasterit.

Këtu duhet të bëjmë një kthesë në punën me elementët grafikë në Tk — për elemente të ndryshme të hyrjes së të dhënave (entry, combobox, checkbutton, etj.) është futur një parametër siç është variabla tekstore (textvariable):

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

Kyçja është e përcaktuar në hapësirën globale dhe përmban vlerën e tanishme të futur. Për të marrë tekstin e futur në fushë, thjesht duhet të lexoni vlerën e përkatëses, natyrisht nëse është e përcaktuar kur është krijuar elementi.

Metoda e dytë për të marrë tekstin e futur (për elementi të tipit entry) është përdorimi i komandës get:

.add.frm.ent_name get

Të dy këto metoda mund të shihen në kodin e mësipërm.

Shtypja e kësaj butoni, në këtë rast, nxit procedurën RunCommand me radhen e formuar të komandës 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:1545

Këtu kemi arritur në komandën kryesore, e cila menaxhon nisjen e rac me parametrat që na nevojiten, gjithashtu analizon daljet e komandave në lista dhe i 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
    # hapim kanali në mod instiktiv
    # $rac - një komande me rrugë të plotë
    # $par - çelësat dhe opsionet e formuara për nisjen
    set pipe [open "|$rac_cmd $par" "r"]
    try {
        set lst ""
        set l ""
        # outputi i komandës shtohet në listën e listave
        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} {
        # Aktivizohet menaxhimi i gabimeve
        ErrorParcing $result $options
        return ""
    }
}

Pasi të futen të dhënat e serverit kryesor, ai do të shtohet në pemë, 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ë grupeve të menaxhuara nga ai server, dhe duke klikuar në grup, do të marrim një listë të elementeve të grupit (servera, bazave të informacionit etj.). Kjo është realizuar në procedurën TreePress (fajl 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, dmth llojin e elementit të zgjedhur
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
   # dhe në varësi të asaj që zgjodhët, do të aktivizohet procedura përkatëse
   # në hapësirën Run
    Run::$key $tree $host $values
}

Prandaj, për serverin kryesor do të aktivizohet Run::server (për klasterin — Run::cluster, për serverin e punës — Run::work_server, etj.). Pra, vlera e variables $key është pjesa e emrit të elementit të pemës, e cila përcaktohet nga opsioni -id.

Le të tregojmë për procedurën

Run::server

proc Run::server {tree host values} {
    # marrim listën e grupeve për serverin e kërkuar
    set lst [RunCommand server::$host "cluster list $host"]
    if {$lst eq ""} {return}
    set l [lindex $lst 0]
    # heqim gjërat e tepërta nga lista
    .frm_work.tree_work delete  [ .frm_work.tree_work children {}]
    # lexojmë listën
    foreach cluster_list $lst {
        # Plotësuojmë listën me vlerat e marra
        InsertItemsWorkList $cluster_list
        # përpunojmë 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ë grupet 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ë grup
            InsertClusterItems $tree $id
        }
    }
    if { [$tree exists "agent_admins::$id"] == 0 } {
        $tree insert "server::$host" end -id "agent_admins::$id" -text "Administratoret" -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ë — grupe, elemente të ndryshme rrënjore (bazat, serverë punues, seanca dhe kështu me radhë). Nëse shikon me kujdes, brenda mund të vëshhtë një thirrje të 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 kthyer në format liste në variablën $lst. Kjo është një listë e listave, që përmban çiftet e elementeve të ndara 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

Në pamjen grafike, kjo do të dukej më shumë kështu:

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Procedura e përmendur më sipër identifikon emrat e elementeve për titullin dhe të dhënat për plotësimin e tabelës:

InsertItemsWorkList

proc InsertItemsWorkList {lst} {
    global work_list_row_count
    # vendosja e alternimit të ngjyrave për rresht
    if [expr $work_list_row_count % 2] {
        set tag dark
    } else {
        set tag light
    }
    # analiza e 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]
        }
    }
     # plotësimi i 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
    # vendosja e 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 vargun në elemente të ndara nga «:» dhe kthen një listë, është aplikuar një shprehje rregullore, pasi disa elemente përmbajnë gjithashtu dy pika.

Procedura InsertClusterItems (një nga disa të ngjashme) thjesht shton në pemë për elementin e nevojshëm klustërin e listës së 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 "Serverët aktiv" -values "$id"
    $tree insert $parent end -id "admins::$id" -text "Administratorët" -values "$id"
    $tree insert $parent end -id "managers::$id" -text "Menaxherët e klasterit" -values $id
    $tree insert $parent end -id "processes::$id" -text "Procese aktive" -values "workprocess-all"
    $tree insert $parent end -id "sessions::$id" -text "Seanca" -values "sessions-all"
    $tree insert $parent end -id "locks::$id" -text "Bllokimet" -values "blocks-all"
    $tree insert $parent end -id "connections::$id" -text "Konekcionet" -values "connections-all"
    $tree insert $parent end -id "profiles::$id" -text "Profilet e sigurisë" -values $id
}

Mund të shqyrtojmë edhe dy variante të tjera të realizimit të një procedure të tillë, ku do të jetë qartë si mund të optimizohet dhe të eliminohen komandat e përsëritura:

Në këtë procedurë, shtimi dhe verifikimi janë zgjidhur në mënyrë të drejtpërdrejtë:

InsertBaseItems

proc InsertBaseItems {tree id} {
    set parent "infobase::$id"
    if { [$tree exists "sessions::$id"] == 0 } {
        $tree insert $parent end -id "sessions::$id" -text "Seanca" -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 "Konekcionet" -values "$id"
    }
}

Këtu qasja është më e saktë:

InsertProfileItems

proc InsertProfileItems {tree id} {
    set parent "profile::$id"
    set lst {
        {dir "Kataloget Virtual"}
        {com "Klasa COM të lejuara"}
        {addin "Komponentët e jashtëm"}
        {module "Raportet dhe përpunimet e jashtme"}
        {app "Aplikacionet e 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ë aplikimin e ciklit, ku ekzekutohet komanda e përsëritur (komandat). Cila qasje të përdoret — kjo është në discretion të zhvilluesit.

Ne kemi shqyrtuar shtimin e elementeve dhe marrjen e të dhënave, tani është koha të ndalemi te redaktimi. Duke qenë se, kryesisht, për redaktimin dhe shtimin përdoren të njëjtat parametra (përjashtimi është baza informative), forma dialoguese gjithashtu përdoren të njëjta. Algoritmi i thirrjes së procedurave për shtimin duket kështu:

Add::$key->AddToplevel

Dhe për redaktimin kështu:

Edit::$key->Add::$key->AddTopLevel

Për shembull, le të marrim redaktimin e grupit, domethënë duke klikuar në emrin e grupit në pemën e informacionit, shtypim butonin e redaktimit në panelin e mjeteve (majtë) dhe forma përkatëse do të shfaqet në ekran:

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk
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 ""
    }
    # krijo formën për klasterin
    set frm [Add::cluster $tree $host $values]
    # ndrysho tekstin në etiketë
    $frm configure -text "Editimi i klasterit"
    
    set active_cluster $values
    # merr të dhënat për klasterin e përzgjedhur
    set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
    # plotëso fushat
    FormFieldsDataInsert $frm $lst
    # çaktivizo fushat, redaktimi i të cilave është i ndaluar
    $frm.ent_host configure -state disable
    $frm.ent_port configure -state disable
    # riemëro 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
    }
}

Në përgjithësi, gjithçka është e qartë nga komentet në kod, përveç faktit se kodi për procesin e butonit është pastruar dhe ka një procedurë FormFieldsDataInsert, e cila mbush 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] {
            # ndërruam simbolet
            regsub -all -- "-" [string trim $param] "_" entry_name
            # mbushim 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 ndërruam 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ë ka dalë njëAvantazh tjetër i tcl — si emra variablash, vendosen vlerat e variablave të tjerë. Pra, për automatizimin e plotësimit të formave dhe inicializimin e variablave, emrat e fushave dhe variablave përputhen me çelësat e komandës së utilitarit rac dhe emrat e parametrave të daljes së komandave me disa përjashtime — minus shënohet si nënvizim. Për shembull, scheduled-jobs-deny përputhet me fushën ent_scheduled_jobs_deny dhe me variablën scheduled_jobs_deny.

Format e shtimit dhe redaktimit mund të ndryshojnë për nga përbërja e fushave, për shembull, puna me bazën informuese:

Shtimi i IB

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Redaktimi i IB

Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Në procedurën e redaktimit Edit::infobase, në formë shtohen fushat e nevojshme, kodi është i volumshëm prandaj këtu nuk e përmend.

Procedurat e shtimit, redaktimit, fshirjes janë realizuar në mënyrë analogjike edhe për elementet e tjera.

Duke puna e utilitarit përfshin një numër të pakufizuar serverash, klasterash, bazash informacioni etj., për të përcaktuar se cilit klaster i përket cili server ose baza e informacionit, janë futur disa variabla globalë, vlerat e të cilëve caktohen për çdo klikim mbi elementet e pemës. Kjo do të thotë se procedura kalon rekursivisht nëpër të gjithë elementët prind dhe cakton 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
    }
}

Klienti 1C mund të punojnë si me autorizim ashtu edhe pa. Ekzistojnë dy lloje administratorësh — administratori i agjentit të klientit dhe administratori i klientit. Për këtë arsye, janë futur edhe 4 variabla globalë, të cilët përmbajnë emrin dhe fjalëkalimin e administratorit. Pra, nëse në klient ka një llogari administratori, do të shfaqet një dialog për të futur emrin dhe fjalëkalimin, të dhënat do të ruhen në memorie dhe do të përdoren në çdo komandë për klientin e përkatshëm.

Kjo është përgjegjësia e procedurës së trajtimit të gabimeve

ErrorParcing

proc ErrorParcing {err opt} {
    global cluster_user cluster_pwd agent_user agent_pwd
        switch -regexp -- $err {
        "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 agjentit 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 agjentit 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 ndaj, në varësi të asaj që kthen komanda, do të jetë edhe reagimi.

Aktualisht, funksionaliteti është realizuar rreth 95%, mbetet të realizohet puna me profilet e sigurisë dhe ta testojmë =). Kjo është gjithçka. Më vjen keq për rrëfimin e ngjeshur.

Kodi, sipas traditës, është i disponueshëm. këtu.

Përditësim: Perfekte këmba e punës me profilin e sigurisë. Tani funksionaliteti është realizuar në 100%.

Përditësim 2: është shtuar lokalizimi në anglisht dhe rusisht, funksionimi është verifikuar në win7.
Shkruajmë GUI për 1C RAC, ose sërish për Tcl/Tk

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster