# # GUI support for managing CORE node services. # # # Popup a services configuration dialog box. Similar to popupCapabilityConfig # but customized for configuring node services. This dialog has two uses: # (1) selecting the default services for a node type (when session != ""), and # (2) selecting/customizing services for a certain node # proc popupServicesConfig { channel node types values captions possible_values groups session } { global plugin_img_edit global g_service_ctls global g_sent_nodelink_definitions set wi .popupServicesConfig catch {destroy $wi} toplevel $wi # instead of using vals, the activated services are stored in this list set activated "" if { $session != "" } { global g_node_type_services_hint if { ![info exists g_node_type_services_hint] } { set g_node_type_services_hint "router" } set title "Default services" set toptitle "Default services for node type $g_node_type_services_hint" set activated [getNodeTypeServices $g_node_type_services_hint] set parent .nodesConfig } else { set title "Node [getNodeName $node] ($node) services" set toptitle $title set activated [getNodeServices $node true] set parent .popup } if { ![winfo exists $parent] } { set parent "." } wm title $wi $title wm transient $wi $parent label $wi.top -text $toptitle pack $wi.top -side top -padx 4 -pady 4 frame $wi.vals -relief raised -borderwidth 1 set g_sent_nodelink_definitions 0 ;# only send node/link defs once set g_service_ctls {} ;# list of checkboxes set n 0 set gn 0 set lastgn -1 foreach type $types { # group values into frames based on groups TLV set groupinfo [popupCapabilityConfigGroup $groups [expr {$n + 1}]] set gn [lindex $groupinfo 0] set groupcaption [lindex $groupinfo 1] if { $lastgn != $gn } { labelframe $wi.vals.$gn -text $groupcaption \ -borderwidth 1 -padx 4 -pady 4 } frame $wi.vals.$gn.item$n if {$type != 11} { ;# boolean value puts "warning: skipping service config [lindex $captions $n]" incr n continue } set servicename [lindex $captions $n] global $wi.vals.$gn.item$n.entval checkbutton $wi.vals.$gn.item$n.ent -width 12 -wraplength 90 \ -variable $wi.vals.$gn.item$n.entval -text $servicename \ -offrelief flat -indicatoron false -overrelief raised lappend g_service_ctls $wi.vals.$gn.item$n.ent if { [lsearch -exact $activated [lindex $captions $n]] == -1 } { set value 0 ;# not in the activated list } else { set value 1 } set $wi.vals.$gn.item$n.entval $value if { $session == "" } { set needcustom false if { $n < [llength $possible_values] } { if { [lindex $possible_values $n] == 1 } { set needcustom true } } set btn $wi.vals.$gn.item$n.custom button $btn -image $plugin_img_edit \ -command "customizeService $wi $node $servicename $btn" setCustomButtonColor $btn $node $servicename $needcustom pack $wi.vals.$gn.item$n.custom -side right -padx 4 -pady 4 # this causes the button for services that require customization to # turn yellow when the service is selected $wi.vals.$gn.item$n.ent configure -command \ "setCustomButtonColor $btn $node $servicename $needcustom" } pack $wi.vals.$gn.item$n.ent -side right -padx 4 -pady 4 pack $wi.vals.$gn.item$n -side top -anchor e if { $lastgn != $gn } { pack $wi.vals.$gn -side left -anchor n -fill both set lastgn $gn } incr n }; # end foreach pack $wi.vals.$gn -side left -anchor n -fill both pack $wi.vals -side top -padx 4 -pady 4 # Apply / Cancel buttons set apply_cmd "popupServicesConfigApply $wi $channel $node {$session}" set cancel_cmd "destroy $wi" frame $wi.btn button $wi.btn.apply -text "Apply" -command $apply_cmd button $wi.btn.def -text "Defaults" -command \ "popupServicesConfigDefaults $wi $node {$types} {$captions} {$groups}" button $wi.btn.cancel -text "Cancel" -command $cancel_cmd set buttons [list $wi.btn.apply $wi.btn.cancel] if { $session == "" } { set buttons [linsert $buttons 1 $wi.btn.def] } foreach b $buttons { pack $b -side left -padx 4 -pady 4 } pack $wi.btn -side bottom bind $wi <Key-Return> $apply_cmd bind $wi <Key-Escape> $cancel_cmd } # # Save the selection of activated services with the node or in the g_node_types # array when configuring node type defaults. # proc popupServicesConfigApply { wi channel node session } { set vals [getSelectedServices] # save default services for a node type into the g_node_types array if { $session != "" } { global g_node_types g_node_type_services_hint set type $g_node_type_services_hint set idx [getNodeTypeIndex $type] if { $idx < 0 } { puts "warning: skipping unknown node type $type" } else { set typedata $g_node_types($idx) set typedata [lreplace $typedata 3 3 $vals] array set g_node_types [list $idx $typedata] } # save the services configured for a specific node } else { setNodeServices $node $vals } destroy $wi } # load the default set of services for this node type proc popupServicesConfigDefaults { wi node types captions groups } { set type [getNodeModel $node] set defaults [getNodeTypeServices $type] for { set n 0 } { $n < [llength $types] } { incr n } { set groupinfo [popupCapabilityConfigGroup $groups [expr {$n + 1}]] set gn [lindex $groupinfo 0] set val 0 set valname [lindex $captions $n] if { [lsearch $defaults $valname] != -1 } { set val 1 } global $wi.vals.$gn.item$n.entval set $wi.vals.$gn.item$n.entval $val } } # # Popup a service customization dialog for a given service on a node # The customize/edit button next to a service has been pressed # proc customizeService { wi node service btn } { global g_sent_nodelink_definitions global plugin_img_add plugin_img_del plugin_img_folder global plugin_img_open plugin_img_save global eventtypes set selected [getSelectedServices] # if service is not enabled, enable it here if { [lsearch -exact $selected $service] == -1 } { set i [string last ".custom" $btn] set entval [string replace $btn $i end ".entval"] global $entval set $entval 1 lappend selected $service } # inform the CORE services about all nodes and links, so it can build # custom configurations for services if { $g_sent_nodelink_definitions == 0 } { set g_sent_nodelink_definitions 1 set sock [lindex [getEmulPlugin $node] 2] sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0 sendNodeLinkDefinitions $sock } set w .popupServicesCustomize catch {destroy $w} toplevel $w wm transient $w .popupServicesConfig wm title $w "$service on node [getNodeName $node] ($node)" ttk::frame $w.top ttk::label $w.top.lab -text "$service service" ttk::frame $w.top.meta ttk::label $w.top.meta.lab -text "Meta-data" ttk::entry $w.top.meta.ent -width 40 pack $w.top.lab -side top pack $w.top.meta.lab -side left -padx 4 -pady 4 pack $w.top.meta.ent -fill x -side left -padx 4 -pady 4 pack $w.top.meta -side top pack $w.top -side top -padx 4 -pady 4 ttk::notebook $w.note pack $w.note -fill both -expand true -padx 4 -pady 4 ttk::notebook::enableTraversal $w.note set enableapplycmd "$w.btn.apply configure -state normal" ### Custom ### # services may define custom popup configuration dialogs invoked here set custom_popup "popupServiceConfig_$service" if { [info commands $custom_popup] == $custom_popup } { $custom_popup $wi $w $node $service $btn } ### Files ### ttk::frame $w.note.files set fileshelp "Config files and scripts that are generated for this" set fileshelp "$fileshelp service." ttk::label $w.note.files.help -text $fileshelp pack $w.note.files.help -side top -anchor w -padx 4 -pady 4 $w.note add $w.note.files -text "Files" -underline 0 ttk::frame $w.note.files.name ttk::label $w.note.files.name.lab -text "File name:" set combo $w.note.files.name.combo ttk::combobox $combo -width 30 set helpercmd "customizeServiceFileHelper $w" ttk::button $w.note.files.name.add -image $plugin_img_add \ -command "listboxAddDelHelper add $combo $combo true; $helpercmd false" ttk::button $w.note.files.name.del -image $plugin_img_del \ -command "listboxAddDelHelper del $combo $combo true; $helpercmd true" pack $w.note.files.name.lab -side left pack $w.note.files.name.combo -side left -fill x -expand true foreach c [list add del] { pack $w.note.files.name.$c -side left } pack $w.note.files.name -side top -anchor w -padx 4 -pady 4 -fill x # copy source file global g_service_configs_opt set g_service_configs_opt "use" set f ${w}.note.files.copy ttk::frame $f ttk::radiobutton $f.opt -text "Copy this source file: " \ -value "copy" -variable g_service_configs_opt \ -command "customizeServiceFileOpt $w copy true" ttk::entry $f.name -width 45 ttk::button $f.btn -image $plugin_img_open -command \ "customizeServiceFileOpt $w copy true; fileButtonPopup $f.name {}" pack $f.opt $f.name $f.btn -side left -anchor w pack $f -side top -anchor w -padx 4 -pady 4 -fill x bind $f.btn <Button> "customizeServiceFileOpt $w copy true" # use file text set f ${w}.note.files.use ttk::frame $f ttk::radiobutton $f.opt -text "Use text below for file contents:" \ -value "use" -variable g_service_configs_opt \ -command "customizeServiceFileOpt $w use true" pack $f.opt -side left foreach c [list open save] { ttk::button $f.$c -image [set plugin_img_$c] -command \ "customizeServiceFileOpt $w use true; genericOpenSaveButtonPress $c $w.note.files.txt $w.note.files.name.combo" pack $f.$c -side left } pack $f -side top -anchor w -padx 4 -pady 4 -fill x text $w.note.files.txt -bg white -width 80 -height 10 \ -yscrollcommand "$w.note.files.scroll set" -undo 1 ttk::scrollbar $w.note.files.scroll -command "$w.note.files.txt yview" pack $w.note.files.txt -side left -fill both -expand true pack $w.note.files.scroll -side right -fill y bind $w.note.files.txt <KeyPress> $enableapplycmd global g_service_configs_tmp g_service_configs_last if { [info exists g_service_configs_tmp ] } { array unset g_service_configs_tmp } array set g_service_configs_tmp {} set g_service_configs_last "" bind $w.note.files.name.combo <<ComboboxSelected>> "$helpercmd true" bind $w.note.files.name.combo <KeyPress> $enableapplycmd ### Directories ### ttk::frame $w.note.dirs $w.note add $w.note.dirs -text "Directories" -underline 0 set helptxt "Directories required by this service that are" set helptxt "$helptxt unique for each node." ttk::label $w.note.dirs.help -text $helptxt pack $w.note.dirs.help -side top -anchor w -padx 4 -pady 4 ttk::treeview $w.note.dirs.tree -height 3 -selectmode browse $w.note.dirs.tree heading \#0 -text "Per-node directories" $w.note.dirs.tree insert {} end -id root -text "/" -open true \ -image $plugin_img_folder ttk::button $w.note.dirs.add -image $plugin_img_add \ -command "customizeServiceDirectoryHelper $w add; $enableapplycmd" ttk::button $w.note.dirs.del -image $plugin_img_del \ -command "customizeServiceDirectoryHelper $w del; $enableapplycmd" pack $w.note.dirs.tree -side top -fill both -expand true -padx 4 -pady 4 pack $w.note.dirs.del $w.note.dirs.add -side right ### Startup/shutdown ### ttk::frame $w.note.ss $w.note add $w.note.ss -text "Startup/shutdown" -underline 0 global g_service_startup_index set g_service_startup_index 50 ttk::frame $w.note.ss.si ttk::label $w.note.ss.si.idxlab -text "Startup index:" ttk::entry $w.note.ss.si.idxval -width 5 \ -textvariable g_service_startup_index ttk::scale $w.note.ss.si.idx -from 0 -to 100 -orient horizontal \ -variable g_service_startup_index \ -command "$enableapplycmd; scaleresolution 1 g_service_startup_index" pack $w.note.ss.si.idxlab $w.note.ss.si.idxval -side left -padx 4 -pady 4 pack $w.note.ss.si.idx -side left -expand true -fill x -padx 4 -pady 4 pack $w.note.ss.si -side top -padx 4 -pady 4 -fill x global g_service_startup_time set g_service_startup_time "" ttk::frame $w.note.ss.st ttk::label $w.note.ss.st.timelab -text "Start time:" ttk::entry $w.note.ss.st.timeval -width 5 \ -textvariable g_service_startup_time set txt "(seconds after runtime; leave empty for default)" ttk::label $w.note.ss.st.help -text $txt pack $w.note.ss.st.timelab $w.note.ss.st.timeval $w.note.ss.st.help \ -side left -padx 4 -pady 4 pack $w.note.ss.st -side top -padx 4 -pady 4 -fill x bind $w.note.ss.st.timeval <KeyPress> $enableapplycmd set captions "Startup Shutdown Validate" foreach c "up down val" { set fr $w.note.ss set caption [lindex $captions 0] set captions [lreplace $captions 0 0] entrylistbox $fr $c "$caption Commands" $enableapplycmd } set closecmd "destroy $w; setCustomButtonColor $btn $node $service false" ttk::frame $w.btn global g_customize_service_diff_only set g_customize_service_diff_only 1 ttk::checkbutton $w.btn.diff -variable g_customize_service_diff_only \ -text "only store values that have changed from their defaults" ttk::button $w.btn.apply -text "Apply" -state disabled \ -command "customizeServiceApply $w $node $service; $closecmd" ttk::button $w.btn.reset -text "Defaults" \ -command "customizeServiceReset $w $node $service {$selected}; $w.btn.close configure -text Close" ttk::button $w.btn.copy -text "Copy..." \ -command "customizeServiceCopy $node" ttk::button $w.btn.close -text "Cancel" -command $closecmd pack $w.btn.diff -side top pack $w.btn.apply $w.btn.reset $w.btn.copy $w.btn.close -side left pack $w.btn -side top -padx 4 -pady 4 # populate dialog values customizeServiceRefresh $service "$w $node {$selected}" } # popup dialog with tree view for copying customized service configuration # parameters from other nodes proc customizeServiceCopy { cnode } { global node_list plugin_img_edit plugin_img_open set w .popupServicesCopy catch {destroy $w} toplevel $w wm transient $w .popupServicesCustomize wm title $w "Copy services to node [getNodeName $cnode] ($cnode)" ttk::frame $w.nodes ttk::treeview $w.nodes.tree -height 3 -selectmode extended $w.nodes.tree heading \#0 -text "Service configuration items" pack $w.nodes.tree -side top -fill both -expand true -padx 4 -pady 4 pack $w.nodes -side top -anchor w -fill both -expand true ttk::frame $w.btn ttk::button $w.btn.apply -text "Copy" \ -command "customizeServiceCopyApply $w $cnode" ttk::button $w.btn.view -text "View" \ -command "customizeServiceCopyView $w $cnode" ttk::button $w.btn.close -text "Cancel" -command "destroy $w" pack $w.btn.apply $w.btn.view $w.btn.close -side left pack $w.btn -side top -padx 4 -pady 4 set tree $w.nodes.tree foreach node $node_list { if { $node == $cnode } { continue } set customCfgList [getCustomConfig $node] foreach element $customCfgList { set id [getConfig $element "custom-config-id"] set parts [split $id :] if { [lindex $parts 0] != "service" } { continue } set s [lindex $parts 1] # insert node into tree if { ![$tree exists "$node"] } { set img [getCustomImage $node] if { $img == "" } { set model [getNodeModel $node] set img [getNodeTypeImage $model normal] } set img [file tail $img].5 global [set img] set img [set [set img]] $tree insert {} end -id "$node" -text $node -open true \ -image $img } # insert service name if { ![$tree exists "$node:$s"] } { $tree insert $node end -id "$node:$s" -text $s -open true } # insert service elements if { [llength $parts] == 3 } { set f [lindex $parts 2] $tree insert "$node:$s" end -id "$node:$s:$f" -text $f \ -image $plugin_img_open } else { set cfg [getConfig $element "config"] foreach c $cfg { if { [lindex [split $c =] 0] == "files" } { continue } $tree insert "$node:$s" end -id "$node:$s:$c" -text $c \ -image $plugin_img_edit } } } ;# end foreach element } } # copy selected service configuration items from other nodes to the current # customize dialog proc customizeServiceCopyApply { w node } { global g_service_configs_tmp g_service_configs_last global g_service_startup_index g_service_startup_time set tgt .popupServicesCustomize set tree $w.nodes.tree set sel [$tree selection] destroy $w foreach s $sel { set parts [split $s :] set node [lindex $parts 0]; set service [lindex $parts 1] set item [lindex $parts 2] # customized file set f [getCustomService $node "$service:$item"] if { $f != "" } { set filedata [join $f "\n"] array set g_service_configs_tmp [list $item $filedata] set files [$tgt.note.files.name.combo cget -values] if { [lsearch $files $item] < 0 } { lappend files $item $tgt.note.files.name.combo configure -values $files } if { $g_service_configs_last == $item } { customizeServiceFileDataSet $tgt $filedata } # customized parameters } else { set kv [splitKeyValue $item] set key [lindex $kv 0] set value [lindex $kv 1] switch -exact -- $key { meta { $tgt.top.meta.ent delete 0 end $tgt.top.meta.ent insert end $value } dirs { foreach dir [tupleStringToList $value] { set dir [string range $dir 1 end] treeviewInsert $tgt.note.dirs.tree root [split $dir "/"] } } startidx { set g_service_startup_index $value } cmdup - cmddown - cmdval { set name [string range $key 3 end] foreach cmd [tupleStringToList $value] { $tgt.note.ss.$name.cmds.list insert end $cmd } } starttime { set g_service_startup_time $value } default { puts "warning: didn't copy '$key'" } } } } } # view the customization for comparison with current node proc customizeServiceCopyView { w node } { set tree $w.nodes.tree set sel [$tree selection] destroy $w set fn "" set filedata "" foreach s $sel { set parts [split $s :] set node [lindex $parts 0]; set service [lindex $parts 1] set item [lindex $parts 2] # customized file set f [getCustomService $node "$service:$item"] if { $f != "" } { set fn [file join "/tmp" "services.tmp-$node-[file tail $item]"] set filedata [join $f "\n"] # customized parameters } else { set kv [splitKeyValue $item] set key [lindex $kv 0] set value [lindex $kv 1] set fn [file join "/tmp" "services.tmp-$node-$key"] set filedata $value } } if { $fn == "" } { return } if { [catch { set f [open $fn w] } e] } { puts "error opening file: $fn\n ($e)" return } puts $f $filedata close $f popupFileView $fn } # helper for add/delete directories from treeview proc customizeServiceDirectoryHelper { w cmd } { if { $cmd == "add" } { set dir [tk_chooseDirectory -mustexist false -initialdir "/" \ -parent $w -title "Add a per-node directory"] if { $dir == "" } { return } set dir [string range $dir 1 end] ;# chop off leading slash treeviewInsert $w.note.dirs.tree root [split $dir "/"] } elseif { $cmd == "del" } { set s [$w.note.dirs.tree selection] if { $s == "root" } { return } ;# may not delete root $w.note.dirs.tree delete $s ;# delete the current selection set parents [lreplace [split $s /] end end] # delete all parents of the selected node if they do not have children while {[llength $parents] > 1} { set parent [join $parents "/"] if { [llength [$w.note.dirs.tree children $parent]] == 0 } { $w.note.dirs.tree delete $parent } set parents [lreplace $parents end end] } } } # helper for switching files based on combo box selection proc customizeServiceFileHelper { w clear } { global g_service_configs_tmp g_service_configs_last # save old config to array set cfg [customizeServiceFileDataGet $w] if { [info exists g_service_configs_last] && \ $g_service_configs_last != "" } { array set g_service_configs_tmp [list $g_service_configs_last $cfg] } set cfgname [$w.note.files.name.combo get] set g_service_configs_last $cfgname # populate with new config if { $clear } { $w.note.files.txt delete 0.0 end $w.note.files.copy.name delete 0 end customizeServiceFileOpt $w "use" false } if { ![info exists g_service_configs_tmp($cfgname)] } { array set g_service_configs_tmp [list $cfgname ""] } else { set cfg $g_service_configs_tmp($cfgname) customizeServiceFileDataSet $w $cfg } } # helper to insert file contents into the text controls proc customizeServiceFileDataSet { w cfg } { $w.note.files.txt delete 0.0 end $w.note.files.copy.name delete 0 end if { [string range $cfg 0 6] == "file://" } { customizeServiceFileOpt $w "copy" false set cfglines [split $cfg "\n"] set cfg [lindex $cfglines 0] ;# truncate any other lines $w.note.files.copy.name insert 0 [string range $cfg 7 end] } else { customizeServiceFileOpt $w "use" false $w.note.files.txt insert 0.0 $cfg } } # helper to get file contents from the text controls proc customizeServiceFileDataGet { w } { global g_service_configs_opt if { $g_service_configs_opt == "use" } { set cfg [$w.note.files.txt get 0.0 end-1c] } elseif { $g_service_configs_opt == "copy" } { set cfg [$w.note.files.copy.name get] set cfg "file://$cfg" } return $cfg } # helper to set option mode to use/copy proc customizeServiceFileOpt { w mode enable_apply } { global g_service_configs_opt set g_service_configs_opt $mode if { $mode == "copy" } { $w.note.files.txt configure -state disabled -bg gray $w.note.files.copy.name configure -state normal } else { $w.note.files.txt configure -state normal -bg white $w.note.files.copy.name configure -state disabled } if { $enable_apply } { $w.btn.apply configure -state normal } } # create a listbox with a text entry above it, with add/delete buttons and # a scrollbar proc entrylistbox { fr name caption extracmd} { global plugin_img_add plugin_img_del set c $name ttk::labelframe $fr.$name -text "$caption" ttk::frame $fr.$c.edit ttk::entry $fr.$c.edit.cmd -width 40 ttk::button $fr.$c.edit.add -image $plugin_img_add \ -command "listboxAddDelHelper add $fr.$c.edit.cmd $fr.$c.cmds.list false; $extracmd" ttk::button $fr.$c.edit.del -image $plugin_img_del \ -command "listboxAddDelHelper del $fr.$c.edit.cmd $fr.$c.cmds.list false; $extracmd" pack $fr.$c.edit.cmd -side left -fill x -expand true pack $fr.$c.edit.add $fr.$c.edit.del -side left ttk::frame $fr.$c.cmds listbox $fr.$c.cmds.list -height 5 -width 50 \ -yscrollcommand "$fr.$c.cmds.scroll set" -exportselection 0 bind $fr.$c.cmds.list <<ListboxSelect>> "listboxSelect $fr.$c.cmds.list $fr.$c.edit.cmd" ttk::scrollbar $fr.$c.cmds.scroll -command "$fr.$c.cmds.list yview" pack $fr.$c.cmds.list -side left -fill both -expand true pack $fr.$c.cmds.scroll -side left -fill y pack $fr.$c.edit $fr.$c.cmds -side top -anchor w -fill x pack $fr.$c -side top -fill x -expand true } # # color the customize/edit button adjacent to each service checkbutton # proc setCustomButtonColor { btn node service needcustom } { set color lightgray ;# default button background color # color button yellow if enabled and customization is needed if { $needcustom } { # button $wi.vals.$gn.item$n.custom / value $wi.vals.$gn.item$n.entval set i [string last ".custom" $btn] set entval [string replace $btn $i end ".entval"] global $entval if { [set $entval] } { set color yellow } } if { [getCustomService $node $service] != "" } { set color green } $btn configure -bg $color } proc scaleresolution { res var val } { set factor [expr {1 / $res}] set val [expr {int($val * $factor) / $factor}] global $var set $var $val return $val } # return a list of services that have been selected (checkbox is checked) proc getSelectedServices { } { global g_service_ctls set selected {} foreach c $g_service_ctls { global $c set service [$c cget -text] set var [$c cget -variable] global $var if { [set $var] == 1 } { lappend selected $service } } return $selected } # send a config request message with the opaque field set to query for all # service parameters; the opaque field is "service:s5,s2,s3,s4", where service # s5 is being configured (parseConfMessage will invoke customizeServiceValues) proc customizeServiceRefresh { var args } { set args [lindex $args 0] set w [lindex $args 0] set node [lindex $args 1] set services [lindex $args 2] # move service to the front of the list of services set i [lsearch $services $var] if { $i < 0 } { puts "error: service $var not found in '$services'" return } elseif { $i > 0 } { set services [lreplace $services $i $i] set services [linsert $services 0 $var] } # request service parameters from daemon set svcstr [join $services ","] set sock [lindex [getEmulPlugin $node] 2] sendConfRequestMessage $sock $node services 0x1 -1 "service:$svcstr" update } # this returns a list of values for the service s on node if a custom service # configuration exists proc getCustomService { node s } { set values [getCustomConfigByID $node "service:$s"] return $values } # this helper is invoked upon receiving the reply to the message sent from # customizeServiceRefresh; it populates the dialog box fields proc customizeServiceValues { node values services } { global plugin_img_folder set service [lindex $services 0] set w .popupServicesCustomize if { ![winfo exists $w] } { # apply config update without dialog box # this occurs when loading from XML or reconnecting to a session setCustomConfig $node "service:$service" $service $values 0 return } global g_customize_service_values_orig set g_customize_service_values_orig $values # merge any custom values with defaults from message set custom_values [getCustomService $node $service] set i 0 set has_keys [hasKeyValues $custom_values] foreach val $custom_values { if { $has_keys } { set kv [splitKeyValue $val] set key [lindex $kv 0]; set value [lindex $kv 1] set values [setServiceValuesItem $values $key $value] } else { set values [lreplace $values $i $i $val] } incr i } # populate meta-data set meta [getServiceValuesItem $values "meta" 6] $w.top.meta.ent delete 0 end $w.top.meta.ent insert end $meta # populate Files tab set files [tupleStringToList [getServiceValuesItem $values "files" 1]] set chosenfile [lindex $files 0] ;# auto-display first file from list $w.note.files.name.combo configure -values $files $w.note.files.name.combo delete 0 end if { $chosenfile != "" } { $w.note.files.name.combo insert 0 $chosenfile } global g_service_configs_last set g_service_configs_last $chosenfile # file data foreach f $files { set filedata [join [getCustomService $node "$service:$f"] "\n"] if { $filedata != "" } { # use file contents from existing config customizeServiceFile $node $f "service:$service" $filedata false } elseif { $f != "" } { # request the file contents set svcstr [join $services ","] set sock [lindex [getEmulPlugin "*"] 2] set opaque "service:$svcstr:$f" # this causes customizeServiceFile to be invoked upon reply sendConfRequestMessage $sock $node services 0x1 -1 $opaque } } # populate Directories tab set dirs [tupleStringToList [getServiceValuesItem $values "dirs" 0]] $w.note.dirs.tree delete root $w.note.dirs.tree insert {} end -id root -text "/" -open true \ -image $plugin_img_folder foreach dir $dirs { set dir [string range $dir 1 end] ;# chop off leading slash treeviewInsert $w.note.dirs.tree root [split $dir "/"] } # populate Startup/shutdown tab set idx [getServiceValuesItem $values "startidx" 2] global g_service_startup_index set g_service_startup_index $idx set valuesidx 3 foreach c "up down val" { set fr $w.note.ss $fr.$c.edit.cmd delete 0 end $fr.$c.cmds.list delete 0 end set value [getServiceValuesItem $values "cmd$c" $valuesidx] foreach cmd [tupleStringToList $value] { if { $cmd != "" } { $fr.$c.cmds.list insert end $cmd } } incr valuesidx } set starttime [getServiceValuesItem $values "starttime" 6] global g_service_startup_time set g_service_startup_time $starttime # populate any custom service tab set service [lindex $services 0] set custom_vals_callback "popupServiceConfig_${service}_vals" if { [info commands $custom_vals_callback] == $custom_vals_callback } { $custom_vals_callback $node $values $services $w } $w.btn.apply configure -state disabled } # extract items from a list of values # old-style values has an ordered list of values; idx determines key # new-style values is a list of key=value pairs proc getServiceValuesItem { values key idx } { # determine how to handle values set has_keys [hasKeyValues $values] if { $has_keys } { return [getKeyValue $key $values ""] } else { return [lindex $values $idx] } } # replace a "key=value" pair in a list, returning the list proc setServiceValuesItem { values key value } { set i 0 foreach v $values { set k [lindex [splitKeyValue $v] 0] if { $k == $key } { break } incr i } if { $i == [llength $values] } { puts "key not found '$key' in service values" return $values } return [lreplace $values $i $i "$key=$value"] } # this helper is invoked upon receiving a File Message in reply to the Config # Message sent from customizeServiceRefresh; it populates the config file entry proc customizeServiceFile { node name type data generated} { global g_service_configs_tmp g_service_configs_tmp_orig global g_service_configs_last set w .popupServicesCustomize if { ![winfo exists $w] } { # apply file config update without dialog box # this occurs when loading from XML or reconnecting to a session # type should be e.g. "service:zebra" set data [split $data "\n"] setCustomConfig $node "$type:$name" $name $data 0 return } # store file data in array array set g_service_configs_tmp [list $name $data] if { $generated } { array set g_service_configs_tmp_orig [list $name $data] } else { array set g_service_configs_tmp_orig [list $name ""] } # display file if currently selected if { $g_service_configs_last == $name } { customizeServiceFileDataSet $w $data } # invoke any custom service callback set service [string range $type 8 end] ;# assume already checked "service:" set custom_file_callback "popupServiceConfig_${service}_file" if { [info commands $custom_file_callback] == $custom_file_callback } { $custom_file_callback $node $name $data $w } } # helper to recursively add a directory path to a treeview proc treeviewInsert { tree parent items } { # pop first item set item [lindex $items 0] set items [lreplace $items 0 0] set img [$tree item $parent -image] ;# adopt icon from parent if { ![$tree exists "$parent/$item"] } { $tree insert $parent end -id "$parent/$item" -text $item -open true \ -image $img } if { [llength $items] > 0 } { treeviewInsert $tree "$parent/$item" $items } } # return all children that are leaf nodes in a tree proc treeviewLeaves { tree parent } { set leaves "" set children [$tree children $parent] if { [llength $children] == 0 } { return $parent } foreach child $children { set leaves [concat $leaves [treeviewLeaves $tree $child]] } return $leaves } # apply button pressed on customizeService dialog proc customizeServiceApply { w node service } { global g_customize_service_diff_only catch { $w.btn.apply configure -state disabled } set values "" # Directories set dirs "" set dirstmp [treeviewLeaves $w.note.dirs.tree root] foreach dir $dirstmp { set dir [string replace $dir 0 3] ;# chop off "root" prefix if { $dir == "" } { continue } lappend dirs $dir } lappend values "dirs=[listToTupleString $dirs]" # Files set files [$w.note.files.name.combo cget -values] lappend values "files=[listToTupleString $files]" # Startup index global g_service_startup_index lappend values "startidx=$g_service_startup_index" # Startup/shutdown commands foreach c "up down val" { set cmds [$w.note.ss.$c.cmds.list get 0 end] lappend values "cmd$c=[listToTupleString $cmds]" } # meta lappend values "meta=[$w.top.meta.ent get]" # start time global g_service_startup_time lappend values "starttime=$g_service_startup_time" # remove any existing config files for this service # this prevents duplicates when files are renamed/deleted set cfgs [getCustomConfig $node] foreach cfg $cfgs { set cid [lindex [lsearch -inline $cfg "custom-config-id *"] 1] set len [expr {[string length "service:$service:"] - 1}] if { [string range $cid 0 $len] == "service:$service:" } { setCustomConfig $node $cid "" "" 1 } } # save config files (that have changed) set trimmed_files {} global g_service_configs_tmp g_service_configs_tmp_orig global g_service_configs_last set cfg [customizeServiceFileDataGet $w] array set g_service_configs_tmp [list $g_service_configs_last $cfg] foreach cfgname $files { if { ![info exists g_service_configs_tmp($cfgname)] } { puts "missing config for file '$cfgname'" continue } if { [info exists g_service_configs_tmp_orig($cfgname)] } { if { $g_service_configs_tmp_orig($cfgname) == \ $g_service_configs_tmp($cfgname) } { # file has not changed if { $g_customize_service_diff_only } { continue } } } set cfg [split $g_service_configs_tmp($cfgname) "\n"] setCustomConfig $node "service:$service:$cfgname" $cfgname $cfg 0 lappend trimmed_files $cfgname } # store only values that have changed from the defaults set trimmed {} global g_customize_service_values_orig for {set i 0} {$i < [llength $values]} {incr i} { set value_orig [lindex $g_customize_service_values_orig $i] set value_orig [tupleStringToList $value_orig] set value_new [tupleStringToList [lindex $values $i]] if { $i == 1 } { # when a file has changed, store all filenames whether or not # the name(s) have changed if { [llength $trimmed_files] > 0 } { lappend trimmed [lindex $values 1] } continue } if {$value_orig != $value_new} { lappend trimmed [lindex $values $i] } } if { $g_customize_service_diff_only } { set values $trimmed } unset g_customize_service_values_orig # save values without config file setCustomConfig $node "service:$service" $service $values 0 array unset g_service_configs_tmp array unset g_service_configs_tmp_orig unset g_service_configs_last # may want to apply here, if some config validation is implemented or # runtime applying of service customization # otherwise this is not necessary due to config being sent upon startup # also more logic would be needed for using the reset button #set sock [lindex [getEmulPlugin $node] 2] #set types [string repeat "10 " [llength $values]] #sendConfReplyMessage $sock $node services $types $values "service:$service" } # # reset button is pressed on customizeService dialog # proc customizeServiceReset { w node service services } { set cfgnames [$w.note.files.name.combo cget -values] setCustomConfig $node "service:$service" "" "" 1 foreach cfgname $cfgnames { setCustomConfig $node "service:$service:$cfgname" "" "" 1 } customizeServiceRefresh $service [list $w $node $services] } # check for old service configs in all nodes proc upgradeConfigServices {} { global node_list foreach node $node_list { upgradeNodeConfigService $node upgradeCustomPostConfigCommands $node } } # provide backwards-compatibility with changes to services fields here proc upgradeNodeConfigService { node } { set OLD_NUM_FIELDS 7 set cfgs [getCustomConfig $node] foreach cfg $cfgs { set cid [lindex [lsearch -inline $cfg "custom-config-id service:*"] 1] # skip configs that are not a service definition ("service:name") if { [llength [split $cid :]] != 2 } { continue } set values [getConfig $cfg config] if { [llength $values] != [expr {$OLD_NUM_FIELDS-1}] } { continue } # update from 6 service fields to 7 when introducing validate commands set service [lindex [split $cid :] 1] #puts -nonewline "note: updating service $service on $node with empty " #puts "validation commands" set values [linsert $values end-1 {}] setCustomConfig $node "service:$service" $service $values 0 } } proc upgradeCustomPostConfigCommands { node } { set cfg [getCustomPostConfigCommands $node] setCustomPostConfigCommands $node {} if { $cfg == "" } { return } set cfgname "custom-post-config-commands.sh" set values "{files=('$cfgname', )} startidx=35 {cmdup=('sh $cfgname', )}" setCustomConfig $node "service:UserDefined" "UserDefined" $values 0 setCustomConfig $node "service:UserDefined:$cfgname" $cfgname $cfg 0 set services [getNodeServices $node true] lappend services "UserDefined" setNodeServices $node $services puts "adding user-defined custom-post-config-commands.sh service for $node" } # populate services menu when right-clicking on a node at runtime proc addServicesRightClickMenu { m node } { $m add cascade -label "Services" -menu $m.services set i 0 set services [getNodeServices $node true] foreach s $services { set childmenu $m.services.s$i incr i destroy $childmenu menu $childmenu -tearoff 0 $m.services add cascade -label $s -menu $childmenu foreach cmd "start stop restart validate" { $childmenu add command -label $cmd \ -command "sendServiceCmd $node $s $cmd" } } } proc sendServiceCmd { node service cmd } { global eventtypes set plugin [lindex [getEmulPlugin "*"] 0] set sock [pluginConnect $plugin connect true] if { $cmd == "validate" } { set cmd "pause" } set type $eventtypes(event_$cmd) set nodenum [string range $node 1 end] set name "service:$service" set data "" sendEventMessage $sock $type $nodenum $name $data 0 }