We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

Naarmate ik meer in de wereld van 1C-producten in een Linux-omgeving duikte, ontdekte ik één tekortkoming: het ontbreken van een gebruiksvriendelijke grafische multi-platformtool voor het beheren van een cluster van 1C-servers. Daarom besloot ik dit tekortkoming te verhelpen door een GUI te schrijven voor de consoletool rac. De programmeertaal die voor de ontwikkeling werd gekozen, was tcl/tk, omdat dit, naar mijn mening, het meest geschikt was voor deze taak. Ik wil enkele interessante aspecten van deze oplossing in dit materiaal presenteren.

Voor het werk zijn de distributies tcl/tk en 1C nodig. Aangezien ik besloot de mogelijkheden van de standaardversie van tcl/tk maximaal te benutten zonder gebruik te maken van externe pakketten, is versie 8.6.7 nodig, die de ttk bevat - een pakket met aanvullende grafische elementen. Wij hebben voornamelijk ttk::TreeView nodig, dat gegevens zowel in de vorm van een boomstructuur als in de vorm van een tabel (lijst) kan weergeven. Bovendien is in de nieuwe versie de verwerking van uitzonderingen aangepast (de try-opdracht, die in dit project wordt gebruikt bij het starten van externe opdrachten).

Het project bestaat uit verschillende bestanden (hoewel er niets is dat voorkomt dat alles in één bestand wordt gedaan):

rac_gui.cfg - standaardconfiguratie
rac_gui.tcl - het hoofdscript voor opstarten
In de map lib bevinden zich bestanden die automatisch worden geladen bij het opstarten:
function.tcl - bestand met procedures
gui.tcl - de belangrijkste grafische interface
images.tcl - afbeeldingsbibliotheek in base64

Het bestand rac_gui.tcl start eigenlijk de interpreter, initialiseert variabelen, laadt modules, configuraties, enzovoort. De inhoud van het bestand met opmerkingen:

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

Na het laden van alles wat nodig is en het controleren van de beschikbaarheid van de rac-tool, zal het grafische venster worden geopend. De interface van het programma bestaat uit drie elementen:

Werkbalk, boom en lijst

De inhoud van de ‘boom’ heb ik zo veel mogelijk gelijk gemaakt aan de standaard windows-component van 1C.

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

De hoofdcode die dit venster vormgeeft, bevindt zich in het bestand
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

Het werkalgoritme met het programma is als volgt:

1. In het begin moet je de hoofdserver van het cluster toevoegen (dat is, de server die het cluster beheert (in Linux wordt het beheer gestart met het commando ‘/opt/1C/v8.3/x86_64/ras cluster --daemon’)).

Om dit te doen, druk op de knop ‘+’ en voer in het geopende venster het adres van de server en de poort in:

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

Daarna zal onze server verschijnen in de boom, waarop we kunnen klikken om de lijst van clusters te openen of er verschijnt een verbindingsfout.

2. Klik op de naam van het cluster om een lijst met beschikbare functies te openen.

3.…

Enzovoort, met andere woorden, om een nieuw cluster toe te voegen, selecteer een beschikbaar item uit de lijst en klik op de knop «+» in de werkbalk. Vervolgens verschijnt het dialoogvenster voor het toevoegen van een nieuw cluster:

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

De knoppen in de werkbalk voeren functies uit op basis van de context, dat wil zeggen, afhankelijk van welk element van de boom of lijst is geselecteerd, wordt de overeenkomstige procedure uitgevoerd.

Laten we het voorbeeld van de toevoegknop («+») bekijken:

Code voor het genereren van de knop:

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

Hier zien we dat bij het klikken op de knop de procedure «Add» zal worden uitgevoerd, de bijbehorende code is:

proc Add {} {
    global active_cluster host
    # Bepaal de identifier van het geselecteerde element
    set id  [.frm_tree.tree selection] 
    # Bepaal de waarde van dit element
    set values [.frm_tree.tree item [.frm_tree.tree selection] -values]
    set key [lindex [split $id "::"] 0]
    # afhankelijk van wat is geselecteerd, wordt de juiste procedure gestart
    if {$key eq "" || $key eq "server"} {
        set host [ Add::server ]
        return
    }
    Add::$key .frm_tree.tree $host $values
}

En hier zien we een van de voordelen van Tcl — de waarde van de variabele kan als procedure-naam worden doorgegeven:

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

Dus, als we bijvoorbeeld op de hoofdserver klikken en «+» drukken, wordt de procedure Add::server gestart. Als we op een cluster klikken, wordt Add::cluster gestart, enzovoort (ik zal later uitleggen waar de benodigde «sleutels» vandaan komen). De genoemde procedures tekenen de overeenkomende visuele elementen op basis van de context.

Zoals je misschien al hebt opgemerkt, lijken de formulieren qua stijl op elkaar — dat is ook niet verwonderlijk, want ze worden door één procedure weergegeven, om precies te zijn, het hoofdframe van het formulier (venster, knoppen, afbeelding, label), de naam van de procedure. AddTopLevel

proc AddToplevel {lbl img {win_name .add}} {
    set cmd "destroy $win_name"
    if [winfo exists $win_name] {destroy $win_name}
    toplevel $win_name
    wm title $win_name $lbl
    wm iconphoto $win_name tcl
    # label met pictogram
    ttk::label $win_name.lbl -image $img
    # frame met invoervelden
    set frm [ttk::labelframe $win_name.frm -text $lbl -labelanchor nw]
    
    grid columnconfigure $frm 0 -weight 1
    grid rowconfigure $frm 0 -weight 1
    # frame en knoppen
    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
}

Aanroepparameters: titel, naam van de afbeelding voor het pictogram uit de bibliotheek (lib/images.tcl) en een optionele parameter voor de vennaam (standaard .add). Dus als we de bovenstaande voorbeelden voor het toevoegen van de hoofserver en cluster nemen, dan is de aanroep respectievelijk:

AddToplevel "Hoofdserver toevoegen" server_grey_64

of

AddToplevel "Cluster toevoegen" cluster_grey_64

En om met deze voorbeelden verder te gaan, laat ik de procedures zien die de invoerdialogen voor de server of het cluster weergeven.

Add::server

proc Add::server {} {
    global default
    # we tonen het hoofdformulier
    set frm [AddToplevel "Hoofdserver toevoegen" server_grey_64]
    # we voegen labels en invoervelden aan dit formulier toe
    label $frm.lbl_host -text "Serveradres"
    entry  $frm.ent_host
    label $frm.lbl_port -text "Poort"
    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]
    # we definiëren de handler voor de knopdruk
    .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
}

Add::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 ""
    }
    # Stel globale variabelen in
    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 toevoegen" cluster_grey_64]
    
    label $frm.lbl_host -text "Adres van de hoofdserver"
    entry  $frm.ent_host
    label $frm.lbl_port -text "Poort"
    entry $frm.ent_port 
    $frm.ent_port  insert end $default(port)
    label $frm.lbl_name -text "Naam van het cluster"
    entry  $frm.ent_name
    label $frm.lbl_secure_connect -text "Beveiligde verbinding"
    ttk::combobox $frm.cb_security_level -textvariable security_level -values $default(security_level)
    label $frm.lbl_expiration_timeout -text "Proces stopzetten na:"
    entry  $frm.ent_expiration_timeout -textvariable expiration_timeout
    label $frm.lbl_session_fault_tolerance_level -text "Fouttolerantie-niveau"
    entry  $frm.ent_session_fault_tolerance_level -textvariable session_fault_tolerance_level
    label $frm.lbl_load_balancing_mode -text "Laadbalanceringsmodus"
    ttk::combobox $frm.cb_load_balancing_mode -textvariable load_balancing_mode 
    -values $default(load_balancing_mode)
    label $frm.lbl_errors_count_threshold -text "Toegestane afwijking van het aantal serverfouten, %"
    entry  $frm.ent_errors_count_threshold -textvariable errors_count_threshold
    label $frm.lbl_processes -text "Werknemersprocessen:"
    label $frm.lbl_lifetime_limit -text "Herstartperiode, sec."
    entry  $frm.ent_lifetime_limit -textvariable lifetime_limit
    label $frm.lbl_max_memory_size -text "Toegestane geheugenomvang, KB"
    entry  $frm.ent_max_memory_size -textvariable max_memory_size
    label $frm.lbl_max_memory_time_limit -text "Interval overschrijding van toegestane geheugenomvang, sec."
    entry  $frm.ent_max_memory_time_limit -textvariable max_memory_time_limit
    label $frm.lbl_kill_problem_processes -justify left -anchor nw -text "Probleemprocessen forceren om te beëindigen"
    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
    # Herdefinieer de handler
    .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
}

Bij het vergelijken van de code van deze procedures is het verschil met het blote oog zichtbaar, ik wil de aandacht vestigen op de knopverwerkingshandler 'Ok'. In Tk kunnen eigenschappen van grafische elementen tijdens de uitvoering van het programma worden overschreven met behulp van een optie. configureBijvoorbeeld, de initiële opdracht om de knop weer te geven:

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

Maar in onze formulieren hangt de opdracht af van de vereiste functionaliteit:

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

In het bovenstaande voorbeeld is op de knop 'de uitvoering van de procedure voor het toevoegen van een cluster gebonden.

Hier moet een zijstap worden gemaakt naar het werken met grafische elementen in Tk - voor verschillende gegevensinvoerelementen (entry, combobox, checkbutton, enz.) is er een parameter ingevoerd genaamd tekstvariabele (textvariable):

entry  $frm.ent_lifetime_limit -textvariable lifetime_limit

Deze variabele is gedefinieerd in de globale naamruimte en bevat de huidige ingevoerde waarde. Dat wil zeggen, om de ingevoerde tekst uit het veld te krijgen, moet je gewoon de waarde van de overeenkomstige variabele lezen (uiteraard op voorwaarde dat deze is gedefinieerd bij het maken van het element).

De tweede methode om de ingevoerde tekst te krijgen (voor invoerelementen zoals entry) is het gebruik van de command get:

.add.frm.ent_name get

Beide methoden zijn in de bovenstaande code te zien.

Het indrukken van deze knop start in dit geval de procedure RunCommand met de gevormde opdrachtregel voor het toevoegen van een cluster in de termen van 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

Hier zijn we bij de belangrijkste opdracht gekomen, die het starten van rac met de gewenste parameters beheert, ook analyseert het de uitvoer van opdrachten naar lijsten en retourneert, indien nodig:

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
    # open channel in non-blocking mode
    # $rac - command with full path
    # $par - generated launch keys and options    
    set pipe [open "|$rac_cmd $par" "r"]
    try {
        set lst ""
        set l ""
        # add command output to the list of lists
        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} {
        # Start error handler
        ErrorParcing $result $options
        return ""
    }
}

Na het invoeren van de gegevens van de hoofdserver zal deze aan de boom worden toegevoegd; de volgende code in de procedure Add:server is hiervoor verantwoordelijk:

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

Nu kunnen we door op de naam van de server in de boom te klikken, een lijst van clusters die door die server worden beheerd krijgen, en door op een cluster te klikken, krijgen we een lijst van element van het cluster (servers, informatiebases, enz.). Dit is geïmplementeerd in de procedure TreePress (bestand lib/function.tcl):

proc TreePress {tree} {
   global host server active_cluster infobase
   # bepaal het geselecteerde element
    set id  [$tree selection]
   # stel de juiste globale variabelen in
    SetGlobalVarFromTreeItems $tree $id
   # bepaal de sleutel en waarde, d.w.z. het type van het geselecteerde element
    set values [$tree item $id -values]
    set key [lindex [split $id "::"] 0]
   # en afhankelijk van wat is geselecteerd wordt de bijbehorende procedure uitgevoerd
   # in de namespace Run
    Run::$key $tree $host $values
}

Daarom zal voor de hoofdserver Run::server worden uitgevoerd (voor het cluster — Run::cluster, voor de werkserver — Run::work_server, enz.). Dat wil zeggen, de waarde van de variabele $key is een deel van de naam van het boomelement, bepaald door de optie -id.

Laten we aandacht besteden aan de procedure

Run::server

proc Run::server {tree host values} {
    # we verkrijgen de lijst van clusters voor de vereiste server
    set lst [RunCommand server::$host "cluster list $host"]
    if {$lst eq ""} {return}
    set l [lindex $lst 0]
    #puts $lst
    # we verwijderen overbodige items uit de lijst
    .frm_work.tree_work delete  [ .frm_work.tree_work children {}]
    # we lezen de lijst
    foreach cluster_list $lst {
        # We vullen de lijst met de verkregen waarden
        InsertItemsWorkList $cluster_list
        # verwerken van de output (lijst) om gegevens aan de boom toe te voegen
        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]]
            }
        }
    }
    # we voegen clusters aan de boom toe
    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"
            # we voegen elementen aan het cluster toe
            InsertClusterItems $tree $id
        }
    }
    if { [$tree exists "agent_admins::$id"] == 0 } {
        $tree insert "server::$host" end -id "agent_admins::$id" -text "Beheerders" -values "$id"
        #InsertClusterItems $tree $id
    }
}

Deze procedure verwerkt wat van de server is ontvangen via het commando RunCommand en voegt verschillende zaken toe aan de boom - clusters, verschillende hoofdelementen (databases, werkservers, sessies, enzovoort). Als je goed kijkt, kun je de oproep van de procedure InsertItemsWorkList opmerken. Deze wordt gebruikt om items aan de grafische lijst toe te voegen, waarbij de output van de consoletool rac wordt verwerkt, die eerder als lijst in de variabele $lst is teruggegeven. Dit is een lijst van lijsten, die paren van elementen bevatten gesplitst door een dubbele punt.

Bijvoorbeeld, de lijst met verbindingen van het cluster:

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

Grafisch zou dit er ongeveer zo uitzien:

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

De hierboven genoemde procedure haalt de namen van de elementen voor de kop en de gegevens voor het invullen van de tabel:

InsertItemsWorkList

proc InsertItemsWorkList {lst} {
    global work_list_row_count
    # instelling van het afwisselen van kleur voor de regel
    if [expr $work_list_row_count % 2] {
        set tag dark
    } else {
        set tag light
    }
    # splitsen van regels in paren sleutel - waarde
    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]
        }
    }
     # invullen van de tabel
    .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
    # instellen van de koppen
    foreach j $column_list {
        .frm_work.tree_work heading $j -text $j
    }
    incr work_list_row_count
}

Hier in plaats van de eenvoudige opdracht [split $str «:»], die de string splitst in elementen gescheiden door «:» en een lijst retourneert, is een reguliere expressie gebruikt, omdat sommige elementen ook een dubbele punt bevatten.

De procedure InsertClusterItems (een van de verschillende vergelijkbare) voegt eenvoudigweg een lijst van kinderen met bijbehorende identificatoren toe aan de gevraagde clusterelement in de boom.
InsertClusterItems

proc InsertClusterItems {tree id} {
    set parent "cluster::$id"
    $tree insert $parent end -id "infobases::$id" -text "Informatiebases" -values "$id"
    $tree insert $parent end -id "servers::$id" -text "Werken servers" -values "$id"
    $tree insert $parent end -id "admins::$id" -text "Beheerders" -values "$id"
    $tree insert $parent end -id "managers::$id" -text "Clusterbeheerders" -values $id
    $tree insert $parent end -id "processes::$id" -text "Werkprocessen" -values "workprocess-all"
    $tree insert $parent end -id "sessions::$id" -text "Sessies" -values "sessions-all"
    $tree insert $parent end -id "locks::$id" -text "Vergrendelingen" -values "blocks-all"
    $tree insert $parent end -id "connections::$id" -text "Verbindingen" -values "connections-all"
    $tree insert $parent end -id "profiles::$id" -text "Beveiligingsprofielen" -values $id
}

Er kunnen nog twee varianten van een dergelijke procedure worden overwogen, waaruit duidelijk blijkt hoe de herhalende opdrachten kunnen worden geoptimaliseerd en geëlimineerd.

In deze procedure zijn toevoeging en controle rechttoe rechtaan opgelost:

InsertBaseItems

proc InsertBaseItems {tree id} {
    set parent "infobase::$id"
    if { [$tree exists "sessions::$id"] == 0 } {
        $tree insert $parent end -id "sessions::$id" -text "Sessies" -values "$id"
    }
    if { [$tree exists "locks::$id"] == 0 } {
        $tree insert $parent end -id "locks::$id" -text "Vergrendelingen" -values "$id"
    }
    if { [$tree exists "connections::$id"] == 0 } {
        $tree insert $parent end -id "connections::$id" -text "Verbindingen" -values "$id"
    }
}

En hier is de aanpak correcter:

InsertProfileItems

proc InsertProfileItems {tree id} {
    set parent "profile::$id"
    set lst {
        {dir "Virtuele Mappen"}
        {com "Toegestane COM-classes"}
        {addin "Externe Componenten"}
        {module "Externe Rapporten en Verwerkingen"}
        {app "Toegestane Applicaties"}
        {inet "Internetbronnen"}
    }
    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 
    }
}

Het verschil tussen hen ligt in het gebruiken van de lus, waarin de herhalende opdracht (opdrachten) wordt uitgevoerd. Welke benadering te gebruiken, dat is aan de ontwikkelaar.

We hebben de toevoeging van elementen en het verkrijgen van gegevens behandeld, het is tijd om stil te staan bij het bewerken. Aangezien voor het bewerken en toevoegen in principe dezelfde parameters worden gebruikt (uitzondering is de informatiemap), worden vergelijkbare dialoogvensters gebruikt. Het algoritme voor het aanroepen van procedures voor toevoegingen ziet er zo uit:

Add::$key->AddToplevel

En voor bewerking ziet het er als volgt uit:

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

Laten we als voorbeeld de bewerking van een cluster nemen, dat wil zeggen, door in de boom op de naam van het cluster te klikken, drukken we op de bewerkknop in de werkbalk (het potlood) en het bijbehorende venster zal worden weergegeven:

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.
Edit::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 ""
    }
    # teken de vorm voor de cluster
    set frm [Add::cluster $tree $host $values]
    # wijzig de tekst op het label
    $frm configure -text "Cluster Bewerken"
    
    set active_cluster $values
    # verkrijg gegevens van de geselecteerde cluster
    set lst [RunCommand cluster::$values "cluster info --cluster=$active_cluster $host"]
    # vul de velden in
    FormFieldsDataInsert $frm $lst
    # schakel velden uit die niet bewerkt mogen worden
    $frm.ent_host configure -state disable
    $frm.ent_port configure -state disable
    # wijzig de handler
    .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
    }
}

De opmerkingen in de code zijn in principe duidelijk, behalve dat de handler van de knop opnieuw gedefinieerd is en er een procedure FormFieldsDataInsert is die de velden met gegevens vuldt en variabelen initialiseert:

FormFieldsDataInsert

proc FormFieldsDataInsert {frm lst} {
    foreach i [lindex $lst 0] {
        # verkrijg de lijst van parameters en waarden
        if [regexp -nocase -all -- {(D+)(s*?|)(:)(s*?|)(.*)} $i match param v2 v3 v4 value] {
            # wijzig de symbolen
            regsub -all -- "-" [string trim $param] "_" entry_name
            # vul met gegevens
            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 """]
            }
            # voor checkboxen wijzig de waarden
            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
                }
            }
        }
    }
}

In deze procedure komt er nog een voordeel van tcl naar voren — als namen van variabelen worden de waarden van andere variabelen ingevoegd. Dat wil zeggen, voor de automatisering van formulierinvoer en de initialisatie van variabelen, komen de namen van de velden en variabelen overeen met de sleutels van de opdrachtregelutility rac en de namen van de uitvoerparameters van opdrachten, met enkele uitzonderingen — het koppelteken is vervangen door een underscore. Bijvoorbeeld scheduled-jobs-deny komt overeen met het veld ent_scheduled_jobs_deny en de variabele scheduled_jobs_deny.

De formulieren voor toevoegen en bewerken kunnen verschillen in samenstelling van de velden, bijvoorbeeld, het werken met de informatiebasis:

Informatie toevoegen

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

Informatie bewerken

We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

In de bewerkingsprocedure Edit::infobase worden de benodigde velden aan het formulier toegevoegd, de code is omvangrijk, daarom geef ik deze hier niet weer.

Op dezelfde manier zijn de procedures voor het toevoegen, bewerken, verwijderen en voor de andere elementen geïmplementeerd.

Aangezien het werken met de utility een onbeperkt aantal servers, clusters, informatiebases, enz. impliceert, zijn er verschillende globale variabelen geïntroduceerd om te bepalen tot welk cluster elke server of informatiebasis behoort, waarvan de waarden bij elke klik op de boomelementen worden ingesteld. Met andere woorden, de procedure doorloopt recursief alle bovenliggende elementen en stelt de variabelen in:

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

Het 1C-cluster ondersteunt zowel werk met als zonder autorisatie. Er zijn twee soorten beheerders — de clusteragentbeheerder en de clusterbeheerder. Dienovereenkomstig zijn er 4 extra globale variabelen ingevoerd die de inlognaam en het wachtwoord van de beheerder bevatten voor een correcte werking. Dat wil zeggen, als er een beheerdersaccount in het cluster aanwezig is, wordt een dialoogvenster weergegeven voor het invoeren van de gebruikersnaam en het wachtwoord, de gegevens worden in het geheugen opgeslagen en in elke opdracht voor het bijbehorende cluster ingevoegd.

Dit wordt afgehandeld door de foutverwerkingsprocedure

ErrorParsing

proc ErrorParcing {err opt} {
    global cluster_user cluster_pwd agent_user agent_pwd
        switch -regexp -- $err {
        "Cluster administrator is not authenticated" {
            AuthorisationDialog "Clusterbeheerder"
            .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 "Agentbeheerder"
            .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
            }
        }
        "Administrateur van het cluster is niet geauthenticeerd" {
            AuthorisationDialog "Clusterbeheerder"
            .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
        }
        "Beheerder van de centrale server is niet geauthenticeerd" {
            AuthorisationDialog "Agentbeheerder"
            .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"
        }
    }
}

Dat wil zeggen, afhankelijk van wat het commando teruggeeft, zal de reactie dienovereenkomstig zijn.

Op dit moment is de functionaliteit voor ongeveer 95% gerealiseerd, er moet nog gewerkt worden aan de beveiligingsprofielen en getest worden =). Dat is het. Mijn excuses voor het samengeperste verhaal.

De code is, zoals gebruikelijk, beschikbaar. here.

Update: Ik heb het werk aan de beveiligingsprofielen voltooid. Nu is de functionaliteit voor 100% gerealiseerd.

Update 2: Engelse en Russische localisatie toegevoegd, werking op win7 gecontroleerd.
We schrijven een GUI voor 1C RAC, of opnieuw over Tcl/Tk.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster