ad93e7f292
enables filenames containing spaces (Boeing r1834)
1341 lines
40 KiB
Tcl
1341 lines
40 KiB
Tcl
#
|
|
# Copyright 2005-2014 the Boeing Company.
|
|
# See the LICENSE file included in this distribution.
|
|
#
|
|
|
|
set g_imageFileTypes {{"images" {.gif}} {"images" {.jpg}} {"images" {.png}}
|
|
{"images" {.bmp}} {"images" {.pcx}} {"images" {.tga}}
|
|
{"images" {.tif}} {"images" {.ps}} {"images" {.ppm}}
|
|
{"images" {.xbm}} {"All files" {*} }}
|
|
|
|
global execMode
|
|
if { $execMode == "interactive"} {
|
|
if { [catch { package require Img }] } {
|
|
puts "warning: Tcl/Tk Img package not found"
|
|
puts " Thumbnails and other image types (JPG, PNG, etc.) will not be supported."
|
|
puts " Please install it with:"
|
|
puts " yum install tkimg (RedHat/Fedora)"
|
|
puts " sudo apt-get install libtk-img (Debian/Ubuntu)"
|
|
puts " pkg_add -r libimg (FreeBSD)\n"
|
|
set g_imageFileTypes {{"images" {.gif}} {"All files" {*} }}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Set the global systype variable using checkOS, if it hasn't been set yet
|
|
proc setSystype { } {
|
|
global systype
|
|
if { [info exists systype] } { return } ;# global already set
|
|
set systype [lindex [checkOS] 0]
|
|
}
|
|
|
|
#
|
|
# Return a string identifying the operating system,
|
|
# and a string of verbose OS info (for user display)
|
|
proc checkOS {} {
|
|
global tk_patchLevel tk_library
|
|
set ret {}
|
|
|
|
set tcl_ver [info patchlevel]
|
|
set tcl_libpath [info library]
|
|
if { [info exists tk_patchLevel] } {
|
|
set tk_ver $tk_patchLevel
|
|
set tk_libpath $tk_library
|
|
} else {
|
|
set tk_ver 0
|
|
set tk_libpath 0
|
|
}
|
|
set os_name [exec uname]
|
|
set os_ver [exec uname -r]
|
|
|
|
set machine [exec uname -m]
|
|
set kernel [exec uname -v]
|
|
|
|
set x11 0
|
|
catch { set x11 [winfo server .c] }
|
|
|
|
set os_ident "$os_name $os_ver"
|
|
lappend ret $os_ident
|
|
|
|
set os_verbose "$os_ident on $machine\nkernel build $kernel\nTCL version $tcl_ver ($tcl_libpath)\nTk version $tk_ver ($tk_libpath)\nX11 server $x11\n"
|
|
|
|
lappend ret $os_verbose
|
|
return $ret
|
|
}
|
|
|
|
# search a .imn topology file for old statements, and upgrade them
|
|
proc upgradeOldConfig { cfg_ref } {
|
|
global execMode
|
|
# modify config directly!
|
|
upvar 1 $cfg_ref cfg
|
|
|
|
set msg "config line updated:"
|
|
|
|
# s/circle/annotation/
|
|
set match {circle circle([0-9]+) (.)(.)}
|
|
set subs {annotation a\1 \2\3 type oval\3}
|
|
set r [regsub -all $match $cfg $subs cfg]
|
|
if { $r > 0 } { puts "$msg circle -> oval annotation ($r)" }
|
|
|
|
# s/label/annotation/
|
|
set match {label lab([0-9]+) (.)(.)}
|
|
set subs {annotation a1\1 \2\3 type text\3}
|
|
set r2 [regsub -all $match $cfg $subs cfg]
|
|
if { $r2 > 0 } { puts "$msg label -> text annotation ($r2)" }
|
|
|
|
# s/size/fontsize/
|
|
set r3 0
|
|
if { $r2 > 0 } {
|
|
set match {size ([0-9]+)}
|
|
set subs {fontsize \1}
|
|
set r3 [regsub -all $match $cfg $subs cfg]
|
|
if { $r3 > 0 } { puts "$msg size -> fontsize ($r3)" }
|
|
}
|
|
|
|
# s/model quagga/model router/
|
|
set match { model quagga}
|
|
set subs { model router}
|
|
set r4 [regsub -all $match $cfg $subs cfg]
|
|
if { $r4 > 0 } { puts "$msg model quagga -> model router ($r4)" }
|
|
|
|
# s/type pc/type router\n model PC/
|
|
set match { type pc}
|
|
set subs " type router\n model PC"
|
|
set r5 [regsub -all $match $cfg $subs cfg]
|
|
if { $r5 > 0 } { puts "$msg type pc -> model pc ($r5)" }
|
|
|
|
# s/type host/type router\n model host/
|
|
set match { type host}
|
|
set subs " type router\n model host"
|
|
set r6 [regsub -all $match $cfg $subs cfg]
|
|
if { $r6 > 0 } { puts "$msg type host -> model host ($r6)" }
|
|
|
|
incr r [expr {$r2 + $r3 + $r4 + $r5 + $r6}]
|
|
#puts "$cfg"
|
|
set choice ""
|
|
if { $execMode == "interactive" && $r > 0 } {
|
|
puts "$msg $r substitutions made"
|
|
set choice [tk_messageBox -type yesno -icon question -title "CORE" \
|
|
-message "This configuration file contains old syntax, \
|
|
would you like to upgrade it to the new format?"]
|
|
}
|
|
return $choice
|
|
}
|
|
|
|
# renumber n0 if it exists
|
|
proc upgradeConfigRemoveNode0 { } {
|
|
global node_list
|
|
set i [lsearch $node_list n0]
|
|
if { $i < 0 } { return }
|
|
|
|
set new [newObjectId node]
|
|
puts "changing node n0 to $new"
|
|
global $new n0
|
|
set $new $n0
|
|
renumberNodesIfcs n0 $new
|
|
set node_list [lreplace $node_list $i $i]
|
|
lappend node_list $new
|
|
}
|
|
|
|
# update network-config blocks from old imn file configs
|
|
proc upgradeNetworkConfigToServices { } {
|
|
global node_list
|
|
|
|
# XXX this is a hack to populate zebra service values
|
|
# really, we should query for service config items...
|
|
set vals "{('/usr/local/etc/quagga', '/var/run/quagga')}"
|
|
set vals "$vals {('/usr/local/etc/quagga/Quagga.conf', 'quaggaboot.sh', '/usr/local/etc/quagga/vtysh.conf')}"
|
|
set vals "$vals 35 {('sh quaggaboot.sh zebra',)} {('killall zebra',)} {} {}"
|
|
set statvals "{} {('staticroutes.sh', )} 35 {('sh staticroutes.sh', )}"
|
|
set statvals "$statvals {} {} {}"
|
|
|
|
foreach node $node_list {
|
|
#
|
|
# build Quagga services config from network-config block
|
|
#
|
|
set ospfv2 [netconfFetchSection $node "router ospf"]
|
|
set ospfv3 [netconfFetchSection $node "router ospf6"]
|
|
set rip [netconfFetchSection $node "router rip"]
|
|
set ripng [netconfFetchSection $node "router ripng"]
|
|
set bgp [netconfFetchSection $node "router bgp"]
|
|
if { $ospfv2 != "" || $ospfv3 != "" || $rip != "" || $ripng != "" } {
|
|
set cfg ""
|
|
set services "zebra vtysh IPForward"
|
|
foreach ifc [ifcList $node] {
|
|
lappend cfg "interface $ifc"
|
|
set ifccfg [netconfFetchSection $node "interface $ifc"]
|
|
set cfg "$cfg $ifccfg"
|
|
lappend cfg "!"
|
|
}
|
|
if { $ospfv2 != "" } {
|
|
netconfClearSection $node "router ospf"
|
|
lappend cfg "router ospf"; set cfg "$cfg $ospfv2 !"
|
|
lappend services "OSPFv2"
|
|
}
|
|
if { $ospfv3 != "" } {
|
|
netconfClearSection $node "router ospf6"
|
|
lappend cfg "router ospf6"; set cfg "$cfg $ospfv3 !"
|
|
lappend services "OSPFv3"
|
|
}
|
|
if { $rip != "" } {
|
|
netconfClearSection $node "router rip"
|
|
lappend cfg "router rip"; set cfg "$cfg $rip !"
|
|
lappend services "RIP"
|
|
}
|
|
if { $ripng != "" } {
|
|
netconfClearSection $node "router ripng"
|
|
lappend cfg "router ripng"; set cfg "$cfg $ripng !"
|
|
lappend services "RIPNG"
|
|
}
|
|
if { $bgp != "" } {
|
|
netconfClearSection $node "router bgp"
|
|
# AS number is lost here
|
|
lappend cfg "router bgp"; set cfg "$cfg $bgp !"
|
|
lappend services "BGP"
|
|
}
|
|
setCustomConfig $node "service:zebra" "zebra" $vals 0
|
|
set cfgname "/usr/local/etc/quagga/Quagga.conf"
|
|
setCustomConfig $node "service:zebra:$cfgname" $cfgname $cfg 0
|
|
set cfgname "/usr/local/etc/quagga/vtysh.conf"
|
|
setCustomConfig $node "service:zebra:$cfgname" $cfgname \
|
|
"service integrated-vtysh-config" 0
|
|
setNodeServices $node $services
|
|
puts "updating Quagga services on node $node"
|
|
} ;# end quagga services
|
|
#
|
|
# convert static model to router
|
|
#
|
|
if { [getNodeModel $node] == "static" } {
|
|
setNodeModel $node "router"
|
|
setNodeServices $node "IPForward"
|
|
puts "changing model static to router on node $node"
|
|
}
|
|
#
|
|
# convert static routes to a custom service
|
|
#
|
|
if { [getStatIPv4routes $node] != "" || \
|
|
[getStatIPv6routes $node] != "" } {
|
|
set cfg {}
|
|
lappend cfg "# custom static route service generated by util.tcl"
|
|
addStaticRoutesToConfig $node cfg
|
|
setStatIPv4routes $node ""; setStatIPv6routes $node "" ;# clear old
|
|
set cfgname "staticroutes.sh"
|
|
setCustomConfig $node "service:UserDefined" "UserDefined" \
|
|
$statvals 0
|
|
setCustomConfig $node "service:UserDefined:$cfgname" $cfgname $cfg 0
|
|
set services [getNodeServices $node true]
|
|
lappend services "UserDefined"
|
|
setNodeServices $node $services
|
|
puts "adding user-defined static routing service on node $node"
|
|
} ;# end static services
|
|
}
|
|
}
|
|
|
|
# get CPU usage from /proc/stat jiffies
|
|
proc getCPUUsage { } {
|
|
global lastcpu
|
|
|
|
if { [catch {set f [open "/proc/stat" r]} ] } {
|
|
return ""; # unable to open /proc/stat
|
|
}
|
|
|
|
array set cpu {}
|
|
|
|
while { [ gets $f line ] >= 0 } {
|
|
set cpun [lindex $line 0]
|
|
set user [lindex $line 1]; set nice [lindex $line 2]
|
|
set sys [lindex $line 3]; set idle [lindex $line 4]
|
|
if { [string range $cpun 0 2] != "cpu" } { continue }
|
|
set cpu($cpun) "$user $nice $sys $idle"
|
|
}
|
|
close $f
|
|
if { ![info exists cpu(cpu)] } { return "" }
|
|
|
|
set cpuusages ""
|
|
foreach cpun [lsort -dictionary [array names cpu]] {
|
|
if { ![info exists lastcpu($cpun)] } {
|
|
set lastcpu($cpun) "0 0 0 0"
|
|
}
|
|
set lu [lindex $lastcpu($cpun) 0]; set ln [lindex $lastcpu($cpun) 1]
|
|
set ls [lindex $lastcpu($cpun) 2]; set li [lindex $lastcpu($cpun) 3]
|
|
set u [lindex $cpu($cpun) 0]; set n [lindex $cpu($cpun) 1]
|
|
set s [lindex $cpu($cpun) 2]; set i [lindex $cpu($cpun) 3]
|
|
set lastcpu($cpun) "$u $n $s $i"
|
|
|
|
set usage_time [expr {($u-$lu) + ($n-$ln) + ($s-$ls)}]
|
|
set total_time [expr {$usage_time + ($i-$li)}]
|
|
if { $total_time <= 0 } {
|
|
set cpuusage "" ;# avoid div by zero
|
|
} else {
|
|
set cpuusage [expr { 100 * $usage_time / $total_time }]
|
|
}
|
|
lappend cpuusages $cpuusage
|
|
}
|
|
return $cpuusages
|
|
}
|
|
|
|
# Node selection dialog display given message 'msg' with initial node selection
|
|
# set to the 'initsel' list, and calls the callback using the selected nodes
|
|
proc popupSelectNodes { msg initsel callback } {
|
|
global node_list
|
|
|
|
set wi .nodeselect
|
|
catch {destroy $wi}
|
|
toplevel $wi -takefocus 1
|
|
wm resizable $wi 0 0
|
|
wm title $wi "Select Nodes"
|
|
grab $wi
|
|
|
|
frame $wi.nodes -borderwidth 4
|
|
|
|
if { $msg == "" } { set msg "Select one or more nodes:" }
|
|
label $wi.nodes.label -text $msg
|
|
frame $wi.nodes.fr
|
|
listbox $wi.nodes.fr.nodelist -width 40 \
|
|
-listvariable node_list -yscrollcommand "$wi.nodes.fr.scroll set" \
|
|
-activestyle dotbox -selectmode extended
|
|
scrollbar $wi.nodes.fr.scroll -command "$wi.nodes.fr.nodelist yview"
|
|
pack $wi.nodes.fr.nodelist -fill both -expand true -side left
|
|
pack $wi.nodes.fr.scroll -fill y -expand true -side left
|
|
pack $wi.nodes.label $wi.nodes.fr -side top -padx 4 -pady 4 \
|
|
-anchor w -fill both -expand true
|
|
pack $wi.nodes -fill both -expand true -side top
|
|
|
|
frame $wi.fbot -borderwidth 4
|
|
button $wi.fbot.apply -text "OK" \
|
|
-command "selectNodesHelper $wi {$callback}"
|
|
button $wi.fbot.cancel -text "Cancel" -command "destroy $wi"
|
|
pack $wi.fbot.cancel $wi.fbot.apply -side right -padx 4 -pady 4
|
|
pack $wi.fbot -side bottom
|
|
|
|
#set n [$wi.nodes.fr.from get $i]
|
|
#itemconfigure $i -foreground blue
|
|
set idx 0
|
|
foreach node $node_list {
|
|
foreach sel $initsel {
|
|
if { $node == $sel } {
|
|
$wi.nodes.fr.nodelist selection set $idx
|
|
break
|
|
}
|
|
}
|
|
incr idx
|
|
}
|
|
}
|
|
|
|
proc selectNodesHelper { wi callback } {
|
|
set selected_indices [$wi.nodes.fr.nodelist curselection]
|
|
set selected_nodes {}
|
|
foreach idx $selected_indices {
|
|
set node [$wi.nodes.fr.nodelist get $idx]
|
|
lappend selected_nodes $node
|
|
}
|
|
destroy $wi
|
|
lappend callback $selected_nodes
|
|
eval $callback
|
|
}
|
|
|
|
# Boeing: node renumbering dialog
|
|
proc popupRenumberNodes { } {
|
|
set wi .renumbernode
|
|
catch {destroy $wi}
|
|
toplevel $wi -takefocus 1
|
|
wm transient $wi .
|
|
wm resizable $wi 0 0
|
|
wm title $wi "Renumber Nodes"
|
|
grab $wi
|
|
|
|
frame $wi.nodes -borderwidth 4
|
|
|
|
frame $wi.nodes.left
|
|
label $wi.nodes.left.label -text "Change this node:"
|
|
frame $wi.nodes.left.fr
|
|
listbox $wi.nodes.left.fr.from -selectmode single -width 20 \
|
|
-listvariable node_list -yscrollcommand "$wi.nodes.left.fr.scroll set" \
|
|
-activestyle dotbox
|
|
scrollbar $wi.nodes.left.fr.scroll -command "$wi.nodes.left.fr.from yview"
|
|
pack $wi.nodes.left.fr.from $wi.nodes.left.fr.scroll -fill y -side left
|
|
pack $wi.nodes.left.label $wi.nodes.left.fr -side top -padx 4 -pady 4 \
|
|
-anchor w
|
|
pack $wi.nodes.left -side left
|
|
bind $wi.nodes.left.fr.from <<ListboxSelect>> "selectRenumberNodes $wi from"
|
|
|
|
frame $wi.nodes.right
|
|
label $wi.nodes.right.label -text "to this node:"
|
|
frame $wi.nodes.right.fr
|
|
listbox $wi.nodes.right.fr.to -selectmode single -width 20 \
|
|
-listvariable node_list -yscrollcommand "$wi.nodes.right.fr.scroll set"
|
|
scrollbar $wi.nodes.right.fr.scroll -command "$wi.nodes.right.fr.to yview"
|
|
pack $wi.nodes.right.fr.to $wi.nodes.right.fr.scroll -fill y -side left
|
|
pack $wi.nodes.right.label $wi.nodes.right.fr -side top -padx 4 -pady 4 \
|
|
-anchor w
|
|
pack $wi.nodes.right -side left
|
|
bind $wi.nodes.right.fr.to <<ListboxSelect>> "selectRenumberNodes $wi to"
|
|
|
|
pack $wi.nodes -side top
|
|
|
|
frame $wi.fbot -borderwidth 4
|
|
button $wi.fbot.apply -text "Renumber Node" -command "renumberNodes $wi" \
|
|
-state disabled
|
|
button $wi.fbot.cancel -text "Close" -command "destroy $wi"
|
|
pack $wi.fbot.cancel $wi.fbot.apply -side right -padx 4 -pady 4
|
|
pack $wi.fbot -side bottom
|
|
|
|
#set n [$wi.nodes.left.fr.from get $i]
|
|
#itemconfigure $i -foreground blue
|
|
}
|
|
|
|
# helper for highlighting nodes in the node renumbering list boxes
|
|
proc selectRenumberNodes { wi l } {
|
|
global renumber_node_from renumber_node_to; # may be undefined
|
|
if { $l == "to" } {
|
|
set nlist $wi.nodes.right.fr.to
|
|
set other "from"
|
|
} else {
|
|
set nlist $wi.nodes.left.fr.from
|
|
set other "to"
|
|
}
|
|
if { [info exists renumber_node_$other] } { ;# do we have two selections?
|
|
$wi.fbot.apply configure -state normal
|
|
}
|
|
set sel [$nlist curselection]
|
|
if { [info exists renumber_node_${l}] } {; # color prev selected item black
|
|
$nlist itemconfigure [set renumber_node_${l}] -foreground black
|
|
}
|
|
$nlist itemconfigure $sel -foreground blue; # highlight curr selection blue
|
|
set renumber_node_${l} $sel; # this value is used by the apply button
|
|
}
|
|
|
|
# perform node renumbering, e.g. n1 becomes n3 and n3 is now n1
|
|
proc renumberNodes { wi } {
|
|
global renumber_node_from renumber_node_to
|
|
set from_idx $renumber_node_from
|
|
set to_idx $renumber_node_to
|
|
|
|
if { $from_idx < 0 || $to_idx < 0 } { return }
|
|
set from [$wi.nodes.left.fr.from get $from_idx]
|
|
set to [$wi.nodes.right.fr.to get $to_idx]
|
|
set from_name [getNodeName $from]
|
|
set to_name [getNodeName $to]
|
|
|
|
set tmp [newObjectId node]
|
|
|
|
global $to $from $tmp
|
|
set $tmp [set $to]
|
|
renumberNodesIfcs $to $tmp
|
|
renumberNodesIfcs $from $to
|
|
set $to [set $from]
|
|
set $from [set $tmp]
|
|
renumberNodesIfcs $tmp $from
|
|
setNodeName $from $from_name
|
|
setNodeName $to $to_name
|
|
redrawAll
|
|
destroy $wi
|
|
}
|
|
|
|
# helper to change interface-peers and links from one node to another
|
|
proc renumberNodesIfcs { from to } {
|
|
foreach ifc [ifcList $from] {
|
|
set peer [peerByIfc $from $ifc]
|
|
set peerifc [ifcByPeer $peer $from]
|
|
set link [linkByPeers $from $peer]
|
|
global $peer $link
|
|
# modify the peer to point to the new node
|
|
set i [lsearch [set $peer] "interface-peer {$peerifc $from}"]
|
|
set $peer [lreplace [set $peer] $i $i "interface-peer {$peerifc $to}"]
|
|
# update the link variable with the new node
|
|
if { [lindex [linkPeers $link] 0] == $from } {
|
|
set newnodes "nodes {$to $peer}"
|
|
} else {
|
|
set newnodes "nodes {$peer $to}"
|
|
}
|
|
set $link [lreplace [set $link] 0 0 $newnodes]
|
|
}
|
|
}
|
|
|
|
proc addAddressesToConfig { node cfg_ref } {
|
|
global $node systype
|
|
|
|
upvar 1 $cfg_ref cfg
|
|
|
|
foreach ifc [ifcList $node] {
|
|
set addr [getIfcIPv4addr $node $ifc]
|
|
set addr6 [getIfcIPv6addr $node $ifc]
|
|
if {[lindex $systype 0] == "Linux" } { ;# Linux
|
|
if { $addr != "" } {
|
|
lappend cfg "/usr/local/sbin/addip.sh $ifc $addr"
|
|
}
|
|
if { $addr6 != "" } {
|
|
lappend cfg "/usr/local/sbin/addip.sh $ifc $addr6"
|
|
}
|
|
} else {
|
|
if { $addr != "" } {
|
|
lappend cfg "ifconfig $ifc inet $addr"
|
|
}
|
|
if { $addr6 != "" } {
|
|
lappend cfg "ifconfig $ifc inet6 $addr6"
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# support for legacy static routing config
|
|
# called by upgradeNetworkConfigToServices
|
|
proc addStaticRoutesToConfig { node cfg_ref } {
|
|
global $node systype
|
|
|
|
upvar 1 $cfg_ref cfg
|
|
|
|
foreach statrte [getStatIPv4routes $node] {
|
|
if {[lindex $systype 0] == "Linux" } { ;# Linux
|
|
set net [lindex [split $statrte] 0]
|
|
set gw [lindex [split $statrte] 1]
|
|
lappend cfg "/sbin/ip -4 route add $net via $gw"
|
|
} else { ;# FreeBSD
|
|
lappend cfg "route -q add -inet $statrte"
|
|
}
|
|
}
|
|
|
|
foreach statrte [getStatIPv6routes $node] {
|
|
if { [lindex $systype 0] == "Linux" } { ;# Linux
|
|
set net [lindex [split $statrte] 0]
|
|
set gw [lindex [split $statrte] 1]
|
|
if { $net == "::/0" } { set net "default" }
|
|
lappend cfg "/sbin/ip -6 route add $net via $gw"
|
|
} else { ;# FreeBSD
|
|
lappend cfg "route -q add -inet6 $statrte"
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
proc getServiceStartString { } {
|
|
global systype
|
|
|
|
setSystype
|
|
|
|
if { [lindex $systype 0] == "Linux" } { ;# Linux
|
|
return "/etc/init.d/core-daemon start"
|
|
} else { ;# FreeBSD
|
|
return "/usr/local/etc/rc.d/core onestart"
|
|
}
|
|
}
|
|
|
|
proc popupBuildHostsFile { } {
|
|
global node_list
|
|
set wi .buildhostsdialog
|
|
|
|
catch {destroy $wi}
|
|
|
|
toplevel $wi
|
|
wm transient $wi .
|
|
wm resizable $wi 1 1
|
|
wm title $wi "Build hosts File"
|
|
|
|
# help text at top
|
|
frame $wi.top
|
|
set helptext "The entries below can be added to your /etc/hosts file.\n"
|
|
set helptext "$helptext Use the append button to write it now."
|
|
label $wi.top.help -text $helptext
|
|
pack $wi.top.help -side top -fill both -expand true
|
|
pack $wi.top -padx 4 -pady 4 -side top
|
|
|
|
# text box
|
|
frame $wi.mid
|
|
text $wi.mid.hosts -relief sunken -bd 2 \
|
|
-yscrollcommand "$wi.mid.scroll set" -setgrid 1 -height 30 -undo 1 \
|
|
-autosep 1 -background white
|
|
scrollbar $wi.mid.scroll -command "$wi.mid.hosts yview"
|
|
pack $wi.mid.hosts -side left -fill both -expand true
|
|
pack $wi.mid.scroll -side right -fill y
|
|
pack $wi.mid -side top -fill both -expand true
|
|
|
|
$wi.mid.hosts insert end "### begin CORE auto-generated hosts entries\n"
|
|
foreach node $node_list {
|
|
set hostname [getNodeName $node]
|
|
foreach ifc [ifcList $node] {
|
|
foreach addr [getIfcIPv4addr $node $ifc] {
|
|
set ip [lindex [split $addr /] 0]
|
|
$wi.mid.hosts insert end "$ip $hostname\n"
|
|
}
|
|
}
|
|
}
|
|
$wi.mid.hosts insert end "### end CORE auto-generated hosts entries\n"
|
|
|
|
# file selection
|
|
frame $wi.fil
|
|
entry $wi.fil.filename -width 30 -bg white
|
|
button $wi.fil.filebtn -text "..." -command {
|
|
set wi .buildhostsdialog
|
|
set f [$wi.fil.filename get]
|
|
set f [tk_getSaveFile -initialfile $f]
|
|
if { $f != "" } {
|
|
$wi.fil.filename delete 0 end
|
|
$wi.fil.filename insert 0 $f
|
|
}
|
|
}
|
|
pack $wi.fil.filename -expand true -fill x -side left
|
|
pack $wi.fil.filebtn -side left
|
|
pack $wi.fil -side top
|
|
$wi.fil.filename insert 0 "/etc/hosts"
|
|
|
|
# buttons on the bottom
|
|
frame $wi.btm
|
|
button $wi.btm.apply -text "Append file" -command {
|
|
set wi .buildhostsdialog
|
|
set hosts [string trim [$wi.mid.hosts get 0.0 end]]
|
|
set filename [$wi.fil.filename get]
|
|
set fileId [open $filename a]
|
|
puts $fileId $hosts
|
|
close $fileId
|
|
destroy $wi
|
|
}
|
|
button $wi.btm.cancel -text "Close" -command "destroy $wi"
|
|
pack $wi.btm.apply $wi.btm.cancel -side left
|
|
pack $wi.btm
|
|
|
|
focus $wi.mid.hosts
|
|
}
|
|
|
|
proc popupAddressConfig { } {
|
|
global plugin_img_add plugin_img_del g_prefs
|
|
set wi .addrconfig
|
|
|
|
catch {destroy $wi}
|
|
|
|
toplevel $wi
|
|
wm transient $wi .
|
|
wm resizable $wi 1 1
|
|
wm title $wi "IP Addresses"
|
|
|
|
# help text at top
|
|
frame $wi.top
|
|
set helptext "New interfaces are automatically assigned IP addresses from\n"
|
|
set helptext "$helptext the range selected below."
|
|
label $wi.top.help -text $helptext
|
|
pack $wi.top.help -side top -fill both -expand true
|
|
pack $wi.top -padx 4 -pady 4 -side top
|
|
|
|
frame $wi.f
|
|
foreach fam [list "IPv4" "IPv6"] {
|
|
set faml [string tolower $fam]
|
|
set f $wi.f.$faml
|
|
labelframe $f -text "$fam"
|
|
# list of address prefixes
|
|
frame $f.l
|
|
listbox $f.l.list -bg white -yscrollcommand "$f.l.scroll set" \
|
|
-exportselection 0
|
|
scrollbar $f.l.scroll -command "$f.l.list yview" -bd 1 -width 10
|
|
pack $f.l.list $f.l.scroll -side left -fill y -expand true
|
|
pack $f.l -side top -fill y -expand true
|
|
# controls for editing list
|
|
frame $f.edit
|
|
entry $f.edit.addr -bg white -width 20
|
|
button $f.edit.new -image $plugin_img_add \
|
|
-command "addressConfigHelper $wi $fam add"
|
|
button $f.edit.del -image $plugin_img_del \
|
|
-command "addressConfigHelper $wi $fam del"
|
|
pack $f.edit.addr $f.edit.new $f.edit.del -side left
|
|
pack $f.edit -side top
|
|
|
|
frame $f.butt
|
|
label $f.butt.l -text "Remove $fam:"
|
|
button $f.butt.delall -text "all" -command "delAddrs all $fam"
|
|
button $f.butt.delsel -text "selected" -command "delAddrs sel $fam"
|
|
pack $f.butt.l $f.butt.delall $f.butt.delsel -side left
|
|
pack $f.butt -side top
|
|
|
|
pack $f -side left
|
|
|
|
# populate list and select appropriate entry
|
|
if {![info exists g_prefs(gui_${faml}_addr)]} { setDefaultAddrs $faml }
|
|
set idx -1; set i 0
|
|
foreach addr $g_prefs(gui_${faml}_addrs) {
|
|
$f.l.list insert end $addr
|
|
if { $addr == $g_prefs(gui_${faml}_addr) } { set idx $i }
|
|
incr i
|
|
}
|
|
if { $idx < 0 } {
|
|
$f.l.list insert end $g_prefs(gui_${faml}_addr)
|
|
$f.l.list selection set end
|
|
} else {
|
|
$f.l.list selection set $idx
|
|
}
|
|
} ;# end foreach fam
|
|
pack $wi.f -side top
|
|
|
|
# buttons on the bottom
|
|
frame $wi.btm
|
|
button $wi.btm.apply -text "OK" \
|
|
-command "addressConfigHelper $wi \"\" apply"
|
|
button $wi.btm.def -text "Defaults" \
|
|
-command "setDefaultAddrs ipv4; setDefaultAddrs ipv6; destroy $wi; popupAddressConfig"
|
|
button $wi.btm.cancel -text "Cancel" -command "destroy $wi"
|
|
pack $wi.btm.apply $wi.btm.def $wi.btm.cancel -side left
|
|
pack $wi.btm
|
|
|
|
#focus $wi.mid.hosts
|
|
}
|
|
|
|
# listbox helper for adding and removing entries, and applying them
|
|
proc addressConfigHelper { wi fam cmd } {
|
|
global g_prefs
|
|
|
|
set f $wi.f.[string tolower $fam]
|
|
switch -exact -- $cmd {
|
|
add {
|
|
set addr [$f.edit.addr get]
|
|
$f.l.list insert end $addr
|
|
}
|
|
del {
|
|
set i [$f.l.list curselection]
|
|
if { $i == "" } { return }
|
|
$f.l.list delete $i
|
|
}
|
|
apply {
|
|
foreach fam [list "ipv4" "ipv6"] {
|
|
set f $wi.f.$fam
|
|
set i [$f.l.list curselection]
|
|
if { $i == "" } { set i 0 }
|
|
set addr [$f.l.list get $i]
|
|
set addrs [$f.l.list get 0 end]
|
|
array set g_prefs [list gui_${fam}_addr $addr]
|
|
array set g_prefs [list gui_${fam}_addrs $addrs]
|
|
}
|
|
destroy $wi
|
|
}
|
|
}
|
|
}
|
|
|
|
# set the default addresses for automatic allocation in the g_prefs array
|
|
# for the given address family
|
|
proc setDefaultAddrs { fam } {
|
|
global g_prefs
|
|
if { $fam == "ipv4" } {
|
|
set addrs [getDefaultIPv4Addrs]
|
|
} elseif { $fam == "ipv6" } {
|
|
set addrs [getDefaultIPv6Addrs]
|
|
} else {
|
|
return
|
|
}
|
|
array set g_prefs [list gui_${fam}_addr [lindex $addrs 0]]
|
|
array set g_prefs [list gui_${fam}_addrs $addrs]
|
|
}
|
|
|
|
proc popupMacAddressConfig { } {
|
|
set wi .macaddrconfig
|
|
global mac_addr_start
|
|
|
|
catch {destroy $wi}
|
|
|
|
toplevel $wi
|
|
wm transient $wi .
|
|
wm resizable $wi 1 1
|
|
wm title $wi "MAC Addresses"
|
|
|
|
# help text at top
|
|
frame $wi.top
|
|
set helptext "MAC addresses are automatically assigned starting with\n"
|
|
set helptext "$helptext 00:00:00:aa:00:nn, where nn starts with the below"
|
|
set helptext "$helptext value.\n You should change this value when tunneling"
|
|
set helptext "$helptext between \nemulations to prevent MAC address conflicts."
|
|
|
|
label $wi.top.help -text $helptext
|
|
pack $wi.top.help -side top -fill both -expand true
|
|
pack $wi.top -padx 4 -pady 4 -side top
|
|
|
|
if { ![info exists mac_addr_start] } { set mac_addr_start 0 }
|
|
frame $wi.f
|
|
label $wi.f.maclab -text "Starting MAC number:"
|
|
entry $wi.f.mac -width 5 -bg white -textvariable mac_addr_start
|
|
pack $wi.f.maclab $wi.f.mac $wi.f -side left
|
|
pack $wi.f -side top
|
|
|
|
frame $wi.btm
|
|
button $wi.btm.apply -text "close" -command "destroy $wi"
|
|
pack $wi.btm.apply -side left
|
|
pack $wi.btm
|
|
|
|
}
|
|
|
|
#
|
|
# Capture a window into an image
|
|
# Author: David Easton
|
|
#
|
|
proc captureWindow { win } {
|
|
|
|
|
|
regexp {([0-9]*)x([0-9]*)\+([0-9]*)\+([0-9]*)} [winfo geometry $win] - w h x y
|
|
|
|
# Make the base image based on the window
|
|
set image [image create photo -format window -data $win]
|
|
|
|
foreach child [winfo children $win] {
|
|
captureWindowSub $child $image 0 0
|
|
}
|
|
|
|
return $image
|
|
}
|
|
|
|
proc captureWindowSub { win image px py } {
|
|
|
|
if {![winfo ismapped $win]} {
|
|
return
|
|
}
|
|
|
|
regexp {([0-9]*)x([0-9]*)\+([0-9]*)\+([0-9]*)} [winfo geometry $win] - w h x y
|
|
|
|
incr px $x
|
|
incr py $y
|
|
|
|
# Make an image from this widget
|
|
set tempImage [image create photo -format window -data $win]
|
|
|
|
# Copy this image into place on the main image
|
|
$image copy $tempImage -to $px $py
|
|
image delete $tempImage
|
|
|
|
foreach child [winfo children $win] {
|
|
captureWindowSub $child $image $px $py
|
|
}
|
|
}
|
|
|
|
proc writeCanvasThumbnail { c fn } {
|
|
global execMode
|
|
set r false
|
|
if [ catch {
|
|
set img [captureWindow $c]
|
|
set imgthumb [image create photo]
|
|
# resize image to height 100
|
|
set w [image height $img]
|
|
$imgthumb copy $img -subsample [expr { int($w / 100)} ]
|
|
$imgthumb write $fn -format jpeg
|
|
image delete $img
|
|
image delete $imgthumb
|
|
set r true
|
|
} e ] {
|
|
if { $execMode == "interactive" } {
|
|
puts "warning: failed to create canvas thumbnail"
|
|
}
|
|
}
|
|
return $r
|
|
}
|
|
|
|
# contributed code from CL from http://wiki.tcl.tk/557 fetched 6/11/10
|
|
proc _launchBrowser url {
|
|
global tcl_platform
|
|
|
|
# It *is* generally a mistake to switch on $tcl_platform(os), particularly
|
|
# in comparison to $tcl_platform(platform). For now, let's just regard it
|
|
# as a stylistic variation subject to debate.
|
|
switch $tcl_platform(os) {
|
|
Darwin {
|
|
set command [list open $url]
|
|
}
|
|
HP-UX -
|
|
Linux -
|
|
SunOS {
|
|
foreach executable {firefox mozilla netscape iexplorer opera lynx
|
|
w3m links epiphany galeon konqueror mosaic amaya
|
|
browsex elinks} {
|
|
set executable [auto_execok $executable]
|
|
if [string length $executable] {
|
|
# Do you want to mess with -remote? How about other browsers?
|
|
set command [list $executable $url &]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
{Windows 95} -
|
|
{Windows NT} {
|
|
set command "[auto_execok start] {} [list $url]"
|
|
}
|
|
}
|
|
if [info exists command] {
|
|
if [catch {eval exec $command } err] {
|
|
tk_messageBox -icon error -message "error '$err' with '$command'"
|
|
}
|
|
} else {
|
|
tk_messageBox -icon error -message \
|
|
"Please tell CL that ($tcl_platform(os), $tcl_platform(platform)) is not yet ready for browsing."
|
|
}
|
|
}
|
|
|
|
# helper for registering a callback with a tk_optionMenu variable, when a user
|
|
# clicks on the menu and changes the value; if the global variable var is
|
|
# cleared, this callback is cancelled
|
|
# NOTE: when closing the window that calls this, ensure that var is cleared
|
|
proc tkOptionMenuCallback { ctl var cb args } {
|
|
if { ![winfo exists $ctl] } { return }
|
|
global $var
|
|
tkwait variable $var
|
|
# cancel callback when var is cleared
|
|
if { [set $var] == "" } { return }
|
|
# here is a hack to remove the outer list
|
|
if {[llength $args] == 1} { set args [lindex $args 0] }
|
|
$cb [set $var] $args
|
|
|
|
tkOptionMenuCallback $ctl $var $cb $args
|
|
}
|
|
|
|
# split a string on commas, while respecting single quote "'" quoting
|
|
# items should be in single quotes which are omitted from the resulting list
|
|
# e.g. 'foo', 'ba\'r', 'baz' ==> {foo, ba'r, baz}
|
|
proc safeCommaSplit { str } {
|
|
set item ""
|
|
set r []
|
|
set inquotes false
|
|
for {set i 0} {$i < [string length $str]} {incr i} {
|
|
set c [string index $str $i]
|
|
if { $c == "'" } {
|
|
# check for escaped quote and skip it
|
|
if { $i > 0 && [string index $str [expr $i-1]] == "\\" } {
|
|
set item "$item$c"
|
|
continue
|
|
}
|
|
if { !$inquotes } {
|
|
set inquotes true ;# begin quoted block
|
|
} else {
|
|
set inquotes false ;# end quoted block
|
|
}
|
|
continue
|
|
}
|
|
if { $c == "," && !$inquotes } { ;# comma outside quoted block
|
|
lappend r $item
|
|
set item ""
|
|
continue
|
|
}
|
|
set item "$item$c"
|
|
}
|
|
if { $item != "" } { lappend r $item } ;# last item in list
|
|
return $r
|
|
}
|
|
|
|
# convert a string in the Python tuple format into a TCL list
|
|
proc tupleStringToList { str } {
|
|
set str [string trim $str] ;# remove whitespace
|
|
set str [string trim $str "()"] ;# remove (...)
|
|
set quotedlist [safeCommaSplit $str] ;# [split $str ","] doesn't work here
|
|
set r ""
|
|
foreach item $quotedlist {
|
|
set item [string trim $item] ;# remove whitespace
|
|
# un-escape double and single quotes
|
|
set item [string map {\\\" \"} $item]
|
|
set item [string map {\\' '} $item]
|
|
if { $item != "" } { lappend r $item }
|
|
}
|
|
return $r
|
|
}
|
|
|
|
# convert a TCL list to the Python tuple format
|
|
proc listToTupleString { l } {
|
|
if { [llength $l] == 0 } { return {} } ;# don't return empty tuples "()"
|
|
set str "("
|
|
foreach item $l {
|
|
# escape double and single quotes
|
|
set item [string map {\" \\\"} $item]
|
|
set item [string map {' \\'} $item]
|
|
# enclose each item in single quotes
|
|
set str "$str'$item', "
|
|
}
|
|
set str "$str)"
|
|
return $str
|
|
}
|
|
|
|
proc exportPython { } {
|
|
global node_list link_list
|
|
|
|
foreach n $node_list {
|
|
set name [getNodeName $n]
|
|
set xy [getNodeCoords $n]
|
|
puts " $name = session.addobj(cls = pycore.nodes.class, name=\"$name\")"
|
|
puts " $name.setposition(x=[lindex $xy 0],y=[lindex $xy 1])"
|
|
}
|
|
|
|
foreach l $link_list {
|
|
set lnode1 [lindex [linkPeers $l] 0]
|
|
set lnode2 [lindex [linkPeers $l] 1]
|
|
set ifc1 [ifcByPeer $lnode1 $lnode2]
|
|
set ifc2 [ifcByPeer $lnode2 $lnode1]
|
|
set net "ptpnet"
|
|
if { [[typemodel $lnode1].layer] == "LINK" } {
|
|
set net [getNodeName $lnode1]
|
|
}
|
|
if { [[typemodel $lnode2].layer] == "LINK" } {
|
|
set net [getNodeName $lnode2]
|
|
}
|
|
if { [[typemodel $lnode1].layer] == "NETWORK" } {
|
|
set ipv4 [getIfcIPv4addr $lnode1 $ifc1]
|
|
set if1n [ifcNameToNum $ifc1]
|
|
puts -nonewline " [getNodeName $lnode1].newnetif(net=$net, "
|
|
puts "addrlist=\[\"$ipv4\"\], ifindex=$if1n)"
|
|
}
|
|
if { [[typemodel $lnode2].layer] == "NETWORK" } {
|
|
set ipv4 [getIfcIPv4addr $lnode2 $ifc2]
|
|
set if2n [ifcNameToNum $ifc2]
|
|
puts -nonewline " [getNodeName $lnode2].newnetif(net=$net, "
|
|
puts "addrlist=\[\"$ipv4\"\], ifindex=$if2n)"
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
# ask the daemon to execute the selected file
|
|
proc execPython { with_options } {
|
|
global fileDialogBox_initial g_prefs
|
|
set ft {{ "CORE XML or Python scripts" {.py .xml} } { "All files" {*}}}
|
|
|
|
if { $fileDialogBox_initial == 0 } {
|
|
set fileDialogBox_initial 1
|
|
set dir $g_prefs(default_conf_path)
|
|
set fn [tk_getOpenFile -filetypes $ft -initialdir $dir]
|
|
} else {
|
|
set fn [tk_getOpenFile -filetypes $ft]
|
|
}
|
|
if { $fn == "" } { return }
|
|
if { $with_options } {
|
|
set prompt "Append any command-line options for running the Python"
|
|
set prompt "$prompt script:"
|
|
set fn [tk_inputBox "Python Script Options" $prompt $fn . 50]
|
|
if { $fn == "" } { return }
|
|
}
|
|
set flags 0x10 ;# status request flag
|
|
sendRegMessage -1 $flags [list "exec" $fn]
|
|
addFileToMrulist $fn
|
|
}
|
|
|
|
# open a dialog that prompts the user with a text entry
|
|
# this is a blocking dialog that returns "" for cancel, or the entry text for OK
|
|
proc tk_inputBox { title prompt default_text parent width} {
|
|
set w .input_box
|
|
catch {destroy $w}
|
|
toplevel $w
|
|
|
|
global g_input_box_btn_state
|
|
set g_input_box_btn_state 0
|
|
global g_input_box_text
|
|
set g_input_box_text $default_text
|
|
|
|
wm title $w $title
|
|
wm transient $w $parent
|
|
wm attributes $w -type dialog
|
|
|
|
ttk::frame $w.f
|
|
ttk::label $w.f.top -text $prompt
|
|
ttk::entry $w.f.ent -width $width -textvariable g_input_box_text
|
|
pack $w.f.top $w.f.ent -side top -padx 4 -pady 4
|
|
pack $w.f -side top
|
|
|
|
ttk::frame $w.btn
|
|
ttk::button $w.btn.ok -text "OK" -command {
|
|
global g_input_box_btn_state
|
|
set g_input_box_btn_state 1
|
|
}
|
|
ttk::button $w.btn.cancel -text "Cancel" -command {
|
|
global g_input_box_text
|
|
global g_input_box_btn_state
|
|
set g_input_box_text ""
|
|
set g_input_box_btn_state 2
|
|
}
|
|
pack $w.btn.ok $w.btn.cancel -side left -padx 4 -pady 4
|
|
pack $w.btn -side top
|
|
|
|
vwait g_input_box_btn_state
|
|
destroy $w
|
|
return $g_input_box_text
|
|
}
|
|
|
|
# from Practical Programming in Tcl and Tk, page 190
|
|
proc Call_Trace {{file stdout}} {
|
|
puts $file "*** Tcl Call Trace:"
|
|
for {set x [expr [info level]-1]} {$x > 0} {incr x -1} {
|
|
puts $file " $x: [info level $x]"
|
|
}
|
|
}
|
|
|
|
# toggle a boolean variable
|
|
proc toggle { v } {
|
|
upvar 1 $v var
|
|
set var [expr {!$var}]
|
|
}
|
|
|
|
# return the name of the text editor to use
|
|
# - the preference g_prefs(gui_text_editor) overrides any default
|
|
# - if want_default is specified, first return EDITOR environment variable,
|
|
# then find the first in the list of editors that exists on the system
|
|
set EDITORS "vim emacs gedit nano vi"
|
|
proc get_text_editor { want_default } {
|
|
global g_prefs env EDITORS
|
|
set ed ""
|
|
if { [info exists env(EDITOR)] } {
|
|
set ed $env(EDITOR)
|
|
}
|
|
if { !$want_default && [info exists g_prefs(gui_text_editor)] } {
|
|
set edpref $g_prefs(gui_text_editor)
|
|
# preference can be EDITOR, to use environment variable EDITOR
|
|
if { $edpref == "EDITOR" } {
|
|
set edpref $ed
|
|
}
|
|
if { $edpref != "" } {
|
|
return $edpref
|
|
}
|
|
# fall through since environment variable or pref not set
|
|
}
|
|
|
|
# first use any EDITOR variable
|
|
if { $ed != "" } {
|
|
if { $want_default } {
|
|
return "EDITOR"
|
|
} else {
|
|
return $ed
|
|
}
|
|
}
|
|
# return the first installed editor from EDITORS global
|
|
foreach ed $EDITORS {
|
|
if { [auto_execok $ed] != "" } {
|
|
return $ed
|
|
}
|
|
}
|
|
# none of the editors were found, just return the first one
|
|
return [lindex $EDITORS 0]
|
|
}
|
|
|
|
# return the name of the terminal program to use
|
|
# - the preference g_prefs(gui_term_prog) overrides any default
|
|
# - if want_default is specified, first return COLORTERM or TERM environment
|
|
# variable, then find the first in the list of terminals that exists on the
|
|
# system
|
|
set TERMS "{gnome-terminal -x} {lxterminal -e} {konsole -e} {xterm -e}"
|
|
set TERMS "$TERMS {aterm -e} {eterm -e} {rxvt -e} {xfce4-terminal -e}"
|
|
|
|
proc get_term_prog { want_default } {
|
|
global g_prefs env TERMS
|
|
# initialize term = COLORTERM or TERM environment variables
|
|
set term ""
|
|
if { [info exists env(COLORTERM)] } {
|
|
if { ![string is integer $env(COLORTERM) ] } {
|
|
# under OpenSUSE, COLORTERM=1
|
|
set term [auto_execok $env(COLORTERM)]
|
|
}
|
|
}
|
|
if { $term == "" && [info exists env(TERM)] } {
|
|
set term [auto_execok $env(TERM)]
|
|
}
|
|
if { $term != "" } {
|
|
set arg "-e"
|
|
# gnome-terminal has problem w/subsequent arguments after -e, needs -x
|
|
if { [file tail $term] == "gnome-terminal" } { set arg "-x" }
|
|
set term "$term $arg"
|
|
}
|
|
|
|
if { !$want_default && [info exists g_prefs(gui_term_prog)] } {
|
|
set termpref $g_prefs(gui_term_prog)
|
|
# preference can be TERM, to adopt environment variable TERM
|
|
if { $termpref == "TERM" } {
|
|
set termpref $term
|
|
}
|
|
if { $termpref != "" } {
|
|
return $termpref ;# pre-configured preference or expanded TERM
|
|
}
|
|
# fall through since environment variable or preference not set
|
|
}
|
|
|
|
# first use any TERM variable
|
|
if { $term != "" } {
|
|
if { $want_default } {
|
|
return "TERM"
|
|
} else {
|
|
return $term
|
|
}
|
|
}
|
|
# return the first installed terminal from TERMS global
|
|
foreach term $TERMS {
|
|
if { [auto_execok [lindex $term 0]] != "" } {
|
|
return $term
|
|
}
|
|
}
|
|
# none of the terminals were found, just return the first one
|
|
return [lindex $TERMS 0]
|
|
}
|
|
|
|
# short session ID used by Python daemon for interface names
|
|
proc shortSessionID { sid } {
|
|
return [ expr { ($sid >> 8) ^ ($sid & ((1<<8) - 1)) } ]
|
|
}
|
|
|
|
|
|
proc delAddrs { mode fam } {
|
|
global node_list
|
|
if { $mode == "all" } {
|
|
delAddrsFromNodes $fam $node_list
|
|
} else {
|
|
set msg "Remove all $fam addresses from these nodes:"
|
|
popupSelectNodes $msg "" "delAddrsFromNodes $fam"
|
|
}
|
|
}
|
|
|
|
proc delAddrsFromNodes { fam nodes } {
|
|
foreach node $nodes {
|
|
foreach ifc [ifcList $node] {
|
|
if { $fam == "IPv4" } {
|
|
setIfcIPv4addr $node $ifc ""
|
|
} elseif { $fam == "IPv6" } {
|
|
setIfcIPv6addr $node $ifc ""
|
|
}
|
|
}
|
|
}
|
|
redrawAll
|
|
}
|
|
|
|
# fix for Tcl/Tk 8.5.8 and lower which doesn't have ttk::spinbox
|
|
# set spinbox [getspinbox]
|
|
# $spinbox $var -justify right -width 10 ...
|
|
#
|
|
proc getspinbox {} {
|
|
if { [info command ttk::spinbox] == "" } {
|
|
return spinbox
|
|
} else {
|
|
return ttk::spinbox
|
|
}
|
|
}
|
|
|
|
# find dialog for searching for nodes and links
|
|
proc popupFind {} {
|
|
set msg "find"
|
|
set initsel ""
|
|
set callback ""
|
|
global node_list
|
|
|
|
set w .find
|
|
catch {destroy $w}
|
|
toplevel $w -takefocus 1
|
|
wm transient $w .
|
|
wm title $w "Find"
|
|
|
|
ttk::frame $w.find -borderwidth 4
|
|
ttk::label $w.find.lab -text "Find:"
|
|
ttk::entry $w.find.text -width 40
|
|
pack $w.find.lab -side left
|
|
pack $w.find.text -side left -fill x -expand 1
|
|
pack $w.find -fill x -side top -padx 4 -pady 4
|
|
bind $w.find.text <Key-Return> "findButton $w"
|
|
|
|
ttk::frame $w.mid
|
|
ttk::treeview $w.mid.tree -columns {number name location details} \
|
|
-show headings -yscroll "$w.mid.vsb set" -xscroll "$w.mid.hsb set"
|
|
ttk::scrollbar $w.mid.vsb -orient vertical -command "$w.mid.tree yview"
|
|
ttk::scrollbar $w.mid.hsb -orient horizontal -command "$w.mid.tree xview"
|
|
pack $w.mid -side top -fill both -expand true -padx 4 -pady 4
|
|
grid $w.mid.tree $w.mid.vsb -in $w.mid -sticky nsew
|
|
grid $w.mid.hsb -in $w.mid -sticky nsew
|
|
grid column $w.mid 0 -weight 1
|
|
grid row $w.mid 0 -weight 1
|
|
|
|
bind $w.mid.tree <<TreeviewSelect>> "findTreeSelect $w.mid.tree"
|
|
|
|
set closecmd "drawNodeCircle {} {} {} {} findhi; destroy $w"
|
|
bind $w.find.text <Key-Escape> $closecmd
|
|
bind $w <Key-Escape> $closecmd
|
|
|
|
ttk::frame $w.fbot -borderwidth 4
|
|
ttk::button $w.fbot.find -text "Find" -command "findButton $w"
|
|
ttk::button $w.fbot.close -text "Close" -command $closecmd
|
|
pack $w.fbot.find $w.fbot.close -side left -padx 4 -pady 4
|
|
pack $w.fbot -side bottom
|
|
|
|
findTreeHeader $w.mid.tree
|
|
focus $w.find.text
|
|
}
|
|
|
|
# helper for find button, implements searching of nodes, links, and node names
|
|
# TODO: search IPv4/IPv6/MAC addresses, services, annotations, EMANE configs?
|
|
proc findButton { w } {
|
|
global node_list link_list
|
|
set terms [$w.find.text get]
|
|
set tree $w.mid.tree
|
|
|
|
findTreeHeader $tree
|
|
. config -cursor watch; update
|
|
set first ""
|
|
set nodename_list ""
|
|
foreach n $node_list { lappend nodename_list [getNodeName $n] }
|
|
|
|
foreach search [list node nodename link] {
|
|
set ${search}_results [lsearch -nocase -all -glob \
|
|
[set ${search}_list] "*$terms*"]
|
|
# populate results
|
|
foreach result [set ${search}_results] {
|
|
if { $result == -1 } { continue }
|
|
set search_list ${search}_list
|
|
if { ${search} == "nodename" } { set search_list node_list }
|
|
set obj [lindex [set $search_list] $result]
|
|
if { $first == "" } { set first $obj }
|
|
|
|
set num $obj
|
|
if { $search == "link" } {
|
|
set peers [linkPeers $obj]
|
|
set name "[lindex $peers 0]-[lindex $peers 1]"
|
|
set coords [getNodeCoords [lindex $peers 0]]
|
|
set details [getLinkBandwidthString $obj]
|
|
set details "$details [getLinkDelayString $obj]"
|
|
} else {
|
|
set name [getNodeName $obj]
|
|
set coords [getNodeCoords $obj]
|
|
set details [ipv4List $obj false]
|
|
set details "$details [ipv6List $obj false]"
|
|
}
|
|
set coords "<[lindex $coords 0], [lindex $coords 1]>"
|
|
if { ![$tree exists $obj] } {
|
|
$tree insert {} end -id $obj \
|
|
-values [list $num $name $coords $details]
|
|
}
|
|
}
|
|
}
|
|
|
|
if { $first == "" } {
|
|
$tree insert {} end -id none -values [list "" "" "" "no results found"]
|
|
} else {
|
|
$tree selection set $first
|
|
}
|
|
|
|
. config -cursor left_ptr
|
|
}
|
|
|
|
# helper clears treeview and populates column header row
|
|
proc findTreeHeader { tree } {
|
|
$tree delete [$tree children {}]
|
|
set widths {75 75 125 350}; set i 0
|
|
foreach col {number name location details} {
|
|
$tree heading $col -text $col
|
|
set width [lindex $widths $i]; incr i
|
|
$tree column $col -width $width
|
|
}
|
|
drawNodeCircle "" "" "" "" findhi
|
|
}
|
|
|
|
# helper handles treeview selection changes in the Find dialog
|
|
proc findTreeSelect { ctl } {
|
|
global curcanvas
|
|
drawNodeCircle "" "" "" "" findhi
|
|
set obj [$ctl selection]
|
|
if { $obj == "none" } { return }
|
|
|
|
if { [string range $obj 0 0] == "l" } {
|
|
lassign [linkPeers $obj] node node2
|
|
} else {
|
|
set node $obj
|
|
set node2 ""
|
|
}
|
|
|
|
# highlight node(s) and reposition canvas view so items are visible
|
|
set target_canvas [getNodeCanvas $node]
|
|
if { $target_canvas != $curcanvas } {
|
|
set curcanvas $target_canvas
|
|
switchCanvas none
|
|
}
|
|
drawNodeCircle $node 30 red findhi ""
|
|
if { $node2 != "" } { drawNodeCircle $node2 30 red findhi "" }
|
|
canvasSee .c $node
|
|
}
|
|
|
|
|