ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

የ1C ምርቶች በሊኑክስ አካባቢ እንዴት እንደሚሠሩ ወደሚለው ርዕስ ስንመረምር አንድ ችግር ታይቷል - የ1C አገልጋይ ክላስተር ለማስተዳደር ምቹ የሆነ የግራፊክ መልቲ ፕላትፎርም መሳሪያ እጥረት። እና ለራክ ኮንሶል መገልገያ GUI በመጻፍ ይህንን ጉድለት ለማስተካከል ተወስኗል። የልማት ቋንቋ tcl/tk ነበር በእኔ አስተያየት ለዚህ ተግባር በጣም ተስማሚ የሆነው። እና አሁን, በዚህ ጽሑፍ ውስጥ የመፍትሄውን አንዳንድ አስደሳች ገጽታዎች ማቅረብ እፈልጋለሁ.

ለመስራት tcl/tk እና 1C ማሰራጫዎች ያስፈልግዎታል። እና የሶስተኛ ወገን ፓኬጆችን ሳይጠቀም የ tcl / tk የመሠረታዊ አቅርቦት እድሎችን ለመጠቀም ወስኛለሁ ፣ ስሪት 8.6.7 እንፈልጋለን ፣ ይህም ttkን ያካትታል - ተጨማሪ ግራፊክ አካላት ያለው ጥቅል ፣ ከእነዚህም ውስጥ በዋነኝነት ttk እንፈልጋለን። :: TreeView፣ በዛፍ መዋቅር መልክ እና በሠንጠረዥ መልክ (ዝርዝር) መልክ የውጤት መረጃን ይፈቅዳል። እንዲሁም ከልዩነቶች ጋር ያለው ሥራ በአዲሱ ስሪት ውስጥ እንደገና ተዘጋጅቷል (የሙከራ ትዕዛዝ ፣ በፕሮጀክቱ ውስጥ የውጭ ትዕዛዞችን ሲያከናውን ጥቅም ላይ ይውላል)።

ፕሮጀክቱ ብዙ ፋይሎችን ያቀፈ ነው (ምንም እንኳን ሁሉም ነገር አንድ ከመሆን የሚከለክለው ምንም ነገር ባይኖርም)

rac_gui.cfg - ነባሪ ውቅር
rac_gui.tcl - ዋና ጅምር ስክሪፕት።
የlib ማውጫው በሚነሳበት ጊዜ በራስ ሰር የተጫኑ ፋይሎችን ይዟል፡-
function.tcl - ከሂደቶች ጋር ፋይል ያድርጉ
gui.tcl - ዋና GUI
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"
    }    
}

አስፈላጊውን ሁሉ ካወረዱ በኋላ እና የራክ መገልገያ መኖሩን ካረጋገጡ በኋላ የግራፊክስ መስኮት ይከፈታል. የፕሮግራሙ በይነገጽ ሶስት አካላትን ያቀፈ ነው-

የመሳሪያ አሞሌ, ዛፍ እና ዝርዝር

የ "ዛፉ" ይዘቶች በተቻለ መጠን ከመደበኛው የዊንዶውስ-መሳሪያዎች ከ 1 ሴ.

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

ይህ መስኮት የሚሠራው ዋናው ኮድ በፋይሉ ውስጥ ይገኛል
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. መጀመሪያ ላይ ዋናውን ክላስተር አገልጋይ (ማለትም የክላስተር አስተዳደር አገልጋይ (በሊኑክስ ላይ ማኔጅመንት የሚጀምረው በ "/opt/1C/v8.3/x86_64/ras cluster -daemon") ትዕዛዝ ነው))።

ይህንን ለማድረግ የ"+" ቁልፍን ጠቅ ያድርጉ እና በሚከፈተው መስኮት ውስጥ የአገልጋዩን አድራሻ እና ወደብ ያስገቡ።

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

ከዚያ በኋላ የኛ አገልጋይ በዛፉ ላይ ጠቅ በማድረግ ይታያል, የክላስተር ዝርዝር ይከፈታል ወይም የግንኙነት ስህተት ይታያል.

2. የክላስተር ስም ላይ ጠቅ ማድረግ ለእሱ ያሉትን ተግባራት ዝርዝር ይከፍታል.

3…

ደህና, እና ወዘተ, i.e. አዲስ ክላስተር ለመጨመር በዝርዝሩ ውስጥ ያለውን ማንኛውንም ይምረጡ እና በመሳሪያ አሞሌው ላይ ያለውን "+" ቁልፍ ይጫኑ እና አዲስ ለመጨመር ንግግሩ ይታያል.

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

በመሳሪያ አሞሌው ውስጥ ያሉ አዝራሮች እንደ አውድ ላይ ተመስርተው ተግባራትን ያከናውናሉ, ማለትም. የትኛው የዛፉ ወይም የዝርዝሩ አካል እንደተመረጠ ይህ ወይም ያኛው አሰራር ይከናወናል.

የመደመር ቁልፍን ("+") ምሳሌን አስቡበት፡-

የአዝራር ምስረታ ኮድ፡-

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:: አገልጋይ አሰራር ይጀመራል፡ ወደ ክላስተር — መደመር:: ክላስተር ከገባ እና ወዘተ (ትንሽ እጽፋለሁ)። ስለ አስፈላጊዎቹ "ቁልፎች" ከታች ከየት እንደሚመጡ), የተዘረዘሩት ሂደቶች ለዐውደ-ጽሑፉ ተስማሚ የሆኑ ስዕላዊ ክፍሎችን ይሳሉ.

እርስዎ እንዳስተዋሉት, ቅጾቹ በቅጥ ተመሳሳይ ናቸው - ይህ አያስገርምም, ምክንያቱም እነሱ በአንድ አሰራር ስለሚታዩ, የበለጠ በትክክል, የቅጹ ዋና ፍሬም (መስኮት, አዝራሮች, ምስል, መለያ), የሂደቱ ስም. 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
}

የእነዚህን ሂደቶች ኮድ በማነፃፀር ልዩነቱ ለዓይን ይታያል, እሺ አዝራር ተቆጣጣሪ ላይ አተኩራለሁ. በ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
    }

ከላይ ባለው ምሳሌ ውስጥ "የተጨናነቀ" አዝራር ክላስተር ለመጨመር ሂደቱን ይጀምራል.

እዚህ በቲኬ ውስጥ ከግራፊክ አካላት ጋር ለመስራት ትኩረት መስጠት ጠቃሚ ነው - ለተለያዩ የውሂብ ግቤት አካላት (ግቤት ፣ ኮምቦቦክስ ፣ አመልካች ቁልፍ ፣ ወዘተ) ፣ እንደ የጽሑፍ ተለዋዋጭ (ጽሑፍ) መለኪያ ገብቷል ።

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

ይህ ተለዋዋጭ በአለምአቀፍ የስም ቦታ ላይ ይገለጻል እና አሁን የገባውን እሴት ይዟል. እነዚያ። የገባውን ጽሑፍ ከመስክ ለማግኘት ከተለዋዋጭ ጋር የሚዛመደውን እሴት ማንበብ ብቻ ያስፈልግዎታል (በእርግጥ ኤለመንቱ ሲፈጠር ከተገለጸ)።

የገባውን ጽሑፍ ለማግኘት ሁለተኛው ዘዴ (ለአካላት ግቤት አይነት) የማግኘት ትዕዛዙን መጠቀም ነው፡-

.add.frm.ent_name get

እነዚህ ሁለቱም ዘዴዎች ከላይ ባለው ኮድ ውስጥ ሊታዩ ይችላሉ.

ይህንን ቁልፍ ሲጫኑ ፣በዚህ አጋጣሚ ፣ 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

ስለዚህ ወደ ዋናው ትእዛዝ ደርሰናል ፣እሱ በሚያስፈልገን መለኪያዎች የሩጫውን ጅምር የሚቆጣጠረው ፣ አስፈላጊ ከሆነም የትዕዛዙን ውጤት ወደ ዝርዝሮች እና መልሶች ይተነትናል ።

ትዕዛዝን አሂድ

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: አገልጋይ ሂደት ውስጥ ፣ የሚከተለው ኮድ ተጠያቂ ነው

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

አሁን, በዛፉ ውስጥ ያለውን የአገልጋይ ስም ጠቅ በማድረግ, በዚህ አገልጋይ የሚተዳደሩ የክላስተር ዝርዝር እናገኛለን, እና ክላስተር ላይ ጠቅ በማድረግ, የክላስተር አባላትን ዝርዝር (ሰርቨሮች, ኢንፎቤዝ, ወዘተ) እናገኛለን. ይህ በትሬፕረስ አሰራር (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:: cluster for the cluster፣ Run:: work_server for the working 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 አሰራር ጥሪን ማየት ይችላሉ። ከዚህ ቀደም በ$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

በሥዕላዊ መግለጫው እንደዚህ ያለ ነገር ይመስላል።

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

ከላይ ያለው አሰራር ለርዕሱ እና ሰንጠረዡን ለመሙላት የንጥል ስሞችን ያወጣል፡-

የስራ ዝርዝር አስገባ

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
}

እዚህ ላይ፣ ሕብረቁምፊውን በ":" ወደሚለያዩ ክፍሎች ከሚከፍለው እና ዝርዝርን ከሚመልስ ቀላል ትእዛዝ [የተከፈለ $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
}

ተደጋጋሚ ትዕዛዞችን እንዴት ማመቻቸት እና ማስወገድ እንደሚችሉ በግልፅ የሚታይበት እንዲህ ዓይነቱን አሰራር ለመተግበር ሁለት ተጨማሪ አማራጮችን ግምት ውስጥ ማስገባት ይችላሉ-

በዚህ ሂደት ውስጥ መጨመር እና ማጣራት ወደፊት ይፈታሉ፡-

BaseItems አስገባ

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->አክልTopLevel

እና እንደዚህ ላለው ማረም;

አርትዕ::$key->አክል::$key->አክልቶፕ ደረጃ

ለምሳሌ፣ ክላስተር አርትዖትን እንውሰድ፣ ማለትም. በክላስተር ስም ላይ በዛፉ ላይ ጠቅ በማድረግ በመሳሪያ አሞሌው (እርሳስ) ውስጥ ያለውን የአርትዖት ቁልፍ ይጫኑ እና ተዛማጅ ቅጹ በስክሪኑ ላይ ይታያል፡

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።
አርትዕ::ክላስተር

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 ፕላስ ታየ - የሌሎች ተለዋዋጮች እሴቶች በተለዋዋጭ ስሞች ተተክተዋል። እነዚያ። ቅጾችን መሙላት እና ተለዋዋጮችን ማስጀመር ፣ የመስኮች እና ተለዋዋጮች ስሞች ከሬክ መገልገያው የትእዛዝ መስመር መቀየሪያዎች እና የትዕዛዝ ውፅዓት ግቤቶች ስሞች ጋር ይዛመዳሉ - ሰረዝ በስር ምልክት ተተክቷል። ለምሳሌ የታቀዱ - ስራዎች - ውድቅ ከሜዳው ጋር ይዛመዳል የታቀዱ_ስራዎችን_ካዱ እና ተለዋዋጭ የታቀዱ_ስራዎች_ካዱ.

የመደመር እና የማረም ቅጾች በመስኮቹ ስብጥር ሊለያዩ ይችላሉ፣ ለምሳሌ ከመረጃ ቤዝ ጋር መስራት፡

IB በማከል ላይ

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

IB አርትዖት

ለ1C RAC ወይም ስለ Tcl/Tk GUI እንጽፋለን።

በአርትዖት ሂደት ውስጥ አርትዕ :: infobase, አስፈላጊዎቹ መስኮች በቅጹ ላይ ተጨምረዋል, ስለዚህ ኮዱን እዚህ አልሰጥም.

በተመሣሣይ ሁኔታ, የመደመር, የማረም እና የመሰረዝ ሂደቶች ለሌሎች አካላት ይተገበራሉ.

የመገልገያው አሠራር ያልተገደበ ቁጥር ያላቸው አገልጋዮች ፣ ስብስቦች ፣ የመረጃ ቋቶች ፣ ወዘተ. አንድ አገልጋይ ወይም አይ ኤስ የየትኛው ክላስተር እንደሆነ ለመወሰን ብዙ ዓለም አቀፍ ተለዋዋጮች ይተዋወቃሉ ፣ እሴቶቹ በሚጫኑበት በእያንዳንዱ ጊዜ ይዘጋጃሉ። የዛፉ ንጥረ ነገሮች. እነዚያ። የአሰራር ሂደቱ በሁሉም የወላጅ አካላት ውስጥ በተደጋጋሚ ይከናወናል እና ተለዋዋጮችን ያዘጋጃል-

GlobalVarFromTreeItems አዘጋጅ

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 ተጨማሪ ዓለም አቀፍ ተለዋዋጮች ቀርበዋል። እነዚያ። በክላስተር ውስጥ የአስተዳዳሪ መለያ ካለ ፣ መግቢያ እና የይለፍ ቃል ለማስገባት ንግግር ይታያል ፣ ውሂቡ በማህደረ ትውስታ ውስጥ ይቀመጣል እና ለተዛማጅ ክላስተር በእያንዳንዱ ትዕዛዝ ይተካል ።

ይህ የስህተት አያያዝ ሂደት ሃላፊነት ነው.

የፓርኪንግ ስህተት

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 ወይም ስለ Tcl/Tk GUI እንጽፋለን።

ምንጭ: hab.com

አስተያየት ያክሉ