1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

1C өнімдерінің Linux ортасында қалай жұмыс істейтіні туралы тақырыпты зерттеген кезде, бір кемшілік анықталды - 1С серверлерінің кластерін басқаруға арналған ыңғайлы графикалық көп платформалы құралдың жоқтығы. Бұл кемшілікті rac console утилитасы үшін GUI жазу арқылы түзету туралы шешім қабылданды. Tcl/tk әзірлеу тілі ретінде, менің ойымша, бұл тапсырма үшін ең қолайлы болып таңдалды. Сонымен, мен осы материалда шешімнің кейбір қызықты аспектілерін ұсынғым келеді.

Жұмыс істеу үшін сізге tcl/tk және 1C дистрибутивтері қажет. Мен үшінші тарап пакеттерін пайдаланбай негізгі tcl/tk жеткізу мүмкіндіктерін барынша пайдалануды шешкендіктен, маған ttk кіретін 8.6.7 нұсқасы қажет болады - қосымша графикалық элементтері бар пакет, оның ішінде бізге негізінен ttk қажет. ::TreeView, ол ағаш құрылымы түрінде де, кесте (тізім) түрінде де деректерді көрсетуге мүмкіндік береді. Сондай-ақ, жаңа нұсқада ерекшеліктермен жұмыс қайта өңделді (сыртқы командаларды орындау кезінде жобада қолданылатын try командасы).

Жоба бірнеше файлдардан тұрады (бірақ бәрін бір файлда жасауға ештеңе кедергі жасамайды):

rac_gui.cfg - әдепкі конфигурация
rac_gui.tcl - негізгі іске қосу сценарийі
lib каталогында іске қосу кезінде автоматты түрде жүктелетін файлдар бар:
function.tcl – процедуралары бар файл
gui.tcl – негізгі графикалық интерфейс
images.tcl - base64 кескіндер кітапханасы

rac_gui.tcl файлы, шын мәнінде, интерпретаторды іске қосады, айнымалыларды инициализациялайды, модульдерді, конфигурацияларды және т.б. жүктейді. Түсініктемелері бар файлдың мазмұны:

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

Қажетті барлық нәрсені жүктеп алып, rac утилитасының бар-жоғын тексергеннен кейін графикалық терезе іске қосылады. Бағдарлама интерфейсі үш элементтен тұрады:

Құралдар тақтасы, ағаш және тізім

Мен «ағаштың» мазмұнын 1С стандартты Windows жабдығына барынша ұқсас етіп жасадым.

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Бұл терезені құрайтын негізгі код файлда бар
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

Бағдарламамен жұмыс істеу алгоритмі келесідей:

1. Алдымен негізгі кластер серверін қосу керек (яғни, кластерді басқару сервері (Linux жүйесінде басқару «/opt/1C/v8.3/x86_64/ras cluster —daemon» пәрменімен іске қосылады)).

Ол үшін «+» түймесін басып, ашылған терезеде сервер мекенжайы мен портты енгізіңіз:

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Содан кейін біздің сервер оны басу арқылы ағашта пайда болады, кластерлер тізімі ашылады немесе қосылым қатесі көрсетіледі.

2. Кластер атауын басу оған қолжетімді функциялар тізімін ашады.

3. ...

Және т.б., яғни. жаңа кластерді қосу үшін тізімде қолжетімді кез келгенін таңдап, құралдар тақтасындағы «+» түймесін басыңыз, сонда жаңа кластерді қосу диалогы көрсетіледі:

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Құралдар тақтасындағы түймелер контекстке байланысты функцияларды орындайды, яғни. Ағаштың немесе тізімнің қандай элементі таңдалғанына байланысты сол немесе басқа процедура орындалады.

Қосу батырмасының мысалын қарастырайық («+»):

Түймелерді құру коды:

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

Мұнда біз түймені басқан кезде «Қосу» процедурасы орындалатынын көреміз, оның коды:

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
}

Міне, қытық артықшылығының бірі: айнымалының мәнін процедура атауы ретінде беруге болады:

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

Яғни, мысалы, негізгі серверді көрсетіп, «+» пернесін бассақ, Add::server процедурасы іске қосылады, егер кластерде - Add::cluster және т.б. қажетті «кілттер» аздап төменде келеді), аталған процедуралар контекстке сәйкес графикалық элементтерді салады.

Сіз байқаған боларсыз, пішіндер стилі бойынша ұқсас - бұл таңқаларлық емес, өйткені олар бір процедурамен, дәлірек пішіннің негізгі жақтауымен (терезе, түймелер, сурет, белгі), процедураның атымен көрсетіледі. 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
}

Қоңырау параметрлері: тақырып, кітапханадағы белгішеге арналған сурет атауы (lib/images.tcl) және қосымша терезе атауы параметрі (әдепкі .add). Осылайша, егер негізгі сервер мен кластерді қосу үшін жоғарыда келтірілген мысалдарды алсақ, қоңырау сәйкес болады:

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

немесе

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

Осы мысалдарды жалғастыра отырып, мен сервер немесе кластер үшін қосу диалогтарын көрсететін процедураларды көрсетемін.

::серверді қосыңыз

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
}

Қосу::кластер

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
}

Бұл процедуралардың кодын салыстыру кезінде айырмашылық жалаңаш көзге көрінеді, мен «Ok» түймешігінің өңдеушісіне назар аударамын. Tk тілінде графикалық элементтердің қасиеттерін бағдарламаны орындау кезінде опцияны пайдаланып қайта анықтауға болады теңшейді. Мысалы, түймені көрсетуге арналған бастапқы пәрмен:

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

Бірақ біздің пішіндерімізде пәрмен қажетті функционалдылыққа байланысты:

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

Жоғарыдағы мысалда «бітелген» түймешігі кластерді қосу процедурасын бастайды.

Мұнда Tk-дағы графикалық элементтермен жұмыс істеуге қысқаша тоқталған жөн - әртүрлі деректерді енгізу элементтері үшін (енгізу, құрама ұяшық, тексеру түймесі және т.б.) мәтіндік айнымалы ретінде параметр енгізілген:

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

Бұл айнымалы жаһандық аттар кеңістігінде анықталған және ағымдағы енгізілген мәнді қамтиды. Анау. өрістен енгізілген мәтінді алу үшін айнымалыға сәйкес мәнді оқу керек (әрине, ол элементті құру кезінде анықталған жағдайда).

Енгізілген мәтінді шығарып алудың екінші әдісі (жазба түрінің элементтері үшін) алу пәрменін пайдалану болып табылады:

.add.frm.ent_name get

Бұл әдістердің екеуін де жоғарыдағы кодта көруге болады.

Бұл түймені басу, бұл жағдайда rac тұрғысынан кластерді қосу үшін жасалған пәрмен жолы бар RunCommand процедурасын іске қосады:

/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

Енді біз қажетті параметрлермен rac іске қосуды басқаратын негізгі пәрменге келдік, сонымен қатар командалардың шығысын тізімдерге талдайды және қажет болған жағдайда қайтарады:

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

Негізгі сервер деректерін енгізгеннен кейін ол ағашқа қосылады, ол үшін жоғарыда көрсетілген Add:server процедурасында келесі код жауап береді:

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

Енді ағаштағы сервер атауын басу арқылы біз сол сервер басқаратын кластерлер тізімін аламыз, ал кластерді басу арқылы кластер элементтерінің (серверлер, инфобазалар және т.б.) тізімін аламыз. Бұл TreePress процедурасында орындалады (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
}

Сәйкесінше, Run::server негізгі сервер үшін іске қосылады (кластер үшін - Run::cluster, жұмыс істейтін сервер үшін - Run::work_server және т.б.). Анау. $key айнымалысының мәні опциямен көрсетілген ағаш элементі атауының бөлігі болып табылады -ид.

Процедураға назар аударайық

::серверді іске қосыңыз

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

Бұл процедура RunCommand пәрмені арқылы серверден алынған нәрсені өңдейді және ағашқа заттардың барлық түрлерін қосады - кластерлер, әртүрлі түбірлік элементтер (негіздер, жұмыс серверлері, сеанстар және т.б.). Мұқият қарасаңыз, ішіндегі InsertItemsWorkList процедурасына шақыруды байқайсыз. Ол бұрын $lst айнымалысына тізім ретінде қайтарылған rac console утилитасының шығысын өңдеу арқылы графикалық тізімге элементтерді қосу үшін пайдаланылады. Бұл қос нүктемен бөлінген жұп элементтерді қамтитын тізімдер тізімі.

Мысалы, кластерлік қосылымдардың тізімі:

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

Графикалық түрде ол келесідей болады:

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Жоғарыдағы процедура кестені толтыру үшін тақырып пен деректерге арналған элементтердің атауларын таңдайды:

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
}

Мұнда жолды «:» арқылы бөлінген элементтерге бөлетін және тізімді қайтаратын қарапайым [split $str «:»] пәрменінің орнына тұрақты өрнек пайдаланылады, өйткені кейбір элементтерде қос нүкте де болады.

InsertClusterItems процедурасы (бірнеше ұқсастардың бірі) қажетті кластер элементінің ағашына сәйкес идентификаторлары бар еншілес элементтердің тізімін қосады.
InsertClusterItems

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
}

Ұқсас процедураны жүзеге асырудың тағы екі нұсқасын қарастыруға болады, онда қайталанатын пәрмендерді қалай оңтайландыруға және құтылуға болатыны анық көрінеді:

Бұл процедурада қосу және тексеру бетпе-бет шешіледі:

InsertBaseItems

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

Міне, дұрысырақ тәсіл:

InsertProfileItems

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

Олардың арасындағы айырмашылық циклды пайдалану болып табылады, онда қайталанатын команда(лар) орындалады. Қай тәсілді қолдану әзірлеушінің өз еркінде.

Біз элементтерді қосу және деректерді алуды қарастырдық, енді өңдеуге назар аударатын кез келді. Өңдеу және қосу үшін негізінен бірдей параметрлер қолданылатындықтан (ақпараттық базаны қоспағанда) бірдей диалогтық пішіндер қолданылады. Қосуға арналған процедураларды шақыру алгоритмі келесідей:

Add::$key->AddToplevel

Және келесідей өңдеу үшін:

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

Мысалы, кластерді өңдеуді алайық, яғни. Ағаштағы кластердің атауын басқаннан кейін құралдар тақтасындағы (қарындаш) өңдеу түймесін басыңыз, экранда сәйкес пішін пайда болады:

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы
Өңдеу::кластер

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

Кодтағы түсініктемелерге сүйене отырып, негізінен, бәрі түсінікті, тек түймелерді өңдеуші коды қайта белгіленеді және өрістерді деректермен толтыратын және айнымалыларды инициализациялайтын FormFieldsDataInsert процедурасы бар:

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

Бұл процедурада tcl тағы бір артықшылығы пайда болды - басқа айнымалылардың мәндері айнымалы атаулар ретінде ауыстырылады. Анау. пішіндерді толтыруды және айнымалыларды инициализациялауды автоматтандыру үшін өрістер мен айнымалылардың атаулары rac утилитасының пәрмен жолы қосқыштарына және кейбір ерекшеліктерді қоспағанда, команданың шығыс параметрлерінің атауларына сәйкес келеді - сызықша астын сызумен ауыстырылады. Мысалыға жоспарланған-жұмыстарды-басқа өріске сәйкес келеді Жоспарланған_жұмыстарды_қабылдамау және айнымалы жоспарланған_жұмыстарды_қабылдамау.

Қосу және өңдеу үшін пішіндер өрістердің құрамында әр түрлі болуы мүмкін, мысалы, ақпараттық базамен жұмыс істеу:

Ақпараттық қауіпсіздікті қосу

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Ақпараттық қауіпсіздікті өңдеу

1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Edit::infobase өңдеу процедурасында пішінге қажетті өрістер қосылады; код көлемді, сондықтан мен оны мұнда ұсынбаймын.

Аналогия бойынша басқа элементтер үшін қосу, өңдеу, жою процедуралары жүзеге асырылады.

Утилитаның жұмысы серверлердің, кластерлердің, ақпараттық базалардың және т.б. шектеусіз санын білдіретіндіктен, қай кластер қай серверге немесе ақпараттық қауіпсіздік жүйесіне жататынын анықтау үшін әрқайсысында мәндері орнатылатын бірнеше жаһандық айнымалылар енгізілді. ағаш элементтерін басқан кезде. Анау. процедура барлық негізгі элементтер арқылы рекурсивті түрде орындалады және айнымалы мәндерді орнатады:

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

1С кластері авторизациямен немесе рұқсатсыз жұмыс істеуге мүмкіндік береді. Әкімшілердің екі түрі бар — кластер агентінің әкімшісі және кластер әкімшісі. Тиісінше, дұрыс жұмыс істеу үшін әкімшінің логині мен құпия сөзін қамтитын тағы 4 жаһандық айнымалы енгізілді. Анау. кластерде әкімші тіркелгісі болса, логин мен құпия сөзді енгізу үшін диалогтық терезе көрсетіледі, деректер жадта сақталады және сәйкес кластерге арналған әрбір пәрменге енгізіледі.

Бұл қателерді өңдеу процедурасының жауапкершілігі.

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

Анау. пәрмен қайтаратынына байланысты реакция сәйкес болады.

Қазіргі уақытта функционалдылықтың шамамен 95 пайызы орындалды, қалғаны қауіпсіздік профильдерімен жұмысты жүзеге асыру және оны сынау =). Бар болғаны. Тартылған әңгіме үшін кешірім сұраймын.

Код дәстүрлі түрде қол жетімді осында.

Жаңарту: қауіпсіздік профильдерімен жұмысты аяқтадым. Қазір бұл функция 100% орындалды.

2-жаңарту: ағылшын және орыс тілдеріндегі локализация қосылды, win7-де жұмыс тексерілді
1C RAC үшін GUI жазу немесе қайтадан Tcl/Tk туралы

Ақпарат көзі: www.habr.com

пікір қалдыру