arborist 0.2.0.pre20170519125456 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +670 -1
- data/History.md +67 -0
- data/Manifest.txt +9 -6
- data/README.md +1 -3
- data/Rakefile +39 -4
- data/TODO.md +22 -31
- data/lib/arborist.rb +9 -2
- data/lib/arborist/cli.rb +67 -85
- data/lib/arborist/client.rb +125 -59
- data/lib/arborist/command/ack.rb +86 -0
- data/lib/arborist/command/reset.rb +48 -0
- data/lib/arborist/command/start.rb +11 -1
- data/lib/arborist/command/summary.rb +173 -0
- data/lib/arborist/command/tree.rb +215 -0
- data/lib/arborist/command/watch.rb +22 -22
- data/lib/arborist/dependency.rb +24 -4
- data/lib/arborist/event.rb +18 -2
- data/lib/arborist/event/node.rb +6 -2
- data/lib/arborist/event/node_warn.rb +16 -0
- data/lib/arborist/manager.rb +179 -48
- data/lib/arborist/mixins.rb +11 -0
- data/lib/arborist/monitor.rb +29 -17
- data/lib/arborist/monitor/connection_batching.rb +293 -0
- data/lib/arborist/monitor/socket.rb +101 -167
- data/lib/arborist/monitor_runner.rb +101 -24
- data/lib/arborist/node.rb +297 -68
- data/lib/arborist/node/ack.rb +1 -1
- data/lib/arborist/node/host.rb +26 -5
- data/lib/arborist/node/resource.rb +14 -5
- data/lib/arborist/node/root.rb +12 -3
- data/lib/arborist/node/service.rb +29 -26
- data/lib/arborist/node_subscription.rb +65 -0
- data/lib/arborist/observer.rb +8 -0
- data/lib/arborist/observer/action.rb +6 -0
- data/lib/arborist/subscription.rb +22 -16
- data/lib/arborist/tree_api.rb +7 -2
- data/spec/arborist/client_spec.rb +157 -51
- data/spec/arborist/dependency_spec.rb +21 -0
- data/spec/arborist/event/node_spec.rb +5 -0
- data/spec/arborist/event_spec.rb +3 -3
- data/spec/arborist/manager_spec.rb +626 -347
- data/spec/arborist/mixins_spec.rb +19 -0
- data/spec/arborist/monitor/socket_spec.rb +1 -2
- data/spec/arborist/monitor_runner_spec.rb +81 -29
- data/spec/arborist/monitor_spec.rb +89 -14
- data/spec/arborist/node/host_spec.rb +68 -0
- data/spec/arborist/node/resource_spec.rb +2 -0
- data/spec/arborist/node/root_spec.rb +13 -0
- data/spec/arborist/node/service_spec.rb +9 -0
- data/spec/arborist/node_spec.rb +673 -111
- data/spec/arborist/node_subscription_spec.rb +54 -0
- data/spec/arborist/observer/action_spec.rb +6 -0
- data/spec/arborist/observer_runner_spec.rb +8 -1
- data/spec/arborist/tree_api_spec.rb +111 -8
- data/spec/data/monitors/pings.rb +0 -11
- data/spec/data/monitors/port_checks.rb +0 -9
- data/spec/data/nodes/sidonie.rb +1 -0
- data/spec/data/nodes/vhosts.rb +23 -0
- data/spec/data/nodes/yevaud.rb +4 -2
- data/spec/spec_helper.rb +71 -1
- metadata +91 -28
- metadata.gz.sig +0 -0
- data/Events.md +0 -35
- data/Monitors.md +0 -155
- data/Nodes.md +0 -70
- data/Observers.md +0 -72
- data/Protocol.md +0 -276
- data/Tutorial.md +0 -8
@@ -0,0 +1,173 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'time'
|
5
|
+
require 'tty/table'
|
6
|
+
|
7
|
+
require 'arborist/cli' unless defined?( Arborist::CLI )
|
8
|
+
require 'arborist/client'
|
9
|
+
|
10
|
+
|
11
|
+
# Command to fetch down/acked/disabled nodes for quick display.
|
12
|
+
module Arborist::CLI::Summary
|
13
|
+
extend Arborist::CLI::Subcommand
|
14
|
+
using Arborist::TimeRefinements
|
15
|
+
|
16
|
+
BANNER = [
|
17
|
+
' _ _ _',
|
18
|
+
' __ _ _ _| |__ ___ _ _(_)__| |',
|
19
|
+
'/ _` | \'_| \'_ \\/ _ \\ \'_| (_-< _|',
|
20
|
+
'\\__,_|_| |_.__/\\___/_| |_/__/\\__| %s, %s nodes',
|
21
|
+
]
|
22
|
+
|
23
|
+
desc 'Summarize known problems'
|
24
|
+
|
25
|
+
command :summary do |cmd|
|
26
|
+
|
27
|
+
cmd.flag [:s, :sort],
|
28
|
+
type: String,
|
29
|
+
desc: "Sort output by this node key",
|
30
|
+
arg_name: 'sort',
|
31
|
+
default_value: 'status_changed'
|
32
|
+
|
33
|
+
cmd.action do |globals, options, args|
|
34
|
+
client = Arborist::Client.new
|
35
|
+
status = client.status
|
36
|
+
nodes = client.fetch
|
37
|
+
|
38
|
+
down = get_status( nodes, 'down' )
|
39
|
+
warning = get_status( nodes, 'warn' )
|
40
|
+
acked = get_status( nodes, 'acked' )
|
41
|
+
disabled = get_status( nodes, 'disabled' )
|
42
|
+
quieted = get_status( nodes, 'quieted' )
|
43
|
+
problems = ! ( down.size + acked.size + disabled.size + warning.size ).zero?
|
44
|
+
|
45
|
+
prompt.say "Connected to: %s" % [ highlight_string(client.tree_api_url) ]
|
46
|
+
prompt.say "Status as of: %s" % [ hl.on_blue.bright_white(Time.now.to_s) ]
|
47
|
+
|
48
|
+
(0..2).each do |i|
|
49
|
+
prompt.say "%s" % [ hl.bold.bright_green( BANNER[i] ) ]
|
50
|
+
end
|
51
|
+
prompt.say hl.bold.bright_green( BANNER.last ) % [
|
52
|
+
highlight_string(status['server_version']),
|
53
|
+
highlight_string(status['nodecount'])
|
54
|
+
]
|
55
|
+
|
56
|
+
puts
|
57
|
+
if problems
|
58
|
+
output_problems( disabled, acked, down, quieted, warning, options[:sort] )
|
59
|
+
else
|
60
|
+
prompt.say success_string( "No problems found!" )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
###############
|
68
|
+
module_function
|
69
|
+
###############
|
70
|
+
|
71
|
+
### Since we fetch all nodes instead of doing separate
|
72
|
+
### API searches, quickly return nodes of a given +status+.
|
73
|
+
def get_status( nodes, status )
|
74
|
+
return nodes.select{|n| n['status'] == status}
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
### Output all problems.
|
79
|
+
###
|
80
|
+
def output_problems( disabled, acked, down, quieted, warning, sort )
|
81
|
+
unless disabled.size.zero?
|
82
|
+
prompt.say hl.headline( "Disabled Nodes" )
|
83
|
+
display_table( *format_acked(disabled, sort) )
|
84
|
+
puts
|
85
|
+
end
|
86
|
+
unless acked.size.zero?
|
87
|
+
prompt.say hl.headline( "Acknowledged Outages" )
|
88
|
+
display_table( *format_acked(acked, sort) )
|
89
|
+
puts
|
90
|
+
end
|
91
|
+
unless warning.size.zero?
|
92
|
+
prompt.say hl.headline( "Warnings" )
|
93
|
+
header = [
|
94
|
+
highlight_string( 'identifier' ),
|
95
|
+
highlight_string( 'type' ),
|
96
|
+
highlight_string( 'when' ),
|
97
|
+
highlight_string( 'warnings' )
|
98
|
+
]
|
99
|
+
|
100
|
+
display_table( header, format_warn(warning, sort) )
|
101
|
+
puts
|
102
|
+
end
|
103
|
+
unless down.size.zero?
|
104
|
+
prompt.say hl.headline( "Current Outages" )
|
105
|
+
header = [
|
106
|
+
highlight_string( 'identifier' ),
|
107
|
+
highlight_string( 'type' ),
|
108
|
+
highlight_string( 'when' ),
|
109
|
+
highlight_string( 'errors' )
|
110
|
+
]
|
111
|
+
|
112
|
+
display_table( header, format_down(down, sort) )
|
113
|
+
prompt.say "%d nodes have been %s as a result of the above problems." % [
|
114
|
+
quieted.size,
|
115
|
+
hl.quieted( 'quieted' )
|
116
|
+
]
|
117
|
+
puts
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
### Prepare an array of acked/disabled nodes.
|
123
|
+
def format_acked( nodes, sort_key )
|
124
|
+
header = [
|
125
|
+
highlight_string( 'identifier' ),
|
126
|
+
highlight_string( 'type' ),
|
127
|
+
highlight_string( 'when' ),
|
128
|
+
highlight_string( 'who' ),
|
129
|
+
highlight_string( 'message' )
|
130
|
+
]
|
131
|
+
|
132
|
+
rows = nodes.sort_by{|n| n[sort_key] }.each_with_object([]) do |node, acc|
|
133
|
+
acc << [
|
134
|
+
hl.disabled( node['identifier'] ),
|
135
|
+
node[ 'type' ],
|
136
|
+
Time.parse( node[ 'status_changed' ] ).as_delta,
|
137
|
+
node[ 'ack' ][ 'sender' ],
|
138
|
+
node[ 'ack' ][ 'message' ]
|
139
|
+
]
|
140
|
+
end
|
141
|
+
return header, rows
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
### Prepare an array of down nodes.
|
146
|
+
def format_down( nodes, sort_key )
|
147
|
+
return nodes.sort_by{|n| n[sort_key] }.each_with_object([]) do |node, acc|
|
148
|
+
errors = node[ 'errors' ].map{|err| "%s: %s" % [ err.first, err.last ]}
|
149
|
+
acc << [
|
150
|
+
hl.down( node['identifier'] ),
|
151
|
+
node[ 'type' ],
|
152
|
+
Time.parse( node[ 'status_changed' ] ).as_delta,
|
153
|
+
errors.join( "\n" )
|
154
|
+
]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
### Prepare an array of nodes in a warning state.
|
160
|
+
def format_warn( nodes, sort_key )
|
161
|
+
return nodes.sort_by{|n| n[sort_key] }.each_with_object([]) do |node, acc|
|
162
|
+
warnings = node[ 'warnings' ].map{|err| "%s: %s" % [ err.first, err.last ]}
|
163
|
+
acc << [
|
164
|
+
hl.warn( node['identifier'] ),
|
165
|
+
node[ 'type' ],
|
166
|
+
Time.parse( node[ 'status_changed' ] ).as_delta,
|
167
|
+
warnings.join( "\n" )
|
168
|
+
]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end # module Arborist::CLI::Summary
|
173
|
+
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'pp'
|
5
|
+
require 'msgpack'
|
6
|
+
require 'tty-tree'
|
7
|
+
|
8
|
+
require 'arborist/cli' unless defined?( Arborist::CLI )
|
9
|
+
require 'arborist/client'
|
10
|
+
|
11
|
+
|
12
|
+
# Command to dump the node tree of a running Arborist manager
|
13
|
+
module Arborist::CLI::Tree
|
14
|
+
extend Arborist::CLI::Subcommand
|
15
|
+
|
16
|
+
desc 'Dump the node tree of the running manager'
|
17
|
+
|
18
|
+
command :tree do |cmd|
|
19
|
+
cmd.switch :raw,
|
20
|
+
desc: "Dump the node tree data as raw data instead of prettifying it.",
|
21
|
+
negatable: false
|
22
|
+
cmd.switch :path,
|
23
|
+
desc: "Include the parent path back to root, when using --from.",
|
24
|
+
negatable: false
|
25
|
+
|
26
|
+
cmd.flag [:f, :from],
|
27
|
+
type: String,
|
28
|
+
desc: "Start at a node other than the root.",
|
29
|
+
arg_name: 'identifier'
|
30
|
+
cmd.flag [:e, :depth],
|
31
|
+
type: Integer,
|
32
|
+
desc: "Limit the depth of the fetched tree.",
|
33
|
+
arg_name: 'integer'
|
34
|
+
|
35
|
+
cmd.action do |globals, options, args|
|
36
|
+
client = Arborist::Client.instance
|
37
|
+
|
38
|
+
opts = { tree: true }
|
39
|
+
opts[ :from ] = options[ :from ] if options[ :from ]
|
40
|
+
opts[ :depth ] = options[ :depth ] if options[ :depth ]
|
41
|
+
|
42
|
+
nodes = client.fetch( opts )
|
43
|
+
|
44
|
+
if options[:raw]
|
45
|
+
pp nodes.first
|
46
|
+
|
47
|
+
else
|
48
|
+
status = client.status
|
49
|
+
prompt.say "Arborist Manager %s {%s} [%s nodes] (uptime: %s secs)\n\n" % [
|
50
|
+
highlight_string( status['server_version'] ),
|
51
|
+
highlight_string( client.tree_api_url ),
|
52
|
+
highlight_string( status['nodecount'] ),
|
53
|
+
highlight_string( "%d" % status['uptime'] )
|
54
|
+
]
|
55
|
+
|
56
|
+
root = nodes.first
|
57
|
+
root_data = {}
|
58
|
+
|
59
|
+
# Recursively fetch each parent node upwards to the
|
60
|
+
# Arborist root.
|
61
|
+
#
|
62
|
+
tree_data = if options[ :path ] && options[ :from ]
|
63
|
+
path_to_root = fetch_parents( root )
|
64
|
+
{ node_description( path_to_root.shift ) => build_path( path_to_root, root ) }
|
65
|
+
|
66
|
+
# Just display starting at the specified root.
|
67
|
+
#
|
68
|
+
else
|
69
|
+
{ node_description(root) => root_data }
|
70
|
+
end
|
71
|
+
|
72
|
+
root[ 'children' ].each_value do |node|
|
73
|
+
root_data[ node_description(node) ] = build_tree( node )
|
74
|
+
end
|
75
|
+
|
76
|
+
tree = TTY::Tree.new( tree_data )
|
77
|
+
prompt.say tree.render( indent: 4 )
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
###############
|
85
|
+
module_function
|
86
|
+
###############
|
87
|
+
|
88
|
+
#### Reorganize the node data to format used by TTY::Tree.
|
89
|
+
def build_tree( node )
|
90
|
+
return [] if node[ 'children' ].empty?
|
91
|
+
|
92
|
+
children = []
|
93
|
+
node[ 'children' ].each_value do |child|
|
94
|
+
children << { node_description(child) => build_tree(child) }
|
95
|
+
end
|
96
|
+
return children
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
### Given a sorted array of +nodes+, reorganize it for TTY::Tree.
|
101
|
+
def build_path( nodes, root )
|
102
|
+
children = []
|
103
|
+
parent_node = nodes.shift
|
104
|
+
return children unless parent_node
|
105
|
+
|
106
|
+
if parent_node == root
|
107
|
+
children << { node_description(parent_node) => build_tree(parent_node) }
|
108
|
+
else
|
109
|
+
children << { node_description(parent_node) => build_path(nodes, root) }
|
110
|
+
end
|
111
|
+
return children
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
### Given a starting node, walk upwards through the tree until
|
116
|
+
### reaching the Arborist root node. Returns an array of nodes,
|
117
|
+
### sorted root down.
|
118
|
+
def fetch_parents( start_node )
|
119
|
+
client = Arborist::Client.instance
|
120
|
+
path = [ start_node ]
|
121
|
+
parent = start_node[ 'parent' ]
|
122
|
+
while parent
|
123
|
+
parent_node = client.fetch_node( parent )
|
124
|
+
path << parent_node
|
125
|
+
parent = parent_node[ 'parent' ]
|
126
|
+
end
|
127
|
+
return path.reverse
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
### Return a description of the specified +node+.
|
132
|
+
def node_description( node )
|
133
|
+
desc = ""
|
134
|
+
|
135
|
+
case node['type']
|
136
|
+
when 'root'
|
137
|
+
desc << "%s" % [ hl.bold.bright_blue(node['type']) ]
|
138
|
+
else
|
139
|
+
desc << highlight_string( node['identifier'] )
|
140
|
+
desc << " %s" % [ hl.dark.white(node['type']) ]
|
141
|
+
end
|
142
|
+
|
143
|
+
desc << " [%s]" % [ node['description'] ] unless
|
144
|
+
!node['description'] || node['description'].empty?
|
145
|
+
desc << " (%s)" % [ status_description(node) ]
|
146
|
+
|
147
|
+
child_count = node[ 'children' ].length
|
148
|
+
desc << " [%d child node%s" % [
|
149
|
+
child_count, child_count == 1 ? ']' : 's]'
|
150
|
+
] unless child_count.zero?
|
151
|
+
|
152
|
+
case node['status']
|
153
|
+
when 'down'
|
154
|
+
desc << errors_description( node )
|
155
|
+
when 'warn'
|
156
|
+
desc << warnings_description( node )
|
157
|
+
when 'quieted'
|
158
|
+
desc << quieted_reasons_description( node )
|
159
|
+
when 'acked'
|
160
|
+
desc << ack_description( node )
|
161
|
+
desc << "; was: "
|
162
|
+
desc << errors_description( node )
|
163
|
+
end
|
164
|
+
|
165
|
+
return desc
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
### Return a more colorful description of the status of the given +node+.
|
170
|
+
def status_description( node )
|
171
|
+
status = node['status'] or return '-'
|
172
|
+
return hl.decorate( status, status.to_sym ) rescue status
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
### Return the errors from the specified +node+ in a single line.
|
177
|
+
def errors_description( node )
|
178
|
+
errors = node['errors'] or return ''
|
179
|
+
return ' ' + errors.map do |monid, error|
|
180
|
+
"%s: %s" % [ monid, error ]
|
181
|
+
end.join( '; ' )
|
182
|
+
end
|
183
|
+
|
184
|
+
### Return the warnings from the specified +node+ in a single line.
|
185
|
+
def warnings_description( node )
|
186
|
+
warnings = node['warnings'] or return ''
|
187
|
+
return ' ' + warnings.map do |monid, error|
|
188
|
+
"%s: %s" % [ monid, error ]
|
189
|
+
end.join( '; ' )
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
### Return the quieted reasons from the specified +node+ in a single line.
|
194
|
+
def quieted_reasons_description( node )
|
195
|
+
reasons = node['quieted_reasons'] or return ''
|
196
|
+
return ' ' + reasons.map do |depname, reason|
|
197
|
+
"%s: %s" % [ depname, reason ]
|
198
|
+
end.join( '; ' )
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
### Return a description of the acknowledgement from the node.
|
203
|
+
def ack_description( node )
|
204
|
+
ack = node['ack'] or return '(no ack)'
|
205
|
+
|
206
|
+
return " Acked by %s at %s%s: %s" % [
|
207
|
+
ack['sender'],
|
208
|
+
ack['time'],
|
209
|
+
ack['via'] ? ' via ' + ack['via'] : '',
|
210
|
+
ack['message']
|
211
|
+
]
|
212
|
+
end
|
213
|
+
|
214
|
+
end # module Arborist::CLI::Tree
|
215
|
+
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
+
require 'tty/spinner'
|
4
5
|
require 'msgpack'
|
5
6
|
|
6
7
|
require 'arborist/cli' unless defined?( Arborist::CLI )
|
@@ -27,6 +28,9 @@ module Arborist::CLI::Watch
|
|
27
28
|
prompt.say "Subscribing to manager heartbeat events."
|
28
29
|
sock.subscribe( 'sys.heartbeat' )
|
29
30
|
|
31
|
+
spinner = TTY::Spinner.new( "[ :spinner ] waiting for events...\r",
|
32
|
+
frames: HEARTBEAT_CHARACTERS, hide_cursor: true )
|
33
|
+
|
30
34
|
begin
|
31
35
|
last_runid = nil
|
32
36
|
prompt.say "Watching for events on manager at %s" % [ client.event_api_url ]
|
@@ -47,22 +51,22 @@ module Arborist::CLI::Watch
|
|
47
51
|
self.log.debug "Manager is alive (runid: %s)" % [ this_runid ]
|
48
52
|
end
|
49
53
|
|
50
|
-
|
54
|
+
spinner.spin
|
51
55
|
last_runid = this_runid
|
52
56
|
when 'sys.node_added'
|
53
57
|
prompt.say "[%s] «Node added» %s\n" % [
|
54
|
-
hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')
|
55
|
-
hl(event['node']
|
58
|
+
hl.dark.white( Time.now.strftime('%Y-%m-%d %H:%M:%S %Z') ),
|
59
|
+
hl.bold.cyan( event['node'] )
|
56
60
|
]
|
57
61
|
when 'sys.node_removed'
|
58
62
|
prompt.say "[%s] »Node removed« %s\n" % [
|
59
|
-
hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')
|
60
|
-
hl(event['node']
|
63
|
+
hl.dark.white( Time.now.strftime('%Y-%m-%d %H:%M:%S %Z') ),
|
64
|
+
hl.dark.cyan( event['node'] )
|
61
65
|
]
|
62
66
|
else
|
63
67
|
prompt.say "[%s] %s\n" % [
|
64
|
-
hl(Time.now.strftime('%Y-%m-%d %H:%M:%S %Z')
|
65
|
-
hl(dump_event( event )
|
68
|
+
hl.dark.white( Time.now.strftime('%Y-%m-%d %H:%M:%S %Z') ),
|
69
|
+
hl.dark.white( dump_event( event ) )
|
66
70
|
]
|
67
71
|
end
|
68
72
|
end
|
@@ -97,42 +101,38 @@ module Arborist::CLI::Watch
|
|
97
101
|
when 'node.update'
|
98
102
|
type, status, errors = event['data'].values_at( *%w'type status errors' )
|
99
103
|
return "%s updated: %s is %s%s" % [
|
100
|
-
hl( id )
|
104
|
+
hl.cyan( id ),
|
101
105
|
type,
|
102
|
-
hl( status
|
106
|
+
hl.decorate( status, status.to_sym ),
|
103
107
|
errors ? " (#{errors})" : ''
|
104
108
|
]
|
105
109
|
when 'node.delta'
|
106
110
|
pairs = diff_pairs( event['data'] )
|
107
|
-
return "%s delta, changes: %s" % [ hl( id )
|
111
|
+
return "%s delta, changes: %s" % [ hl.cyan( id ), pairs ]
|
108
112
|
else
|
109
|
-
return "%s event: %p" % [ hl
|
113
|
+
return "%s event: %p" % [ hl.dark.white( event_type ), event ]
|
110
114
|
end
|
111
115
|
end
|
112
116
|
|
113
117
|
|
114
118
|
### Return a string showing the differences in a delta event's change +data+.
|
115
119
|
def diff_pairs( data )
|
116
|
-
|
120
|
+
diff = data.collect do |key, pairs|
|
117
121
|
if pairs.is_a?( Hash )
|
118
122
|
diff_pairs( pairs )
|
119
123
|
else
|
120
124
|
val1, val2 = *pairs
|
121
125
|
"%s: %s -> %s" % [
|
122
|
-
hl( key )
|
123
|
-
hl( val1 )
|
124
|
-
hl( val2 )
|
126
|
+
hl.dark.white( key ),
|
127
|
+
hl.yellow( val1 ),
|
128
|
+
hl.bold.yellow( val2 )
|
125
129
|
]
|
126
130
|
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
131
|
+
end
|
130
132
|
|
131
|
-
|
132
|
-
def heartbeat
|
133
|
-
idx = (Time.now.to_i % HEARTBEAT_CHARACTERS.length) - 1
|
134
|
-
return " " + HEARTBEAT_CHARACTERS[ idx ] + "\x08\x08"
|
133
|
+
return hl.dark.white( diff.join(', ') )
|
135
134
|
end
|
136
135
|
|
136
|
+
|
137
137
|
end # module Arborist::CLI::Watch
|
138
138
|
|