GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

Ahogy belemerültem a 1C termékek linuxos működésébe, egy hiányosságot észleltem — a 1C szerverek klaszterének kezelésére szolgáló kényelmes, grafikus, multiplatform eszköz hiányát. E hiányosság orvoslására úgy döntöttem, hogy GUI-t írok a rac konzolos segédprogramhoz. A fejlesztéshez a tcl/tk nyelvet választottam, mivel szerintem ez a legmegfelelőbb erre a feladatra. Íme, néhány érdekes szempontot szeretnék bemutatni ebben az anyagban.

A munkához szükség van a tcl/tk és a 1C disztribúciókra. Mivel azt terveztem, hogy maximálisan kihasználom a tcl/tk alapcsomagjának lehetőségeit külső csomagok használata nélkül, ezért a 8.6.7 verzióra lesz szükség, amely tartalmazza a ttk-t — egy csomag, amely további grafikus elemeket kínál, amelyek közül számunkra elsősorban a ttk::TreeView szükséges, amely lehetővé teszi az adatok megjelenítését fa struktúrában vagy táblázatos (listás) formában. Ezenkívül az új verzióban átalakításra került a kivételekkel való munka (a try parancs, amely a projektben külső parancsok futtatásakor használatos).

A projekt több fájlból áll (bár semmi nem akadályoz meg abban, hogy mindent egy fájlba tegyünk):

rac_gui.cfg — alapértelmezett konfiguráció
rac_gui.tcl — fő indító script
A lib mappában találhatók azok a fájlok, amelyek automatikusan betöltődnek indításkor:
function.tcl — a programok eljárásai
gui.tcl — fő grafikus felület
images.tcl — képkönyvtár base64 formátumban

A rac_gui.tcl fájl indítja el az interpretert, inicializálja a változókat, betölti a modulokat, konfigurációkat és így tovább. A fájl tartalma megjegyzésekkel:

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"
    }    
}

Miután minden szükséges dolgot betöltöttünk és ellenőriztük a rac segédprogram létezését, megnyílik a grafikus ablak. A program felülete három elemből áll:

Eszköztár, fa nézet és lista

A 'fa' tartalmát maximálisan hasonlóvá tettem az 1C beépített Windows-oszközkészletéhez.

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

A program ezen ablakának fő kódja a következő fájlban található:
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

A program használatának algoritmusa a következő:

1. Először is, hozzá kell adni a klaszter fő szerverét (azaz a klaszter vezérlő szerverét (Linux alatt a vezérlés indítása a ' /opt/1C/v8.3/x86_64/ras cluster —daemon' parancs segítségével történik)).

Ehhez kattintson a '+' gombra, majd a megnyíló ablakban adja meg a szerver címét és a portot:

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

Ezután a fában megjelenik a szerver, amelyre kattintva a klaszterek listája nyílik meg, vagy kapcsolat hibaüzenet jelenik meg.

2. A klaszterre kattintva megnyílik a hozzá tartozó funkciók listája.

3.…

Tehát, ha új klasztert szeretnénk hozzáadni, válasszunk ki bármelyik elérhető elemet a listában, majd kattintsunk a "+" gombra az eszköztáron, és megjelenik az új klaszter hozzáadásának párbeszédpanele:

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

Az eszköztáron található gombok a kontextustól függően hajtanak végre funkciókat, tehát attól függően, hogy melyik fa- vagy listaelemet választottuk ki, az adott eljárás fog végrehajtódni.

Vegyük például a hozzáadás gombot ("+"):

A gombkód:

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

Itt látható, hogy a gomb megnyomásakor végrehajtódik az "Add" eljárás, amelynek kódja:

proc Add {} {
    global active_cluster host
    # Meghatározzuk a kiválasztott elem azonosítóját
    set id  [.frm_tree.tree selection] 
    # Meghatározzuk ennek az elemnek az értékét
    set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
    set key [lindex [split $id "::"] 0]
    # Attól függően, hogy mit választottunk, a megfelelő eljárás fog elindulni
    if {$key eq "" || $key eq "server"} {
        set host [ Add::server ]
        return
    }
    Add::$key .frm_tree.tree $host $values
}

Itt máris látható az egyik előnye a ticknek — a procedúra nevébe átadhatjuk a változó értékét:

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

Tehát például, ha a fő szerverre kattintunk és rákattintunk a "+" gombra, akkor az Add::server eljárás indul el, ha a klaszterre kattintunk — Add::cluster és így tovább (arról, hogy honnan származnak a szükséges "kulcsok", kicsit később írok), a felsorolt eljárások a kontextusnak megfelelő grafikai elemeket rajzolják ki.

Ahogy már észlelhették, az űrlapok stílusa hasonló — nem meglepő, hiszen egy eljárás generálja őket, pontosabban az űrlap alapvázát (ablak, gombok, kép, címke), a procedúra neve: 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
    # címke ikonképpel
    ttk::label $win_name.lbl -image $img
    # keret a beviteli mezőkkel
    set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
    
    grid columnconfigure $frm 0 -weight 1
    grid rowconfigure $frm 0 -weight 1
    # keret és gombok
    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
}

A hívási paraméterek: cím, ikon kép neve a könyvtárból (lib/images.tcl) és egy opcionális ablaknév paraméter (alapértelmezés szerint .add). Így, ha a fenti példákat vesszük a fő kiszolgáló és a klaszter hozzáadásához, akkor a hívás ennek megfelelően lesz:

AddToplevel "A fő kiszolgáló hozzáadása" server_grey_64

vagy

AddToplevel "Klaszter hozzáadása" cluster_grey_64

Azzal a példával folytatva bemutatom azokat a procedúrákat, amelyek a kiszolgáló vagy klaszter hozzáadási párbeszédablakait jelenítik meg.

Add::server

proc Add::server {} {
    global default
    # megjelenítjük a fő űrlapot
    set frm [AddToplevel "A fő kiszolgáló hozzáadása" server_grey_64]
    # hozzájárulások és beviteli mezők hozzáadása ehhez az űrlaphoz
    label $frm.lbl_host -text "A kiszolgáló címe"
    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]
    # átkonfiguráljuk a gombnyomás kezelőt
    .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 ""
    }
    # beállítjuk a globális változókat ()
    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 "Kluster hozzáadása" cluster_grey_64]
    
    label $frm.lbl_host -text "Fő szerver címe"
    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 neve"
    entry  $frm.ent_name
    label $frm.lbl_secure_connect -text "Biztonságos kapcsolat"
    ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
    label $frm.lbl_expiration_timeout -text "Kikapcsolt folyamatok leállítása után:"
    entry  $frm.ent_expiration_timeout -textvariable expiration_timeout
    label $frm.lbl_session_fault_tolerance_level -text "Hibatűrés"
    entry  $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
    label $frm.lbl_load_balancing_mode -text "Terheléselosztási mód"
    ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode 
    -values $default(load_balancing_mode)
    label $frm.lbl_errors_count_threshold -text "Megengedett hibaszám eltérés, %"
    entry  $frm.ent_errors_count_threshold -textvariable errors_count_threshold
    label $frm.lbl_processes -text "Munka folyamatok:"
    label $frm.lbl_lifetime_limit -text "Újraindítási időszak, mp."
    entry  $frm.ent_lifetime_limit -textvariable lifetime_limit
    label $frm.lbl_max_memory_size -text "Megengedett memória mennyisége, KB"
    entry  $frm.ent_max_memory_size -textvariable max_memory_size
    label $frm.lbl_max_memory_time_limit -text "Maximális memória túllépés időtartama, mp."
    entry  $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
    label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Problémás folyamatok erőltetett leállítása"
    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
    # átírjuk a kezelőt
    .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
}

A kódok összehasonlításakor ezeknek az eljárásoknak a között szabad szemmel is látható a különbség, itt a ‘Ok’ gomb kezelőjére szeretnék figyelmet irányítani. A Tk-ban a grafikus elemek tulajdonságait a program futási ideje alatt újra lehet definiálni egy opció segítségével. configurePéldául, a gomb kiírásának eredeti parancsa:

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

De a mi formáinkban a parancs az elvárt funkcionális igényektől függ:

  .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
    }

Az előző példában a ‘gomb’ elindítja a klaszter hozzáadó eljárást.

Itt érdemes egy kis kitérőt tenni a Tk grafikus elemeivel való munkával kapcsolatban – különböző adatbeviteli elemek (entry, combobox, checkbutton, stb.) esetében bevezetésre került egy olyan paraméter, mint a szöveges változó (textvariable):

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

Ez a változó a globális névtérben van definiálva, és tartalmazza az aktuálisan beírt értéket. Vagyis a beírt szöveg megszerzéséhez egyszerűen letartóztatni kell a megfelelő változó értékét (természetesen feltéve, hogy az létre lett hozva az elem létrehozásakor).

A második módszer a beírt szöveg (entry típusú elemeknél) megszerzésére a get parancs használata:

.add.frm.ent_name get

Mindkét módszert láthatjuk a fentebb említett kódban.

Ez a gomb, jelen esetben, elindítja a RunCommand eljárást a klaszter hozzáadásához szükséges parancssorral a rac terminológiában:

/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

Most pedig elérkeztünk a fő parancshoz, amely a rac megfelelő paraméterekkel való elindítását irányítja, valamint a parancsok kimenetét listákra bontja, és visszaadja, ha szükséges:

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
    # csatorna megnyitása nem blokkoló módban
    # $rac - teljes útvonalú parancs
    # $par - generált indító kulcsok és opciók    
    set pipe [open "|$rac_cmd $par" "r"]
    try {
        set lst ""
        set l ""
        # a parancs kimenetét egy listába adjuk hozzá
        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} {
        # Hibakezelő eljárás indítása
        ErrorParcing $result $options
        return ""
    }
}

A fő szerver adatainak megadása után az hozzáadódik a fába, amelyért a fent említett Add:server eljárás következő kódja felelős:

.frm_tree.tree insert {} end -id "server::$host" -text "$host" -values "$host"

Most, ha rákattintunk a fa szerver nevére, megkapjuk azokat a klasztereket, amelyek az adott szerver által kezelt nagy szerverek, míg a klaszterre kattintva megkapjuk a klaszter elemeinek listáját (szerverek, információs bázisok stb.). Ez a TreePress eljárásban van megvalósítva (fájl lib/function.tcl):

proc TreePress {tree} {
   global host server active_cluster infobase
   # a kijelölt elem meghatározása
    set id  [$tree selection]
   # a szükséges globális változók beállítása
    SetGlobalVarFromTreeItems $tree $id
   # A kulcs és az érték, azaz a kiválasztott elem típusa
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
   # és attól függően, hogy mit választottunk, a megfelelő eljárás indul el
   # a Run névtartományban
    Run::$key $tree $host $values
}

Ezért a fő szerver esetén a Run::server fog elindulni (a klaszterről — Run::cluster, a munkaszerverről — Run::work_server stb.). Tehát a $key változó értéke a fa elem nevének egy része, amelyet az opció határoz meg -id.

Irányítsuk figyelmünket az eljárásra

Run::server

proc Run::server {tree host values} {
    # a szerverhez szükséges klaszterek listájának lekérése
    set lst [RunCommand server::$host "cluster list $host"]
    if {$lst eq ""} {return}
    set l [lindex $lst 0]
    #puts $lst
    # felesleges elemek eltávolítása a listából
    .frm_work.tree_work delete  [ .frm_work.tree_work children {}]
    # a lista beolvasása
    foreach cluster_list $lst {
        # Az értékek kitöltése a listába
        InsertItemsWorkList $cluster_list
        # a kimenet (lista) feldolgozása az adatok fának való hozzáadásához
        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]]
            }
        }
    }
    # a klaszterek hozzáadása a fához
    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"
            # elemek hozzáadása a klaszterhez
            InsertClusterItems $tree $id
        }
    }
    if { [$tree exists "agent_admins::$id"] == 0 } {
        $tree insert "server::$host" end -id "agent_admins::$id" -text "Adminisztrátorok" -values "$id"
        #InsertClusterItems $tree $id
    }
}

Ez a folyamat feldolgozza, amit a szervertől kaptunk a RunCommand parancs útján, és különféle elemeket ad hozzá a fához — klaszterek, különböző gyökérelemek (adatbázisok, munkaszerverek, munkamenetek stb.). Ha alaposan megnézzük, észrevehetjük az InsertItemsWorkList eljárás hívását. Ez az eljárás a grafikus listába való elemek hozzáadására szolgál, feldolgozva a rac konzolsegéd kimenetét, amely korábban listaként került tárolásra a $lst változóban. Ez egy listákból álló lista, amely elempárokat tartalmaz, kettősponttal elválasztva.

Például a klaszter kapcsolatok listája:

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

Grafikus formában így fog kinézni:

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

A fent említett eljárás kiemeli az elemek nevének címét és az adatokhoz szükséges információkat a táblázat kitöltéséhez:

InsertItemsWorkList

proc InsertItemsWorkList {lst} {
    global work_list_row_count
    # színek váltakozásának beállítása a sorhoz
    if [expr $work_list_row_count % 2] {
        set tag dark
    } else {
        set tag light
    }
    # sorok kulcs - érték párokra bontása
    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]
        }
    }
     # táblázat kitöltése
    .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
    # fejléc beállítása
    foreach j $column_list {
        .frm_work.tree_work heading $j -text $j
    }
    incr work_list_row_count
}

Itt a sima parancs [split $str «:»] helyett, amely a kettőspont által elválasztott elemekre bontja a sztringet és visszaad egy listát, reguláris kifejezést használtunk, mivel néhány elem szintén kettőspontot tartalmaz.

Az InsertClusterItems eljárás (az ilyen eljárások egyike) egyszerűen hozzáadja a fát a megfelelő cluster elemeihez egy lista gyermek elemmel a megfelelő azonosítókkal.
InsertClusterItems

proc InsertClusterItems {tree id} {
    set parent "cluster::$id"
    $tree insert $parent end -id "infobases::$id" -text "Információs alapok" -values "$id"
    $tree insert $parent end -id "servers::$id" -text "Munkaszerverek" -values "$id"
    $tree insert $parent end -id "admins::$id" -text "Adminisztrátorok" -values "$id"
    $tree insert $parent end -id "managers::$id" -text "Cluster menedzserek" -values $id
    $tree insert $parent end -id "processes::$id" -text "Munkafolyamatok" -values "workprocess-all"
    $tree insert $parent end -id "sessions::$id" -text "Munkamenetek" -values "sessions-all"
    $tree insert $parent end -id "locks::$id" -text "Zárolások" -values "blocks-all"
    $tree insert $parent end -id "connections::$id" -text "Kapcsolatok" -values "connections-all"
    $tree insert $parent end -id "profiles::$id" -text "Biztonsági profilok" -values $id
}

Két további megvalósítási változat is bemutatható, ahol világosan látható, hogyan lehet optimalizálni és megszabadulni a megismétlődő parancsoktól:

Ebben az eljárásban a hozzáadás és a ellenőrzés nyíltan van megoldva:

InsertBaseItems

proc InsertBaseItems {tree id} {
    set parent "infobase::$id"
    if { [$tree exists "sessions::$id"] == 0 } {
        $tree insert $parent end -id "sessions::$id" -text "Munkamenetek" -values "$id"
    }
    if { [$tree exists "locks::$id"] == 0 } {
        $tree insert $parent end -id "locks::$id" -text "Zárolások" -values "$id"
    }
    if { [$tree exists "connections::$id"] == 0 } {
        $tree insert $parent end -id "connections::$id" -text "Kapcsolatok" -values "$id"
    }
}

Itt a megközelítés helyesebb:

InsertProfileItems

proc InsertProfileItems {tree id} {
    set parent "profile::$id"
    set lst {
        {dir "Virtuális mappák"}
        {com "Engedélyezett COM-osztályok"}
        {addin "Külső komponensek"}
        {module "Külső jelentések és feldolgozások"}
        {app "Engedélyezett alkalmazások"}
        {inet "Internet erőforrások"}
    }
    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 
    }
}

A ciklus alkalmazásában rejlik a különbség, amelyben a visszatérő parancsok (parancs) végrehajtását biztosítja. Melyik megközelítést alkalmazzuk, az már a fejlesztő döntése.

Az elemek hozzáadása és az adatok megszerzése után itt az ideje a szerkesztésre összpontosítani. Mivel a szerkesztéshez és a hozzáadáshoz alapvetően ugyanazokat a paramétereket használjuk (kivételt képez az információs adatbázis), így a párbeszédformák is azonosak. A hozzáadási eljárások hívásának algoritmusa a következő:

Add::$key->AddToplevel

Szerkesztéshez pedig így néz ki:

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

Példaként vegyük a klaszter szerkesztését, azaz a fában a klaszter nevére kattintva megnyomjuk a szerkesztés gombot az eszköztáron (ceruza ikon), és a megfelelő űrlap jelenik meg a képernyőn:

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról
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 ""
    }
    # Klauster formájának megrajzolása
    set frm [Add::cluster $tree $host $values]
    # Szöveg megváltoztatása a cimkén
    $frm configure -text "Klauster szerkesztése"
    
    set active_cluster $values
    # Adatok lekérése a kiválasztott klastról
    set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
    # Mezők kitöltése
    FormFieldsDataInsert $frm $lst
    # A nem szerkeszthető mezők letiltása
    $frm.ent_host configure -state disable
    $frm.ent_port configure -state disable
    # Kezelő újracsoportosítása
    .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
    }
}

A kódban található megjegyzések alapján lényegében minden világos, kivéve, hogy a gombkezelő kódja át lett definiálva, és van egy FormFieldsDataInsert eljárás, amely feltölti a mezőket adatokkal és inicializálja a változókat:

FormFieldsDataInsert

proc FormFieldsDataInsert {frm lst} {
    foreach i [lindex $lst 0] {
        # Paraméterek és értékek listájának lekérése
        if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
            # Karakterek megváltoztatása
            regsub -all -- "-" [string trim $param] "_" entry_name
            # Adatok feltöltése
            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 """]
            }
            # A jelölőnégyzetekhez az értékek megváltoztatása
            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
                }
            }
        }
    }
}

Ezekben a folyamatokban felmerült egy másik előny, hogy a tcl esetében a változók neve helyett más változók értékei kerülnek behelyettesítésre. Azaz a űrlapok automatikus kitöltésére és a változók inicializálására vonatkozóan a mezők és változók elnevezései megfelelnek a rac parancssori eszköz kulcsainak és a parancsok kimeneti paramétereinek neveinek, néhány kivétellel — a kötőjelet aláhúzásra cserélve. Például scheduled-jobs-deny megfelel a mezőnek ent_scheduled_jobs_deny és a változónak scheduled_jobs_deny.

A hozzáadási és szerkesztési űrlapok mezőinek összetétele eltérő lehet, például az információs bázissal való munka során:

IB hozzáadása

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

IB szerkesztése

GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

A Edit::infobase szerkesztési eljárás során a szükséges mezők hozzáadódnak az űrlaphoz, mivel a kód kiterjedt, ezt itt nem hozom.

Hasonlóképpen alakították ki a többi elem hozzáadását, szerkesztését és törlését végző eljárásokat is.

Mivel az eszköz működése korlátlan számú szervert, klasztert, információs bázist stb. feltételez, ezért néhány globális változót vezettek be, melyek értékeit minden egyes faelemre való kattintáskor beállítják. Azaz a folyamat rekurzívan átfut az összes szülőelemhez, és beállítja a változókat:

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
    }
}

A 1C klaszter lehetővé teszi a munkát hitelesítéssel és anélkül. Kétféle adminisztrátor létezik — a klaszter ügynök adminisztrátora és a klaszter adminisztrátora. Ennek megfelelően a helyes működés érdekében további 4 globális változó került bevezetésre, amelyek az adminisztrátor felhasználónevének és jelszavának tárolására szolgálnak. Azaz, ha a klaszterben található egy adminisztrátor felhasználói fiók, megjelenik egy párbeszédablak a felhasználónév és jelszó megadására, az adatok a memóriába kerülnek, és minden parancsban behelyettesítik az adott klaszterre.

Ezt a hibakezelő eljárás kezeli

ErrorParcing

proc ErrorParcing {err opt} {
    global cluster_user cluster_pwd agent_user agent_pwd
        switch -regexp -- $err {
        "A klaszter adminisztrátor nem hitelesített" {
            AuthorisationDialog "Klaszter adminisztrátor"
            .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
        }
        "A központi szerver adminisztrátor nem hitelesített" {
            AuthorisationDialog "A klaszter ügynök adminisztrátor"
            .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
            }
        }
        "A klaszter adminisztrátor nem hitelesített" {
            AuthorisationDialog "Klaszter adminisztrátor"
            .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
        }
        "A központi szerver adminisztrátor nem hitelesített" {
            AuthorisationDialog "A klaszter ügynök adminisztrátor"
            .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"
        }
    }
}

Tehát a visszaadott parancs függvényében lesz a reakció is.

Jelenleg a funkciók 95%-ban megvalósultak, már csak a biztonsági profilok kezelését kell befejezni, na meg tesztelni =). Ennyi lenne. Elnézést a tömör történetért.

A kód, a hagyományokhoz híven, elérhető itt.

Frissítés: Befejeztem a biztonsági profilokkal való munkát. Most a funkciók 100%-ban megvalósultak.

Frissítés 2: Angol és orosz lokalizálás hozzáadva, teszteltem Win7 alatt.
GUI-t írunk 1C RAC-hoz, azaz újra Tcl/Tk-ról

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster