כתיבת GUI עבור 1C RAC, או שוב על Tcl/Tk

כאשר התעמקנו בנושא איך מוצרי 1C עובדים בסביבת לינוקס, התגלה חיסרון אחד - היעדר כלי גרפי מרובה פלטפורמות נוח לניהול אשכול שרתי 1C. והוחלט לתקן את החיסרון הזה על ידי כתיבת GUI עבור כלי השירות של קונסולת ה-rac. Tcl/tk נבחרה כשפת הפיתוח בתור, לדעתי, המתאימה ביותר למשימה זו. ולכן, אני רוצה להציג כמה היבטים מעניינים של הפתרון בחומר זה.

כדי לעבוד תזדקק להפצות tcl/tk ו-1C. ומכיוון שהחלטתי להפיק את המרב מהיכולות של משלוח tcl/tk הבסיסי מבלי להשתמש בחבילות של צד שלישי, אזדקק לגרסה 8.6.7 הכוללת ttk - חבילה עם אלמנטים גרפיים נוספים, שמהם אנחנו צריכים בעיקר 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, יופעל חלון גרפי. ממשק התוכנית מורכב משלושה אלמנטים:

סרגל כלים, עץ ורשימה

עשיתי את התוכן של "העץ" דומה ככל האפשר לציוד Windows הסטנדרטי מ-1C.

כתיבת GUI עבור 1C RAC, או שוב על 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")).

כדי לעשות זאת, לחץ על כפתור "+" ובחלון שנפתח, הזן את כתובת השרת והיציאה:

כתיבת GUI עבור 1C RAC, או שוב על Tcl/Tk

לאחר מכן, השרת שלנו יופיע בעץ בלחיצה עליו, תיפתח רשימה של אשכולות או תוצג שגיאת חיבור.

2. לחיצה על שם האשכול תפתח רשימה של פונקציות הזמינות עבורו.

3. ...

וכן הלאה, כלומר. כדי להוסיף אשכול חדש, בחר כל אחד זמין ברשימה ולחץ על הלחצן "+" בסרגל הכלים ותיבת הדו-שיח הוסף חדש תוצג:

כתיבת GUI עבור 1C RAC, או שוב על 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
}

הנה אחד היתרונות של tickle: אתה יכול להעביר את הערך של משתנה כשם פרוצדורה:

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
}

הוסף::cluster

proc Add::cluster {tree host values} {
    global default lifetime_limit expiration_timeout session_fault_tolerance_level
    global max_memory_size max_memory_time_limit errors_count_threshold security_level
    global load_balancing_mode kill_problem_processes 
    agent_user agent_pwd cluster_user cluster_pwd auth_agent
    if {$agent_user ne "" && $agent_pwd ne ""} {
        set auth_agent "--agent-user=$agent_user --agent-pwd=$agent_pwd"
    } else {
        set auth_agent ""
    }
    # устанавливаем глобальные переменные ()
    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
}

כאשר משווים את הקוד של נהלים אלה, ההבדל גלוי לעין בלתי מזוינת; אני אתמקד במטפל בלחצן "אישור". ב-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 - עבור רכיבי קלט נתונים שונים (כניסה, combobox, checkbutton וכו') הוכנס פרמטר כמשתנה טקסט:

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

משתנה זה מוגדר במרחב השמות הגלובלי ומכיל את הערך שהוזן כעת. הָהֵן. כדי לקבל את הטקסט שהוזן מהשדה, אתה רק צריך לקרוא את הערך המתאים למשתנה (כמובן, בתנאי שהוא מוגדר בעת יצירת האלמנט).

השיטה השנייה לאחזור הטקסט שהוזן (עבור רכיבים מסוג ערך) היא להשתמש בפקודה get:

.add.frm.ent_name get

ניתן לראות את שתי השיטות הללו בקוד לעיל.

לחיצה על כפתור זה, במקרה זה, מפעילה את הליך RunCommand עם שורת הפקודה שנוצרה להוספת אשכול במונחים של rac:

/opt/1C/v8.3/x86_64/rac cluster insert  --host=localhost  --port=1540  --name=dsdsds  --expiration-timeout=0  --lifetime-limit=0  --max-memory-size=0  --max-memory-time-limit=0  --security-level=0  --session-fault-tolerance-level=0  --load-balancing-mode=performance  --errors-count-threshold=0  --kill-problem-processes=no   localhost:1545

כעת הגענו לפקודה הראשית, השולטת בהפעלה של 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 ""
    }
}

לאחר הזנת נתוני השרת הראשי, הוא יתווסף לעץ, לשם כך, בהליך הוסף:שרת לעיל, אחראי הקוד הבא:

.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 הוא חלק מהשם של אלמנט העץ שצוין על ידי האפשרות -.

בואו נשים לב לנוהל

הפעל::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
    }
}

הפרוצדורה הזו מעבדת את מה שהתקבל מהשרת דרך הפקודה RunCommand ומוסיפה כל מיני דברים לעץ - אשכולות, אלמנטים שורשיים שונים (בסיסים, שרתים עובדים, סשנים וכדומה). אם תסתכל מקרוב, תבחין בקריאה לנוהל InsertItemsWorkList בפנים. הוא משמש להוספת אלמנטים לרשימה גרפית על ידי עיבוד הפלט של כלי השירות rac console, שהוחזר בעבר כרשימה למשתנה $lst. זוהי רשימה של רשימות המכילות זוגות של אלמנטים מופרדים בנקודתיים.

לדוגמה, רשימה של חיבורי אשכול:

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

בצורה גרפית זה ייראה בערך כך:

כתיבת GUI עבור 1C RAC, או שוב על 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 
    }
}

ההבדל ביניהם הוא השימוש בלולאה, שבה מבוצעות הפקודות החוזרות ונשנות. איזו גישה להשתמש נתונה לשיקול דעתו של היזם.

סקרנו הוספת אלמנטים ואחזור נתונים, עכשיו הגיע הזמן להתמקד בעריכה. מכיוון שבעצם, אותם פרמטרים משמשים לעריכה והוספה (למעט בסיס המידע), נעשה שימוש באותם טפסי דיאלוג. האלגוריתם לקריאה להליכי הוספה נראה כך:

הוסף::$key->AddToplevel

ולעריכה כזו:

עריכה::$key->הוסף::$key->AddTopLevel

לדוגמה, ניקח עריכה של אשכול, כלומר. לאחר לחיצה על שם האשכול בעץ, לחץ על כפתור העריכה בסרגל הכלים (עיפרון) והטופס המתאים יוצג על המסך:

כתיבת GUI עבור 1C RAC, או שוב על Tcl/Tk
עריכה::cluster

proc Edit::cluster {tree host values} {
    global default lifetime_limit expiration_timeout session_fault_tolerance_level
    global max_memory_size max_memory_time_limit errors_count_threshold security_level
    global load_balancing_mode kill_problem_processes active_cluster 
    agent_user agent_pwd cluster_user cluster_pwd auth
    if {$cluster_user ne "" && $cluster_pwd ne ""} {
        set auth "--cluster-user=$cluster_user --cluster-pwd=$cluster_pwd"
    } else {
        set auth ""
    }
    # рисуем форму для кластера
    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 ולשמות של פרמטרי פלט הפקודה למעט חריג - המקף מוחלף בקו תחתון. לְמָשָׁל מתוזמנים-עבודות-דחיית מתאים לתחום ent_scheduled_jobs_deny ומשתנה דחיית_עבודות_מתוזמנות.

טפסים להוספה ועריכה עשויים להיות שונים בהרכב השדות, למשל עבודה עם בסיס מידע:

הוספת אבטחת מידע

כתיבת GUI עבור 1C RAC, או שוב על Tcl/Tk

עריכת אבטחת מידע

כתיבת GUI עבור 1C RAC, או שוב על 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 משתנים גלובליים נוספים המכילים את הכניסה והסיסמה של מנהל המערכת. הָהֵן. אם יש חשבון מנהל באשכול, תוצג תיבת דו-שיח להזנת פרטי הכניסה והסיסמה שלך, הנתונים יישמרו בזיכרון ויוכנסו לכל פקודה עבור האשכול המתאים.

זו באחריות הליך הטיפול בשגיאות.

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 נבדקה
כתיבת GUI עבור 1C RAC, או שוב על Tcl/Tk

מקור: www.habr.com

הוספת תגובה