1C RAC용 GUI 작성 또는 Tcl/Tk에 대한 다시 작성

Linux 환경에서 1C 제품이 작동하는 방식에 대한 주제를 조사하면서 한 가지 단점이 발견되었습니다. 1C 서버 클러스터를 관리하기 위한 편리한 그래픽 다중 플랫폼 도구가 없다는 것입니다. 그리고 rac 콘솔 유틸리티용 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 유틸리티가 있는지 확인한 후 그래픽 창이 시작됩니다. 프로그램 인터페이스는 세 가지 요소로 구성됩니다.

도구 모음, 트리 및 목록

1C의 표준 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

여기서는 버튼을 누르면 "Add" 프로시저가 실행되는 것을 볼 수 있습니다. 해당 코드는 다음과 같습니다.

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
}

Tickle의 장점 중 하나는 다음과 같습니다. 변수 값을 프로시저 이름으로 전달할 수 있습니다.

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

예를 들어, 메인 서버를 가리키고 "+"를 누르면 Add::server 프로시저가 실행되고, 클러스터에서는 Add::cluster 등이 실행됩니다(어디에 대해 쓰겠습니다). 필요한 "키"는 아래에서 나옵니다) 나열된 절차는 상황에 적합한 그래픽 요소를 그립니다.

이미 알 수 있듯이 양식은 스타일이 비슷합니다. 이는 하나의 프로시저, 더 정확하게는 양식의 기본 프레임(창, 버튼, 이미지, 레이블), 프로시저 이름으로 표시되기 때문에 놀라운 일이 아닙니다. 상단레벨 추가

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
    }

위의 예에서 "clogged" 버튼을 누르면 클러스터 추가 절차가 시작됩니다.

여기에서 Tk의 그래픽 요소 작업에 대해 여담을 만들 가치가 있습니다. 다양한 데이터 입력 요소(항목, 콤보 상자, 체크 버튼 등)에 대해 매개 변수가 텍스트 변수로 도입되었습니다.

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

이 변수는 전역 네임스페이스에 정의되며 현재 입력된 값을 포함합니다. 저것들. 필드에서 입력된 텍스트를 가져오려면 변수에 해당하는 값을 읽기만 하면 됩니다(물론 요소를 생성할 때 정의된 경우).

입력된 텍스트(항목 유형 요소의 경우)를 검색하는 두 번째 방법은 get 명령을 사용하는 것입니다.

.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의 시작을 제어하고 명령의 출력을 목록으로 구문 분석하고 필요한 경우 반환하는 기본 명령에 도달했습니다.

실행명령

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 콘솔 유틸리티의 출력을 처리하여 그래픽 목록에 요소를 추가하는 데 사용됩니다. 콜론으로 구분된 요소 쌍을 포함하는 목록의 목록입니다.

예를 들어 클러스터 연결 목록은 다음과 같습니다.

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에 대한 다시 작성

위 절차에서는 테이블을 채울 헤더 및 데이터의 요소 이름을 선택합니다.

삽입항목작업목록

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 프로시저(여러 유사한 프로시저 중 하나)는 단순히 해당 식별자가 있는 하위 요소 목록을 필요한 클러스터 요소의 트리에 추가합니다.
삽입클러스터 항목

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

보다 정확한 접근 방식은 다음과 같습니다.

프로필 항목 삽입

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

차이점은 반복되는 명령이 실행되는 루프를 사용한다는 것입니다. 어떤 접근 방식을 사용할지는 개발자의 재량에 달려 있습니다.

지금까지 요소 추가 및 데이터 검색에 대해 다루었습니다. 이제 편집에 집중할 차례입니다. 기본적으로 편집 및 추가(정보 베이스 제외)에는 동일한 매개변수가 사용되므로 동일한 대화 상자 양식이 사용됩니다. 추가 절차를 호출하는 알고리즘은 다음과 같습니다.

추가::$key->AddToplevel

그리고 다음과 같이 편집하려면:

편집::$key->추가::$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 프로시저가 있다는 점을 제외하면 모든 것이 명확합니다.

양식필드데이터삽입

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 유틸리티의 명령줄 스위치 및 명령 출력 매개변수의 이름에 해당합니다. 대시는 밑줄로 대체됩니다. 예: 예약된 작업 거부 필드와 일치 ent_scheduled_jobs_deny 그리고 변수 Scheduled_작업_거부.

추가 및 편집 양식은 정보 기반 작업과 같이 필드 구성에 따라 다를 수 있습니다.

정보 보안을 더하다

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

1C 클러스터를 사용하면 인증 여부에 관계없이 작업할 수 있습니다. 관리자에는 클러스터 에이전트 관리자와 클러스터 관리자의 두 가지 유형이 있습니다. 따라서 올바른 작동을 위해 관리자 로그인 및 비밀번호를 포함하는 4개의 전역 변수가 추가로 도입되었습니다. 저것들. 클러스터에 관리자 계정이 있는 경우 로그인 및 비밀번호를 입력하라는 대화 상자가 표시되고 데이터는 메모리에 저장되며 해당 클러스터에 대한 각 명령에 삽입됩니다.

이는 오류 처리 절차의 책임입니다.

오류Parsing

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에 대한 다시 작성

출처 : habr.com

코멘트를 추가