Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Dok smo ulazili u temu kako 1C proizvodi rade u Linux okruženju, otkriven je jedan nedostatak - nedostatak prikladnog grafičkog višeplatformskog alata za upravljanje klasterom 1C poslužitelja. I odlučeno je ispraviti ovaj nedostatak pisanjem GUI-ja za uslužni program rac konzole. Tcl/tk je odabran kao razvojni jezik kao, po mom mišljenju, najprikladniji za ovaj zadatak. I tako, želio bih predstaviti neke zanimljive aspekte rješenja u ovom materijalu.

Za rad će vam trebati distribucije tcl/tk i 1C. A budući da sam odlučio maksimalno iskoristiti mogućnosti osnovne tcl/tk isporuke bez korištenja paketa trećih strana, trebat će mi verzija 8.6.7 koja uključuje ttk - paket s dodatnim grafičkim elementima, od kojih nam uglavnom treba ttk ::TreeView, omogućuje prikaz podataka kako u obliku stabla tako iu obliku tablice (liste). Također, u novoj verziji je prerađen rad s iznimkama (naredba try, koja se koristi u projektu prilikom pokretanja vanjskih naredbi).

Projekt se sastoji od nekoliko datoteka (iako vas ništa ne sprječava da sve radite u jednoj):

rac_gui.cfg - zadana konfiguracija
rac_gui.tcl - glavna skripta za pokretanje
Direktorij lib sadrži datoteke koje se automatski učitavaju pri pokretanju:
function.tcl - datoteka s procedurama
gui.tcl - glavno grafičko sučelje
images.tcl - base64 biblioteka slika

Datoteka rac_gui.tcl, zapravo, pokreće interpreter, inicijalizira varijable, učitava module, konfiguracije i tako dalje. Sadržaj datoteke s komentarima:

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

Nakon preuzimanja svega što je potrebno i provjere prisutnosti uslužnog programa rac, pokrenut će se grafički prozor. Sučelje programa sastoji se od tri elementa:

Alatna traka, stablo i popis

Napravio sam sadržaj "stabla" što je moguće sličnije standardnoj Windows opremi iz 1C.

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Glavni kod koji tvori ovaj prozor nalazi se u datoteci
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

Algoritam za rad s programom je sljedeći:

1. Prvo morate dodati glavni poslužitelj klastera (tj. poslužitelj za upravljanje klasterom (u Linuxu se upravljanje pokreće naredbom “/opt/1C/v8.3/x86_64/ras cluster —daemon”)).

Da biste to učinili, kliknite na gumb "+" iu prozoru koji se otvori unesite adresu poslužitelja i port:

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Nakon toga klikanjem na stablo pojavit će se naš poslužitelj, otvorit će se popis klastera ili će se prikazati pogreška povezivanja.

2. Klikom na naziv klastera otvorit će se popis funkcija dostupnih za njega.

3. ...

I tako dalje, tj. za dodavanje novog klastera odaberite bilo koji dostupan na popisu i pritisnite gumb “+” na alatnoj traci i prikazat će se dijaloški okvir za dodavanje novog:

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Gumbi na alatnoj traci obavljaju funkcije ovisno o kontekstu, tj. Ovisno o tome koji je element stabla ili popisa odabran, izvršit će se jedan ili drugi postupak.

Pogledajmo primjer gumba za dodavanje (“+”):

Kod za generiranje gumba:

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

Ovdje vidimo da će se, kada se pritisne gumb, izvršiti procedura “Dodaj”, njen kod:

proc Add {} {
    global active_cluster host
    # Определяем идентификатор выделенного элемента
    set id  [.frm_tree.tree selection] 
    # Определяем значение этого элемента
    set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
    set key [lindex [split $id "::"] 0]
    # в зависимости от того что выделили будет запущена нужная процедура
    if {$key eq "" || $key eq "server"} {
        set host [ Add::server ]
        return
    }
    Add::$key .frm_tree.tree $host $values
}

Evo jedne od prednosti tickle-a: možete proslijediti vrijednost varijable kao naziv procedure:

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

To jest, na primjer, ako pokažemo na glavni poslužitelj i pritisnemo “+”, pokrenut će se procedura Add::server, ako na klaster - Add::cluster i tako dalje (pisat ću o tome gdje je potrebni "ključevi" dolaze malo niže), navedeni postupci crtaju grafičke elemente koji odgovaraju kontekstu.

Kao što ste već mogli primijetiti, obrasci su stilski slični - to ne čudi, jer ih prikazuje jedna procedura, točnije glavni okvir forme (prozor, gumbi, slika, oznaka), naziv procedure 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
    # метка с иконкой
    ttk::label $win_name.lbl -image $img
    # фрейм с полями ввода
    set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
    
    grid columnconfigure $frm 0 -weight 1
    grid rowconfigure $frm 0 -weight 1
    # фрейм и кнопки
    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
}

Parametri poziva: naslov, naziv slike za ikonu iz biblioteke (lib/images.tcl) i izborni parametar naziva prozora (zadano .add). Stoga, ako uzmemo gornje primjere za dodavanje glavnog poslužitelja i klastera, poziv će biti u skladu s tim:

AddToplevel "Добавление основного сервера" server_grey_64

ili

AddToplevel "Добавление кластера" cluster_grey_64

Pa, nastavljajući s ovim primjerima, pokazat ću postupke koji prikazuju dijaloge za dodavanje za poslužitelj ili klaster.

Dodaj::poslužitelj

proc Add::server {} {
    global default
    # выводим основную форму
    set frm [AddToplevel "Добавление основного сервера" server_grey_64]
    # добавляем етки и поля ввода на эту форму
    label $frm.lbl_host -text "Адрес сервера"
    entry  $frm.ent_host
    label $frm.lbl_port -text "Порт"
    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]
    # переопределяем обработчик нажатия кнопки
    .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
}

Dodaj::klaster

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 ""
    }
    # устанавливаем глобальные переменные ()
    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 "Добавление кластера" cluster_grey_64]
    
    label $frm.lbl_host -text "Адрес основного сервера"
    entry  $frm.ent_host
    label $frm.lbl_port -text "Порт"
    entry $frm.ent_port 
    $frm.ent_port  insert end $default(port)
    label $frm.lbl_name -text "Название кластера"
    entry  $frm.ent_name
    label $frm.lbl_secure_connect -text "Защищённое соединение"
    ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
    label $frm.lbl_expiration_timeout -text "Останавливать выключенные процессы через:"
    entry  $frm.ent_expiration_timeout -textvariable expiration_timeout
    label $frm.lbl_session_fault_tolerance_level -text "Уровень отказоустойчивости"
    entry  $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
    label $frm.lbl_load_balancing_mode -text "Режим распределения нагрузки"
    ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode 
    -values $default(load_balancing_mode)
    label $frm.lbl_errors_count_threshold -text "Допустимое отклонение количества ошибок сервера, %"
    entry  $frm.ent_errors_count_threshold -textvariable errors_count_threshold
    label $frm.lbl_processes -text "Рабочие процессы:"
    label $frm.lbl_lifetime_limit -text "Период перезапуска, сек."
    entry  $frm.ent_lifetime_limit -textvariable lifetime_limit
    label $frm.lbl_max_memory_size -text "Допустимый объём памяти, КБ"
    entry  $frm.ent_max_memory_size -textvariable max_memory_size
    label $frm.lbl_max_memory_time_limit -text "Интервал превышения допустимого объёма памяти, сек."
    entry  $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
    label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Принудительно завершать проблемные процессы"
    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
    # переопределяем обработчик
    .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
}

Uspoređujući kod ovih postupaka, razlika je vidljiva golim okom, ja ću se usredotočiti na rukovatelj gumbom "Ok". U Tk, svojstva grafičkih elemenata mogu se nadjačati tijekom izvođenja programa pomoću opcije konfigurirati. Na primjer, početna naredba za prikaz gumba:

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

Ali u našim obrascima naredba ovisi o potrebnoj funkcionalnosti:

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

U gornjem primjeru, gumb "začepljen" pokreće proceduru za dodavanje klastera.

Ovdje vrijedi napraviti digresiju prema radu s grafičkim elementima u Tk - za različite elemente unosa podataka (unos, kombinirani okvir, gumb za potvrdu, itd.) parametar je uveden kao tekstualna varijabla:

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

Ova varijabla definirana je u globalnom imenskom prostoru i sadrži trenutno unesenu vrijednost. Oni. da biste dobili uneseni tekst iz polja potrebno je samo očitati vrijednost koja odgovara varijabli (naravno, pod uvjetom da je definirana prilikom kreiranja elementa).

Drugi način za dohvaćanje unesenog teksta (za elemente tipa unos) je korištenje naredbe get:

.add.frm.ent_name get

Obje ove metode mogu se vidjeti u gornjem kodu.

Klikom na ovaj gumb, u ovom slučaju, pokreće se procedura RunCommand s generiranom naredbenom linijom za dodavanje klastera u smislu rac-a:

/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

Sada dolazimo do glavne naredbe, koja kontrolira pokretanje rac-a s parametrima koji su nam potrebni, također analizira izlaz naredbi u popise i vraća, ako je potrebno:

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
    # открываем канал в неблокирующем режиме
    # $rac - команда с полным путём
    # $par - сформированные ключи запуска и опции    
    set pipe [open "|$rac_cmd $par" "r"]
    try {
        set lst ""
        set l ""
        # вывод команды добавляем в список списков
        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} {
        # Запуск обработчика ошибок
        ErrorParcing $result $options
        return ""
    }
}

Nakon unosa podataka glavnog poslužitelja, oni će biti dodani u stablo, za to je u gornjoj proceduri Add:server odgovoran sljedeći kod:

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

Sada klikom na naziv poslužitelja u stablu dobivamo popis klastera kojima taj poslužitelj upravlja, a klikom na klaster dobivamo popis elemenata klastera (poslužitelja, infobaza itd.). Ovo je implementirano u TreePress proceduri (datoteka lib/function.tcl):

proc TreePress {tree} {
   global host server active_cluster infobase
   # определяем выделенный элемент
    set id  [$tree selection]
   # устанавливаем нужные глобальные переменные
    SetGlobalVarFromTreeItems $tree $id
   # Определяем ключ и значение, т.е. именно тип выбранного элемента
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
   # и в зависимости от того что выбрали будет запущена соответствующая процедура 
   # в пространстве имён Run
    Run::$key $tree $host $values
}

U skladu s tim, Run::server će se pokrenuti za glavni poslužitelj (za klaster - Run::cluster, za radni poslužitelj - Run::work_server, itd.). Oni. vrijednost varijable $key dio je naziva elementa stabla određenog opcijom -iskaznica.

Obratimo pozornost na postupak

Pokreni::server

proc Run::server {tree host values} {
    # получаем список кластеров требуемого сервера
    set lst [RunCommand server::$host "cluster list $host"]
    if {$lst eq ""} {return}
    set l [lindex $lst 0]
    #puts $lst
    # удаляем лишнее из списка
    .frm_work.tree_work delete  [ .frm_work.tree_work children {}]
    # читаем список
    foreach cluster_list $lst {
        # Заполняем список полученными значениями
        InsertItemsWorkList $cluster_list
        # обрабатываем вывод (список) для добавления данных в дерево
        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]]
            }
        }
    }
    # добавляем кластеры в дерево
    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"
            # добавляем элементы в кластер
            InsertClusterItems $tree $id
        }
    }
    if { [$tree exists "agent_admins::$id"] == 0 } {
        $tree insert "server::$host" end -id "agent_admins::$id" -text "Администраторы" -values "$id"
        #InsertClusterItems $tree $id
    }
}

Ova procedura obrađuje ono što je primljeno od poslužitelja putem naredbe RunCommand i dodaje svakakve stvari u stablo - klastere, razne korijenske elemente (baze, radne poslužitelje, sesije i tako dalje). Ako pažljivo pogledate, primijetit ćete poziv procedure InsertItemsWorkList unutra. Koristi se za dodavanje elemenata na grafički popis obradom izlaza uslužnog programa rac konzole, koji je prethodno vraćen kao popis varijabli $lst. Ovo je popis popisa koji sadrže parove elemenata odvojenih dvotočkom.

Na primjer, popis veza klastera:

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

U grafičkom obliku to će izgledati otprilike ovako:

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Gornji postupak odabire nazive elemenata za zaglavlje i podatke za popunjavanje tablice:

InsertItemsWorkList

proc InsertItemsWorkList {lst} {
    global work_list_row_count
    # установка чередования цвета для строки
    if [expr $work_list_row_count % 2] {
        set tag dark
    } else {
        set tag light
    }
    # разбор строк на пары ключ - значение
    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]
        }
    }
     # заполнение таблицы
    .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
    # установка заголовков
    foreach j $column_list {
        .frm_work.tree_work heading $j -text $j
    }
    incr work_list_row_count
}

Ovdje se umjesto jednostavne naredbe [split $str ":"], koja dijeli niz na elemente odvojene znakom ":" i vraća popis, koristi regularni izraz, budući da neki elementi sadrže i dvotočku.

Procedura InsertClusterItems (jedna od nekoliko sličnih) jednostavno dodaje popis podređenih elemenata s odgovarajućim identifikatorima u stablo potrebnog elementa klastera
Umetni stavke klastera

proc InsertClusterItems {tree id} {
    set parent "cluster::$id"
    $tree insert $parent end -id "infobases::$id" -text "Информационные базы" -values "$id"
    $tree insert $parent end -id "servers::$id" -text "Рабочие серверы" -values "$id"
    $tree insert $parent end -id "admins::$id" -text "Администраторы" -values "$id"
    $tree insert $parent end -id "managers::$id" -text "Менеджеры кластера" -values $id
    $tree insert $parent end -id "processes::$id" -text "Рабочие процессы" -values "workprocess-all"
    $tree insert $parent end -id "sessions::$id" -text "Сеансы" -values "sessions-all"
    $tree insert $parent end -id "locks::$id" -text "Блокировки" -values "blocks-all"
    $tree insert $parent end -id "connections::$id" -text "Соединения" -values "connections-all"
    $tree insert $parent end -id "profiles::$id" -text "Профили безопасности" -values $id
}

Možete razmotriti još dvije opcije za implementaciju sličnog postupka, gdje će biti jasno vidljivo kako možete optimizirati i riješiti se ponavljajućih naredbi:

U ovom postupku zbrajanje i provjera rješavaju se izravno:

Umetni osnovne stavke

proc InsertBaseItems {tree id} {
    set parent "infobase::$id"
    if { [$tree exists "sessions::$id"] == 0 } {
        $tree insert $parent end -id "sessions::$id" -text "Сеансы" -values "$id"
    }
    if { [$tree exists "locks::$id"] == 0 } {
        $tree insert $parent end -id "locks::$id" -text "Блокировки" -values "$id"
    }
    if { [$tree exists "connections::$id"] == 0 } {
        $tree insert $parent end -id "connections::$id" -text "Соединения" -values "$id"
    }
}

Evo ispravnijeg pristupa:

Umetni stavke profila

proc InsertProfileItems {tree id} {
    set parent "profile::$id"
    set lst {
        {dir "Виртуальные каталоги"}
        {com "Разрешённые COM-классы"}
        {addin "Внешние компоненты"}
        {module "Внешние отчёты и обработки"}
        {app "Разрешённые приложения"}
        {inet "Ресурсы интернет"}
    }
    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 
    }
}

Razlika između njih je korištenje petlje, u kojoj se ponavljaju naredbe(e). Programer odlučuje koji pristup koristiti.

Pokrili smo dodavanje elemenata i dohvaćanje podataka, a sada je vrijeme da se usredotočimo na uređivanje. Budući da se u osnovi koriste isti parametri za uređivanje i dodavanje (osim informacijske baze), koriste se iste dijaloške forme. Algoritam za pozivanje procedura za dodavanje izgleda ovako:

Add::$key->AddToplevel

A za ovakvo uređivanje:

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

Na primjer, uzmimo uređivanje klastera, tj. Nakon što kliknete na naziv klastera u stablu, pritisnite gumb za uređivanje na alatnoj traci (olovka) i na ekranu će se prikazati odgovarajući obrazac:

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk
Uredi::klaster

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 ""
    }
    # рисуем форму для кластера
    set frm [Add::cluster $tree $host $values]
    # меняем текст на метке
    $frm configure -text "Редактирование кластера"
    
    set active_cluster $values
    # получаем данные по выделенному кластеру
    set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
    # заполняем поля
    FormFieldsDataInsert $frm $lst
    # выключаем поля, редактирование которых запрещено
    $frm.ent_host configure -state disable
    $frm.ent_port configure -state disable
    # переназначаем обработчик
    .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
    }
}

Na temelju komentara u kodu, u načelu je sve jasno, osim što je kod rukovatelja gumbima poništen i postoji procedura FormFieldsDataInsert koja ispunjava polja podacima i inicijalizira varijable:

FormFieldsDataInsert

proc FormFieldsDataInsert {frm lst} {
    foreach i [lindex $lst 0] {
        # получаем список параметров и значений
        if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
            # меняем символы
            regsub -all -- "-" [string trim $param] "_" entry_name
            # заполняем данными
            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 """]
            }
            # для чекбоксов меняем значения
            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
                }
            }
        }
    }
}

U ovoj proceduri isplivala je još jedna prednost tcl-a - vrijednosti drugih varijabli su zamijenjene kao imena varijabli. Oni. za automatizaciju popunjavanja obrazaca i inicijalizacije varijabli, nazivi polja i varijabli odgovaraju prekidačima naredbenog retka uslužnog programa rac i nazivima izlaznih parametara naredbi uz neke iznimke - crtica je zamijenjena podvlakom. Npr zakazani-poslovi-odbiti odgovara polju ent_scheduled_jobs_deny i promjenjiva zakazani_poslovi_odbijanje.

Obrasci za dodavanje i uređivanje mogu se razlikovati u sastavu polja, na primjer, rad s informacijskom bazom:

Dodavanje informacijske sigurnosti

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Uređivanje informacijske sigurnosti

Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

U proceduri uređivanja Edit::infobase u formu se dodaju obavezna polja, kod je voluminozan pa ga ne predstavljam ovdje.

Analogno tome, postupci za dodavanje, uređivanje, brisanje implementirani su za ostale elemente.

Budući da rad uslužnog programa podrazumijeva neograničen broj poslužitelja, klastera, informacijskih baza itd., da bi se utvrdilo koji klaster pripada kojem poslužitelju ili sustavu informacijske sigurnosti, uvedeno je nekoliko globalnih varijabli čije se vrijednosti postavljaju svaki vrijeme kada kliknete na elemente stabla. Oni. procedura rekurzivno prolazi kroz sve nadređene elemente i postavlja varijable:

PostaviGlobalVarFromTreeItems

proc SetGlobalVarFromTreeItems {tree id} {
    global host server active_cluster infobase
    set parent [$tree parent $id]
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
    switch -- $key {
        server {set host $values}
        work_server {set server $values}
        cluster {set active_cluster $values}
        infobase {set infobase $values}
    }
    if {$parent eq ""} {
        return
    } else {
        SetGlobalVarFromTreeItems $tree $parent
    }
}

1C klaster omogućuje vam rad sa ili bez ovlaštenja. Postoje dvije vrste administratora—administrator agenta klastera i administrator klastera. Sukladno tome, za ispravan rad, uvedene su još 4 globalne varijable koje sadrže administratorsku prijavu i lozinku. Oni. ako u klasteru postoji administratorski račun, prikazat će se dijaloški okvir za unos vaše prijave i lozinke, podaci će biti spremljeni u memoriju i umetnuti u svaku naredbu za odgovarajući klaster.

Ovo je odgovornost postupka obrade grešaka.

ErrorParcing

proc ErrorParcing {err opt} {
    global cluster_user cluster_pwd agent_user agent_pwd
        switch -regexp -- $err {
        "Cluster administrator is not authenticated" {
            AuthorisationDialog "Администратор кластера"
            .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 "Администратор агента кластера"
            .auth_win.frm_btn.btn_ok configure -command {
                set agent_user [.auth_win.frm.ent_name get]
                set agent_pwd [.auth_win.frm.ent_pwd get]
                destroy .auth_win
            }
        }
        "Администратор кластера не аутентифицирован" {
            AuthorisationDialog "Администратор кластера"
            .auth_win.frm_btn.btn_ok configure -command {
                set cluster_user [.auth_win.frm.ent_name get]
                set cluster_pwd [.auth_win.frm.ent_pwd get]
                destroy .auth_win
            }
            #RunCommand $root $par
        }
        "Администратор центрального сервера не аутентифицирован" {
            AuthorisationDialog "Администратор агента кластера"
            .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"
        }
    }
}

Oni. ovisno o tome što naredba vraća, reakcija će biti odgovarajuća.

Trenutačno je implementirano oko 95 posto funkcionalnosti, preostaje samo implementirati rad sa sigurnosnim profilima i testirati ga =). To je sve. Ispričavam se na zgužvanoj priči.

Šifra je tradicionalno dostupna ovdje.

Ažuriranje: Završio sam rad sa sigurnosnim profilima. Sada je funkcionalnost 100% implementirana.

Ažuriranje 2: dodana je lokalizacija na engleski i ruski, testiran je rad u win7
Pisanje GUI za 1C RAC, ili opet o Tcl/Tk

Izvor: www.habr.com

Dodajte komentar