vi_cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +12 -0
- data/Manifest.txt +14 -0
- data/README.txt +58 -0
- data/bin/vi_cli +6 -0
- data/lib/Mock.rb +35 -0
- data/lib/VM.rb +107 -0
- data/lib/VMFolder.rb +55 -0
- data/lib/VMTreeItemRenderer.rb +29 -0
- data/lib/VcData.rb +87 -0
- data/lib/ViCli.rb +95 -0
- data/lib/ViCliConfig.rb +46 -0
- data/lib/ViGUI.rb +195 -0
- data/lib/Watcher.rb +52 -0
- data/rakefile +39 -0
- metadata +89 -0
data/History.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
=== 0.1.0 / 2009-04-26
|
2
|
+
|
3
|
+
* First Public Release
|
4
|
+
* MVC Design
|
5
|
+
* Threaded ncurses interface
|
6
|
+
* Basic VMware Virtual Infrastructure client functionality
|
7
|
+
* Virtual Machines and folders as tree view
|
8
|
+
* Virtual Machine overview/summary
|
9
|
+
* Virtual Machine hardware
|
10
|
+
* Current events
|
11
|
+
* Start/Stop/Reboot Virtual Machine
|
12
|
+
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= vi_cli
|
2
|
+
|
3
|
+
http://rubyforge.org/projects/vi-cli/
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
vi_cli is an ncurses based commandline client for VMware ESX and ESXi servers
|
8
|
+
|
9
|
+
== FEATURES:
|
10
|
+
|
11
|
+
* Virtual Machines and folders as tree view
|
12
|
+
* Virtual Machine overview/summary
|
13
|
+
* Virtual Machine hardware
|
14
|
+
* Current events
|
15
|
+
* Start/Stop/Reboot Virtual Machine
|
16
|
+
|
17
|
+
== PROBLEMS/BUGS:
|
18
|
+
|
19
|
+
* Many ideas for improvements already on the imaginary todo list
|
20
|
+
|
21
|
+
== USAGE:
|
22
|
+
|
23
|
+
Just execute the executable!
|
24
|
+
Type q to quit ;)
|
25
|
+
|
26
|
+
== REQUIREMENTS:
|
27
|
+
|
28
|
+
* rbcurses
|
29
|
+
* vi_api_lib
|
30
|
+
|
31
|
+
== INSTALL:
|
32
|
+
|
33
|
+
* sudo gem install vi_cli
|
34
|
+
|
35
|
+
== LICENSE:
|
36
|
+
|
37
|
+
(The MIT License)
|
38
|
+
|
39
|
+
Copyright (c) 2009 SebDE
|
40
|
+
|
41
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
42
|
+
a copy of this software and associated documentation files (the
|
43
|
+
'Software'), to deal in the Software without restriction, including
|
44
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
45
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
46
|
+
permit persons to whom the Software is furnished to do so, subject to
|
47
|
+
the following conditions:
|
48
|
+
|
49
|
+
The above copyright notice and this permission notice shall be
|
50
|
+
included in all copies or substantial portions of the Software.
|
51
|
+
|
52
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
53
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
54
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
55
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
56
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
57
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
58
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/vi_cli
ADDED
data/lib/Mock.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# A class to replace all vmware data structures with random text if vmware lib not available
|
4
|
+
class Mock < String
|
5
|
+
# initializes self with a random string
|
6
|
+
def initialize
|
7
|
+
# methods that just need to be there to show the task buttons
|
8
|
+
@methods = [:powerOnVM_Task,:shutdownGuest,:powerOffVM,:markAsVirtualMachine,:markAsTemplate,:suspendVM_Task,:resetVM_Task,:rebootGuest,:migrateVM_Task]
|
9
|
+
res = ''
|
10
|
+
-3.upto(rand 8) do
|
11
|
+
res << (65 + rand(57))
|
12
|
+
end
|
13
|
+
super res
|
14
|
+
end
|
15
|
+
# returns a new instance of it's own class
|
16
|
+
def [] i=nil
|
17
|
+
self.class.new
|
18
|
+
end
|
19
|
+
alias :task_manager :[]
|
20
|
+
alias :recent_tasks :[]
|
21
|
+
# expand the list of methods this class responds to
|
22
|
+
def respond_to? sym
|
23
|
+
return true if @methods.include? sym.to_sym
|
24
|
+
super
|
25
|
+
end
|
26
|
+
# fake if a vmware data structure should respond to this method
|
27
|
+
def method_missing sym, *args
|
28
|
+
return if @methods.include? sym.to_sym
|
29
|
+
super
|
30
|
+
end
|
31
|
+
# returns something VcData interprets as device
|
32
|
+
def soap_class
|
33
|
+
'VirtualDisk'
|
34
|
+
end
|
35
|
+
end
|
data/lib/VM.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class VM
|
4
|
+
def initialize ref, session, name
|
5
|
+
@ref = ref
|
6
|
+
@session = session
|
7
|
+
@name = name
|
8
|
+
@state_vars = ['vm_on','vm_off','is_templ']
|
9
|
+
@state_vars.map! {|i| "@"+i+"=nil" }
|
10
|
+
@color = 'grey'
|
11
|
+
end
|
12
|
+
def name
|
13
|
+
@name.strip
|
14
|
+
end
|
15
|
+
def refresh_state
|
16
|
+
@color = 'grey'
|
17
|
+
@state_vars.each {|i| self.instance_eval i } # XXX
|
18
|
+
end
|
19
|
+
def draw
|
20
|
+
yield [@name, self]
|
21
|
+
end
|
22
|
+
def color cached=nil
|
23
|
+
return @color if cached
|
24
|
+
@color = is_templ ? 'blue' : vm_on ? 'green' : vm_off ? 'red' : 'yellow'
|
25
|
+
end
|
26
|
+
def vm_on
|
27
|
+
return @vm_on if !@vm_on.nil?
|
28
|
+
@vm_on = @ref['runtime']['powerState'].to_s == 'poweredOn'
|
29
|
+
end
|
30
|
+
def vm_off
|
31
|
+
return @vm_off if !@vm_off.nil?
|
32
|
+
@vm_off = @ref['runtime']['powerState'].to_s == 'poweredOff'
|
33
|
+
end
|
34
|
+
def is_templ
|
35
|
+
return @is_templ if !@is_templ.nil?
|
36
|
+
@is_templ = @ref['summary']['config']['template']
|
37
|
+
end
|
38
|
+
def has_task? task
|
39
|
+
@ref.respond_to? task
|
40
|
+
end
|
41
|
+
def get_host
|
42
|
+
host = @ref['runtime']['host']
|
43
|
+
return '' if host.nil? or host.kind_of? $mock.class
|
44
|
+
host = @session.managed_object_wrapper_factory "HostSystem", host
|
45
|
+
@host_cpu = host['summary']['hardware']['cpuMhz']
|
46
|
+
host['name']
|
47
|
+
end
|
48
|
+
def get_hw_info
|
49
|
+
@ref['config']['hardware']['device'].collect do |d|
|
50
|
+
if d.soap_type == 'VirtualCdrom'
|
51
|
+
result = [d['deviceInfo']['label'].to_s,d['backing']['fileName'].to_s]
|
52
|
+
end
|
53
|
+
if d.soap_type == 'VirtualDisk'
|
54
|
+
i = d['deviceInfo']
|
55
|
+
result = [i['label'].to_s,i['summary'].to_s]
|
56
|
+
end
|
57
|
+
result unless result.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
def state
|
61
|
+
snapshot = @ref['snapshot']
|
62
|
+
snapshot = snapshot['currentSnapshot'].nil? ? 'no' : 'yes' unless snapshot.nil?
|
63
|
+
runtime = @ref['runtime']
|
64
|
+
summary = @ref['summary']
|
65
|
+
config = @ref['config']
|
66
|
+
num = summary['config']['numCpu'].to_s
|
67
|
+
mem = summary['config']['memorySizeMB'].to_s
|
68
|
+
qs = summary['quickStats']
|
69
|
+
guest = @ref['guest']
|
70
|
+
res = []
|
71
|
+
res << ['power state',runtime['powerState'].to_s+' since '+runtime['bootTime'].to_s]
|
72
|
+
res << ['host',get_host]
|
73
|
+
res << ['cpu usage',"#{qs['overallCpuUsage'].to_s} MHz / #{num} * #{@host_cpu.to_s} MHz"]
|
74
|
+
res << ['guest memory usage',"#{qs['guestMemoryUsage'].to_s} MB / #{mem} MB"]
|
75
|
+
res << ['host memory usage',qs['hostMemoryUsage'].to_s+' MB']
|
76
|
+
res << ['memory overhead',(runtime['memoryOverhead'].to_i/1024/1024).to_s+' MB']
|
77
|
+
res << ['console',runtime['numMksConnections'].to_s]
|
78
|
+
res << ['snapshot',snapshot.to_s]
|
79
|
+
unless guest.nil?
|
80
|
+
res << ['host name',guest['hostName'].to_s]
|
81
|
+
res << ['ip',guest['ipAddress'].to_s]
|
82
|
+
res << ['tools version',guest['toolsVersion'].to_s]
|
83
|
+
begin
|
84
|
+
guest['disk'].each do |d|
|
85
|
+
size = d['capacity']
|
86
|
+
pc = d['freeSpace']*100/size
|
87
|
+
size = size/1024/1024/1024
|
88
|
+
res << [d['diskPath'], "#{size.round.to_s}GB #{pc.round.to_s}% free"]
|
89
|
+
end unless guest['disk'].nil?
|
90
|
+
rescue => ex
|
91
|
+
res << ['exception',ex.to_s]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
res << ['store',config['files']['vmPathName'].to_s]
|
95
|
+
first = true
|
96
|
+
config['annotation'].to_s.split(/\n/).each do |a|
|
97
|
+
res << [first ? 'notes' : '',a]
|
98
|
+
first = false
|
99
|
+
end
|
100
|
+
[res, get_hw_info.compact]
|
101
|
+
end
|
102
|
+
def execute_task task
|
103
|
+
@ref.send task.to_sym
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
data/lib/VMFolder.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class VMFolder
|
4
|
+
attr_reader :collapsed
|
5
|
+
def initialize ref, session, name, collapsed = false
|
6
|
+
@ref = ref
|
7
|
+
@session = session
|
8
|
+
@name = name
|
9
|
+
@children = []
|
10
|
+
@collapsed = collapsed
|
11
|
+
end
|
12
|
+
def name
|
13
|
+
@name.strip
|
14
|
+
end
|
15
|
+
def save_data
|
16
|
+
result = [@collapsed]
|
17
|
+
@children.each { |c| result << c.save_data if c.respond_to? :save_data }
|
18
|
+
result
|
19
|
+
end
|
20
|
+
def add_child child
|
21
|
+
@children.push child
|
22
|
+
end
|
23
|
+
def has_task? task
|
24
|
+
['fold', 'unfold','toggle_fold','fold_children'].include? task.to_s
|
25
|
+
end
|
26
|
+
def state
|
27
|
+
res = [[],[]]
|
28
|
+
end
|
29
|
+
def execute_task task
|
30
|
+
self.send task.to_sym if has_task? task
|
31
|
+
true
|
32
|
+
end
|
33
|
+
def fold
|
34
|
+
@collapsed = true
|
35
|
+
end
|
36
|
+
def unfold
|
37
|
+
@collapsed = false
|
38
|
+
end
|
39
|
+
def toggle_fold
|
40
|
+
@collapsed = !@collapsed
|
41
|
+
end
|
42
|
+
def fold_children
|
43
|
+
@children.each { |c| c.fold if c.respond_to? :fold }
|
44
|
+
end
|
45
|
+
def draw
|
46
|
+
yield [@name, self]
|
47
|
+
unless @collapsed
|
48
|
+
@children.each { |c| c.draw { |i| yield i } }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def color cached=nil
|
52
|
+
@collapsed ? 'grey' : 'white'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class VMTreeItemRenderer
|
4
|
+
attr_accessor :display_length
|
5
|
+
|
6
|
+
def initialize parent, color_callback, w=nil
|
7
|
+
@parent = parent
|
8
|
+
@color_callback = color_callback
|
9
|
+
@display_length = w || 10
|
10
|
+
end
|
11
|
+
def repaint graphic, r=nil,c=nil, idx=-1,value='', focussed=false, selected=false
|
12
|
+
return if r.nil? or c.nil? or idx == -1
|
13
|
+
attr = 'bold'
|
14
|
+
bgcolor = focussed ? 'yellow' : 'black'
|
15
|
+
fgcolor = @color_callback.call idx
|
16
|
+
if fgcolor == 'grey'
|
17
|
+
attr = nil
|
18
|
+
fgcolor = 'white'
|
19
|
+
end
|
20
|
+
color_pair = ColorMap.get_color fgcolor, bgcolor
|
21
|
+
# ensure we do not exceed
|
22
|
+
if !@display_length.nil?
|
23
|
+
value = value[0..@display_length-1] if value.length > @display_length
|
24
|
+
end
|
25
|
+
len = @display_length || value.length
|
26
|
+
graphic.printstring r, c, '%-*s' % [len, value], color_pair, attr
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
data/lib/VcData.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'VM'
|
4
|
+
require 'VMFolder'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'vi_api_lib'
|
8
|
+
rescue LoadError => ex
|
9
|
+
require 'Mock'
|
10
|
+
$mock = Mock.new
|
11
|
+
end
|
12
|
+
|
13
|
+
class VcData
|
14
|
+
attr_reader :tree_changed
|
15
|
+
def initialize login, save_data
|
16
|
+
# get rid of certificate warning, why does it have to be on stdout anyway?
|
17
|
+
$stderr.reopen '/dev/null', 'w'
|
18
|
+
url = "https://#{login['host']}/sdk"
|
19
|
+
@connection = $mock || VMware::Connection.new(url, login['username'], login['password'], false)
|
20
|
+
@session = $mock || VMware::Session.new(@connection)
|
21
|
+
@vm_folder = $mock || @session.root_folder.children[0].vm_folder
|
22
|
+
@refs = []
|
23
|
+
@save_data = save_data
|
24
|
+
@tree_changed = false
|
25
|
+
end
|
26
|
+
def collect folder, save_data, indent=''
|
27
|
+
if folder.class.to_s == "VMware::Folder"
|
28
|
+
save_data = [] if !save_data.kind_of? Array
|
29
|
+
i = 0
|
30
|
+
result = VMFolder.new folder, @session, indent+folder['name'], save_data[0] == true
|
31
|
+
folder.children.each { |c| i+=1; result.add_child collect c, save_data[i], indent+' ' }
|
32
|
+
return result
|
33
|
+
end
|
34
|
+
if folder.kind_of? $mock.class and indent == ''
|
35
|
+
res = VMFolder.new folder, @session, indent+folder['name']
|
36
|
+
res.add_child collect $mock.class.new, [], ' '
|
37
|
+
res.add_child collect $mock.class.new, [], ' '
|
38
|
+
return res
|
39
|
+
end
|
40
|
+
VM.new folder, @session, indent+folder['name']
|
41
|
+
end
|
42
|
+
def get sym, index
|
43
|
+
i = @refs[index]
|
44
|
+
i.send sym if i.respond_to? sym
|
45
|
+
end
|
46
|
+
def execute_task index, task
|
47
|
+
@tree_changed = @refs[index].execute_task task
|
48
|
+
end
|
49
|
+
def get_tasks index, tasks
|
50
|
+
tasks.keys.collect { |t| (@refs[index].has_task? tasks[t]) ? t : nil }.compact
|
51
|
+
end
|
52
|
+
def get_colors cached=nil
|
53
|
+
@refs.collect {|i| i.color cached }
|
54
|
+
end
|
55
|
+
def refresh_list dummy=nil
|
56
|
+
@save_data = @folders.save_data if !@folders.nil?
|
57
|
+
@folders = collect @vm_folder, @save_data
|
58
|
+
end
|
59
|
+
def refresh_state dummy=nil
|
60
|
+
@refs.each {|i| i.refresh_state if i.respond_to? :refresh_state }
|
61
|
+
end
|
62
|
+
def vm_list
|
63
|
+
@refs.clear
|
64
|
+
list = []
|
65
|
+
@folders.draw { |i| list << i[0]; @refs << i[1] }
|
66
|
+
list
|
67
|
+
end
|
68
|
+
def save_data
|
69
|
+
@folders.save_data
|
70
|
+
end
|
71
|
+
def recent_events
|
72
|
+
@session.task_manager.recent_tasks.collect do |t|
|
73
|
+
desc = t['name'] || t['descriptionId'] # TODO use task descriptions form TaskManager
|
74
|
+
target = t['entityName']
|
75
|
+
cancelled = t['cancelled'].to_s
|
76
|
+
status = t['state']
|
77
|
+
status = t['error']['localizedMessage'] if status == 'error'
|
78
|
+
user = t['reason']
|
79
|
+
user = user['userName'] || user['name'] || user['alarmName'] || '<System>'
|
80
|
+
start = t['startTime']
|
81
|
+
queue = t['queueTime']
|
82
|
+
complete = t['completeTime']
|
83
|
+
[desc,target,cancelled,status,user,start.to_s]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
data/lib/ViCli.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ViCliConfig'
|
4
|
+
require 'ViGUI'
|
5
|
+
require 'VcData'
|
6
|
+
require 'Watcher'
|
7
|
+
|
8
|
+
class ViCli
|
9
|
+
def main
|
10
|
+
begin
|
11
|
+
@gui = ViGUI.new
|
12
|
+
@config = ViCliConfig.new
|
13
|
+
@config.login = @gui.enter_login if @config.login_needed?
|
14
|
+
@watcher = Watcher.new
|
15
|
+
@model = VcData.new @config.login, @config.tree_state
|
16
|
+
@gui.setup self.method(:on_focus_callback)
|
17
|
+
@gui.set_status "Loading VM Folders ..."
|
18
|
+
@model.refresh_list
|
19
|
+
@gui.set_status "Loaded VM Folders"
|
20
|
+
draw_list
|
21
|
+
on_focus_callback @gui.current_list_index
|
22
|
+
setup_queues
|
23
|
+
main_loop
|
24
|
+
@config.tree_state = @model.save_data
|
25
|
+
rescue => ex
|
26
|
+
ensure
|
27
|
+
@gui.unload if !@gui.nil?
|
28
|
+
puts ex if ex
|
29
|
+
print ex.backtrace.join "\n" if ex
|
30
|
+
end
|
31
|
+
end
|
32
|
+
def draw_list index=nil
|
33
|
+
@gui.draw_list @model.vm_list
|
34
|
+
@gui.colors = @model.get_colors true
|
35
|
+
@watcher.refresh_queue.push index unless index.nil?
|
36
|
+
end
|
37
|
+
def on_focus_callback index
|
38
|
+
tasks = @model.get_tasks index, @config.keys
|
39
|
+
callback = self.method :on_fire_callback
|
40
|
+
@gui.add_tasks callback, index, tasks
|
41
|
+
@watcher.refresh_queue.push index
|
42
|
+
@gui.refresh
|
43
|
+
end
|
44
|
+
def on_fire_callback index, key
|
45
|
+
redraw = @model.execute_task index, @config.keys[key]
|
46
|
+
@gui.set_status "execute #{@config.keys[key]} on #{@model.get :name, index}"
|
47
|
+
@gui.current_list_index
|
48
|
+
draw_list index if redraw
|
49
|
+
end
|
50
|
+
def handle_char char
|
51
|
+
return false if char.nil?
|
52
|
+
return true if char == ?q
|
53
|
+
@activity = true
|
54
|
+
@gui.enter_login if char == ?l
|
55
|
+
@model.refresh_list @gui.current_list_index if char == 269
|
56
|
+
@gui.draw_list @model.vm_list if [269,360].include? char
|
57
|
+
@gui.handle_key char
|
58
|
+
@gui.refresh
|
59
|
+
false
|
60
|
+
end
|
61
|
+
def draw_state state
|
62
|
+
return if state.nil?
|
63
|
+
@gui.colors = state[2]
|
64
|
+
@gui.add_hardware state[1]
|
65
|
+
@gui.add_state state[0]
|
66
|
+
@gui.refresh
|
67
|
+
end
|
68
|
+
def refresh x
|
69
|
+
return if x.nil?
|
70
|
+
@gui.draw_events @model.recent_events
|
71
|
+
@gui.refresh
|
72
|
+
@model.refresh_state
|
73
|
+
@watcher.refresh_queue.push @gui.current_list_index
|
74
|
+
return @activity = false if @activity
|
75
|
+
@gui.set_status ''
|
76
|
+
end
|
77
|
+
def setup_queues
|
78
|
+
#@watcher.register(:resize) { |q| loop { q.push sleep 5 } }
|
79
|
+
@watcher.register(:getchar) { |q| loop { q.push @gui.getchar } }
|
80
|
+
@watcher.register(:sleep) { |q| loop { q.push sleep 5 } }
|
81
|
+
@watcher.register(:state) do |q|
|
82
|
+
loop do
|
83
|
+
q.push @model.get(:state, @watcher.refresh_queue.poop) << @model.get_colors
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
def main_loop
|
88
|
+
while @watcher.wait
|
89
|
+
return if handle_char @watcher[:getchar].poop
|
90
|
+
draw_state @watcher[:state].poop
|
91
|
+
refresh @watcher[:sleep].poop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
data/lib/ViCliConfig.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class ViCliConfig
|
6
|
+
def initialize
|
7
|
+
@config_file = File.expand_path "~/.vi_cli"
|
8
|
+
@config = YAML.load_file @config_file
|
9
|
+
end
|
10
|
+
def tree_state
|
11
|
+
@config['tree_state']
|
12
|
+
end
|
13
|
+
def tree_state= state
|
14
|
+
@config['tree_state'] = state
|
15
|
+
save
|
16
|
+
end
|
17
|
+
def login
|
18
|
+
@config['login']
|
19
|
+
end
|
20
|
+
def login= login
|
21
|
+
@config['login'] = login
|
22
|
+
end
|
23
|
+
def login_needed?
|
24
|
+
@config['login'].nil?
|
25
|
+
end
|
26
|
+
def keys
|
27
|
+
return @config['keys'] unless @config['keys'].nil?
|
28
|
+
@config['keys'] = {
|
29
|
+
'&power on' => 'powerOnVM_Task',
|
30
|
+
'&unfold' => 'unfold',
|
31
|
+
'&reboot guest' => 'rebootGuest',
|
32
|
+
'convert to &virtual machine' => 'markAsVirtualMachine', # doesn't work
|
33
|
+
'cut power' => 'powerOffVM',
|
34
|
+
'&migrate' => 'migrateVM_Task', # doesn't work
|
35
|
+
'f&old' => 'fold',
|
36
|
+
'fold &children' => 'fold_children',
|
37
|
+
'convert to &template' => 'markAsTemplate',
|
38
|
+
'suspend' => 'suspendVM_Task',
|
39
|
+
'reset' => 'resetVM_Task',
|
40
|
+
'&shutdown guest' => 'shutdownGuest',
|
41
|
+
}
|
42
|
+
end
|
43
|
+
def save
|
44
|
+
File.open(@config_file,'w').puts @config.to_yaml
|
45
|
+
end
|
46
|
+
end
|
data/lib/ViGUI.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'VMTreeItemRenderer'
|
4
|
+
require 'ncurses'
|
5
|
+
require 'rbcurse'
|
6
|
+
require 'rbcurse/rlistbox'
|
7
|
+
require 'rbcurse/rtextarea'
|
8
|
+
require 'rbcurse/rtable'
|
9
|
+
require 'rbcurse/rmessagebox'
|
10
|
+
include RubyCurses
|
11
|
+
|
12
|
+
class ViGUI
|
13
|
+
def initialize
|
14
|
+
$log = Log.new # WHY??? XXX
|
15
|
+
VER::start_ncurses
|
16
|
+
@window = VER::Window.root_window
|
17
|
+
@form = Form.new @window
|
18
|
+
@current_list_index = 0
|
19
|
+
@current_list_toprow = 0
|
20
|
+
@colors = []
|
21
|
+
end
|
22
|
+
def enter_login
|
23
|
+
dialog = Form.new nil
|
24
|
+
inputs = ['host','username','password']
|
25
|
+
inputs.each_with_index do |text,i|
|
26
|
+
Label.new dialog do
|
27
|
+
col 4
|
28
|
+
row 4 + i*2
|
29
|
+
text text+":"
|
30
|
+
attr 'reverse'
|
31
|
+
end
|
32
|
+
Field.new dialog do
|
33
|
+
name text
|
34
|
+
col 14
|
35
|
+
row 4 + i*2
|
36
|
+
display_length 41
|
37
|
+
maxlen 99
|
38
|
+
end
|
39
|
+
end
|
40
|
+
h, w = get_h_w
|
41
|
+
MessageBox.new dialog do
|
42
|
+
title 'Login Configuration'
|
43
|
+
layout 14, 60, h/2 - 7, w/2 - 30
|
44
|
+
end
|
45
|
+
Hash[*inputs.collect{|i| [i,dialog.by_name[i].getvalue]}.flatten]
|
46
|
+
end
|
47
|
+
def get_h_w
|
48
|
+
h = []
|
49
|
+
w = []
|
50
|
+
Ncurses.getmaxyx Ncurses.stdscr, h, w
|
51
|
+
return h[0] -2, w[0]
|
52
|
+
end
|
53
|
+
def setup focus_callback
|
54
|
+
@tw = tw = 40
|
55
|
+
h, w = get_h_w
|
56
|
+
h1 = (h - 1)/3
|
57
|
+
h2 = h/4 - 2
|
58
|
+
h3 = h/4 + 1
|
59
|
+
h4 = h - h1 - h2 - h3 - 4
|
60
|
+
@h1 = h1 + h2
|
61
|
+
create_vmlist 0, 0, h, tw
|
62
|
+
@overview = create_widget Table, 'overview', 'Overview', 0, tw, h1, w - tw
|
63
|
+
create_overview w - tw
|
64
|
+
@hardware = create_widget Table, 'hardware', 'Hardware', h1 + 1, tw, h2, w - tw
|
65
|
+
create_hardware w - tw
|
66
|
+
@tasks = create_widget TextArea, 'tasks', 'Tasks', h1 + h2 + 2, tw, h3, w - tw
|
67
|
+
@events = create_widget Table, 'events', 'Events', h1 + h2 + h3 + 3, tw, h4, w - tw
|
68
|
+
create_events w - tw
|
69
|
+
create_status h, tw, 1, w - tw
|
70
|
+
refresh
|
71
|
+
@tree.bind(:ENTER_ROW, focus_callback) do |tree,callback|
|
72
|
+
focus_callback.call tree.current_index
|
73
|
+
end
|
74
|
+
end
|
75
|
+
def add_tasks callback, index, tasks
|
76
|
+
col = @tw + 1
|
77
|
+
row = @h1 + 3
|
78
|
+
# move to a button list class TODO
|
79
|
+
w = @form.widgets.collect { |w| w.class == Button ? w : nil }
|
80
|
+
w.compact.each { |w| @form.remove_widget w }
|
81
|
+
@tasks.paint
|
82
|
+
tasks.each do |task|
|
83
|
+
b = Button.new @form do
|
84
|
+
col col
|
85
|
+
row row
|
86
|
+
text task
|
87
|
+
end
|
88
|
+
b.command { |form| callback.call index, task }
|
89
|
+
row += 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
def add_state state
|
93
|
+
@overview.set_data state, @overview.table_column_model
|
94
|
+
end
|
95
|
+
def add_hardware hardware
|
96
|
+
@hardware.set_data hardware, @hardware.table_column_model
|
97
|
+
end
|
98
|
+
def draw_events events
|
99
|
+
@events.set_data events, @events.table_column_model
|
100
|
+
end
|
101
|
+
def color_callback index
|
102
|
+
@colors[index] || 'grey'
|
103
|
+
end
|
104
|
+
def create_vmlist row, col, h, w
|
105
|
+
renderer = VMTreeItemRenderer.new self, self.method(:color_callback), w-2
|
106
|
+
@tree = Listbox.new @form do
|
107
|
+
name 'vmlist'
|
108
|
+
title 'VMs'
|
109
|
+
row row
|
110
|
+
col col
|
111
|
+
height h
|
112
|
+
width w
|
113
|
+
list_variable Variable.new []
|
114
|
+
cell_renderer renderer
|
115
|
+
end
|
116
|
+
end
|
117
|
+
def create_overview w
|
118
|
+
w1 = w / 2 - w / 4
|
119
|
+
colnames = DefaultTableColumnModel.new %w[key value]
|
120
|
+
colnames.column(0).width w1
|
121
|
+
colnames.column(1).width w - w1 - 3
|
122
|
+
@overview.set_data [], colnames
|
123
|
+
end
|
124
|
+
def create_hardware w
|
125
|
+
w1 = w / 2 - w / 4
|
126
|
+
colnames = DefaultTableColumnModel.new %w[device summary]
|
127
|
+
colnames.column(0).width w1
|
128
|
+
colnames.column(1).width w - w1 - 3
|
129
|
+
@hardware.set_data [], colnames
|
130
|
+
end
|
131
|
+
def create_events w
|
132
|
+
colnames = DefaultTableColumnModel.new %w[name target canceled status user start]
|
133
|
+
@events.set_data [], colnames
|
134
|
+
end
|
135
|
+
def create_widget class_name, name, title, row, col, h, w
|
136
|
+
class_name.new @form do
|
137
|
+
name name
|
138
|
+
title title
|
139
|
+
row row
|
140
|
+
col col
|
141
|
+
height h
|
142
|
+
width w
|
143
|
+
focusable false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
def create_status row, col, h, w
|
147
|
+
@status = Label.new @form do
|
148
|
+
name 'status'
|
149
|
+
row row
|
150
|
+
col col
|
151
|
+
height h
|
152
|
+
display_length w
|
153
|
+
color 'white'
|
154
|
+
bgcolor 'blue'
|
155
|
+
attr 'bold'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
def current_list_index
|
159
|
+
@current_list_toprow = @tree.toprow
|
160
|
+
@current_list_index = @tree.current_index
|
161
|
+
end
|
162
|
+
def unload
|
163
|
+
@window.destroy if !@window.nil?
|
164
|
+
VER::stop_ncurses
|
165
|
+
end
|
166
|
+
def set_status text
|
167
|
+
@status.text = " "+text
|
168
|
+
refresh
|
169
|
+
end
|
170
|
+
def getchar
|
171
|
+
@window.getchar
|
172
|
+
end
|
173
|
+
def handle_key ch
|
174
|
+
@form.handle_key ch
|
175
|
+
end
|
176
|
+
def refresh
|
177
|
+
@form.repaint
|
178
|
+
end
|
179
|
+
def draw_list list
|
180
|
+
@tree.list_data_model.remove_all
|
181
|
+
list.each { |i| @tree.list_data_model.append i }
|
182
|
+
@tree.current_index = @current_list_index
|
183
|
+
@tree.instance_eval "@toprow = #{@current_list_toprow}" # adapt library XXX
|
184
|
+
refresh
|
185
|
+
end
|
186
|
+
def colors= colors
|
187
|
+
@colors = colors
|
188
|
+
@tree.instance_eval "@repaint_required = true" # XXX
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class Log
|
193
|
+
def debug x
|
194
|
+
end
|
195
|
+
end
|
data/lib/Watcher.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'observer'
|
5
|
+
|
6
|
+
class Watcher
|
7
|
+
attr_reader :refresh_queue
|
8
|
+
def initialize
|
9
|
+
@queue = Queue.new
|
10
|
+
@queues = {}
|
11
|
+
@refresh_queue = LastQueue.new
|
12
|
+
end
|
13
|
+
def update
|
14
|
+
@queue.push true
|
15
|
+
end
|
16
|
+
def wait
|
17
|
+
@queue.pop
|
18
|
+
end
|
19
|
+
def [] key
|
20
|
+
@queues[key.to_s]
|
21
|
+
end
|
22
|
+
def register name, &block
|
23
|
+
q = @queues[name.to_s] = ObservedQueue.new(self)
|
24
|
+
Thread.new q, &block
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ObservedQueue < Queue
|
29
|
+
include Observable
|
30
|
+
def initialize observer
|
31
|
+
super()
|
32
|
+
add_observer observer
|
33
|
+
end
|
34
|
+
def push *args
|
35
|
+
super
|
36
|
+
changed
|
37
|
+
notify_observers
|
38
|
+
end
|
39
|
+
def poop
|
40
|
+
return nil if empty?
|
41
|
+
pop
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class LastQueue < Queue
|
46
|
+
def poop
|
47
|
+
while !empty?
|
48
|
+
ret = pop
|
49
|
+
end
|
50
|
+
ret.nil? ? pop : ret
|
51
|
+
end
|
52
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
version = File.open('History.txt').gets[/\d+\.\d+.\d+/]
|
7
|
+
Hoe.new('vi_cli', version) do |p|
|
8
|
+
p.developer('SebDE', 'v1g46nr02@sneakemail.com')
|
9
|
+
p.extra_deps = [['rbcurse','>= 0.1.2']]
|
10
|
+
p.extra_deps = [['vi_api_lib','>= 0.1.1']]
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :clean
|
14
|
+
|
15
|
+
desc "make new history entry with increased major version number"
|
16
|
+
task 'make_major_history' do
|
17
|
+
make_history version, 0
|
18
|
+
end
|
19
|
+
desc "make new history entry with increased minor version number"
|
20
|
+
task 'make_minor_history' do
|
21
|
+
make_history version, 1
|
22
|
+
end
|
23
|
+
desc "make new history entry"
|
24
|
+
task 'make_history' do
|
25
|
+
make_history version, 2
|
26
|
+
end
|
27
|
+
def make_history version, x
|
28
|
+
v = version.split '.'
|
29
|
+
x.upto(2) do |i|
|
30
|
+
v[i] = i==x ? v[i].to_i + 1 : 0
|
31
|
+
end
|
32
|
+
history = File.open('History.txt').readlines
|
33
|
+
new_history = File.open 'History.txt', 'w'
|
34
|
+
new_history.puts "=== #{v.join '.'} / #{Time.new.strftime '%F'}"
|
35
|
+
new_history.puts "\n*\n\n"
|
36
|
+
new_history << history
|
37
|
+
end
|
38
|
+
|
39
|
+
# vim: syntaxx=Ruby
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vi_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- SebDE
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-27 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: vi_api_lib
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.1.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.11.0
|
34
|
+
version:
|
35
|
+
description: vi_cli is an ncurses based commandline client for VMware ESX and ESXi servers
|
36
|
+
email:
|
37
|
+
- v1g46nr02@sneakemail.com
|
38
|
+
executables:
|
39
|
+
- vi_cli
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- History.txt
|
44
|
+
- Manifest.txt
|
45
|
+
- README.txt
|
46
|
+
files:
|
47
|
+
- History.txt
|
48
|
+
- Manifest.txt
|
49
|
+
- README.txt
|
50
|
+
- bin/vi_cli
|
51
|
+
- lib/Mock.rb
|
52
|
+
- lib/VM.rb
|
53
|
+
- lib/VMFolder.rb
|
54
|
+
- lib/VMTreeItemRenderer.rb
|
55
|
+
- lib/VcData.rb
|
56
|
+
- lib/ViCli.rb
|
57
|
+
- lib/ViCliConfig.rb
|
58
|
+
- lib/ViGUI.rb
|
59
|
+
- lib/Watcher.rb
|
60
|
+
- rakefile
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://rubyforge.org/projects/vi-cli/
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --main
|
66
|
+
- README.txt
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: vi_cli
|
84
|
+
rubygems_version: 1.2.0
|
85
|
+
signing_key:
|
86
|
+
specification_version: 2
|
87
|
+
summary: vi_cli is an ncurses based commandline client for VMware ESX and ESXi servers
|
88
|
+
test_files: []
|
89
|
+
|