pencil 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/pencil +1 -1
- data/docs/pencil_options.md +176 -0
- data/lib/config.rb +103 -0
- data/lib/config.ru +2 -0
- data/lib/dash.rb +153 -0
- data/lib/helpers.rb +197 -0
- data/lib/models/base.rb +73 -0
- data/lib/models/dashboard.rb +106 -0
- data/lib/models/graph.rb +304 -0
- data/lib/models/host.rb +82 -0
- data/lib/models.rb +3 -0
- data/lib/namespace.rb +4 -0
- data/lib/public/favicon.ico +0 -0
- data/lib/public/style.css +97 -0
- data/lib/views/cluster.erb +23 -0
- data/lib/views/dash-cluster-zoom.erb +36 -0
- data/lib/views/dash-cluster.erb +18 -0
- data/lib/views/dash-global-zoom.erb +30 -0
- data/lib/views/dash-global.erb +20 -0
- data/lib/views/global.erb +31 -0
- data/lib/views/host.erb +14 -0
- data/lib/views/layout.erb +55 -0
- data/lib/views/partials/cluster_selector.erb +6 -0
- data/lib/views/partials/cluster_switcher.erb +12 -0
- data/lib/views/partials/cookies_form.erb +12 -0
- data/lib/views/partials/dash_switcher.erb +8 -0
- data/lib/views/partials/graph_switcher.erb +8 -0
- data/lib/views/partials/hosts_selector.erb +7 -0
- data/lib/views/partials/input_boxes.erb +25 -0
- data/lib/views/partials/refresh_button.erb +8 -0
- metadata +52 -10
@@ -0,0 +1,106 @@
|
|
1
|
+
require "models/base"
|
2
|
+
require "models/graph"
|
3
|
+
require "models/host"
|
4
|
+
require "set"
|
5
|
+
|
6
|
+
module Dash::Models
|
7
|
+
class Dashboard < Base
|
8
|
+
attr_accessor :graphs
|
9
|
+
attr_accessor :graph_opts
|
10
|
+
|
11
|
+
def initialize(name, params={})
|
12
|
+
super
|
13
|
+
|
14
|
+
@graphs = []
|
15
|
+
@graph_opts = {}
|
16
|
+
params["graphs"].each do |name|
|
17
|
+
# graphs map to option hashes
|
18
|
+
if name.instance_of?(Hash)
|
19
|
+
g = Graph.find(name.keys.first) # should only be one key
|
20
|
+
@graph_opts[g] = name[name.keys.first]||{}
|
21
|
+
else
|
22
|
+
raise "Bad format for graph (must be a hash)"
|
23
|
+
end
|
24
|
+
|
25
|
+
@graphs << g if g
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def clusters
|
31
|
+
clusters = Set.new
|
32
|
+
@graphs.each { |g| clusters += get_valid_hosts(g)[1] }
|
33
|
+
clusters.sort
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_all_hosts(cluster=nil)
|
37
|
+
hosts = Set.new
|
38
|
+
clusters = Set.new
|
39
|
+
@graphs.each do |g|
|
40
|
+
h, c = get_valid_hosts(g, cluster)
|
41
|
+
hosts += h
|
42
|
+
clusters += c
|
43
|
+
end
|
44
|
+
return hosts, clusters
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_valid_hosts(graph, cluster=nil)
|
48
|
+
clusters = Set.new
|
49
|
+
if cluster
|
50
|
+
hosts = Host.find_by_cluster(cluster)
|
51
|
+
else
|
52
|
+
hosts = Host.all
|
53
|
+
hosts.each { |h| clusters << h.cluster }
|
54
|
+
end
|
55
|
+
|
56
|
+
# filter by what matches the graph definition
|
57
|
+
hosts = hosts.select { |h| h.multi_match(graph["hosts"]) }
|
58
|
+
|
59
|
+
# filter if we have a dashboard-level 'hosts' filter
|
60
|
+
if @params["hosts"]
|
61
|
+
hosts = hosts.select { |h| h.multi_match(@params["hosts"]) }
|
62
|
+
end
|
63
|
+
|
64
|
+
hosts.each { |h| clusters << h.cluster }
|
65
|
+
|
66
|
+
return hosts, clusters
|
67
|
+
end
|
68
|
+
|
69
|
+
def render_cluster_graph(graph, clusters, opts={})
|
70
|
+
# FIXME: edge case where the dash filter does not filter to a subset of
|
71
|
+
# the hosts filter
|
72
|
+
|
73
|
+
hosts = get_host_wildcards(graph)
|
74
|
+
opts[:sum] = :cluster unless opts[:zoom]
|
75
|
+
graph_url = graph.render_url(hosts.to_a, clusters, opts)
|
76
|
+
return graph_url
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_host_wildcards(graph)
|
80
|
+
return graph_opts[graph]["hosts"] || @params["hosts"] || graph["hosts"]
|
81
|
+
end
|
82
|
+
|
83
|
+
def render_global_graph(graph, opts={})
|
84
|
+
hosts = get_host_wildcards(graph)
|
85
|
+
_, clusters = get_valid_hosts(graph)
|
86
|
+
|
87
|
+
next_url = ""
|
88
|
+
type = opts[:zoom] ? :cluster : :global
|
89
|
+
options = opts.merge({:sum => type})
|
90
|
+
graph_url = graph.render_url(hosts, clusters, options)
|
91
|
+
return graph_url
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.find_by_graph(graph)
|
95
|
+
ret = []
|
96
|
+
Dashboard.each do |name, dash|
|
97
|
+
|
98
|
+
if dash["graphs"].map { |x| x.keys.first }.member?(graph.name)
|
99
|
+
ret << dash
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
return ret
|
104
|
+
end
|
105
|
+
end # Dash::Models::Dashboard
|
106
|
+
end # Dash::Models
|
data/lib/models/graph.rb
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
require "models/base"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
module Dash::Models
|
5
|
+
class Graph < Base
|
6
|
+
def initialize(name, params={})
|
7
|
+
super
|
8
|
+
|
9
|
+
@params["hosts"] ||= ["*"]
|
10
|
+
@params["title"] ||= name
|
11
|
+
|
12
|
+
if not @params["targets"]
|
13
|
+
raise ArgumentError, "graph #{name} needs a 'targets' map"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# fixme parameters in general
|
18
|
+
def width(opts={})
|
19
|
+
opts["width"] || @params[:url_opts][:width]
|
20
|
+
end
|
21
|
+
|
22
|
+
# translate STR into graphite-speak for applying FUNC to STR
|
23
|
+
# graphite functions take zero or one argument
|
24
|
+
# pass passes STR through, instead of raising an error if FUNC isn't
|
25
|
+
# recognized
|
26
|
+
def translate(func, str, arg=nil, pass=false)
|
27
|
+
# puts "calling translate"
|
28
|
+
# puts "func => #{func}"
|
29
|
+
# puts "str => #{str}"
|
30
|
+
# puts "arg => #{arg}"
|
31
|
+
# procs and lambdas don't support default arguments in 1.8, so I have to
|
32
|
+
# do this
|
33
|
+
z = lambda { |*body| "#{func}(#{body[0]||str})" }
|
34
|
+
y = "#{str}, #{arg}"
|
35
|
+
x = lambda { z.call(y) }
|
36
|
+
|
37
|
+
return \
|
38
|
+
case func.to_s
|
39
|
+
# comb
|
40
|
+
when "sumSeries", "averageSeries", "minSeries", "maxSeries", "group"
|
41
|
+
z.call
|
42
|
+
# transform
|
43
|
+
when "scale", "offset"
|
44
|
+
# perhaps .to_f
|
45
|
+
x.call
|
46
|
+
when "derivative", "integral"
|
47
|
+
z.call
|
48
|
+
when "nonNegativeDerivative"
|
49
|
+
z.call("#{str}#{', ' + arg if arg}")
|
50
|
+
when "log", "timeShift", "summarize", "hitcount",
|
51
|
+
# calculate
|
52
|
+
"movingAverage", "stdev", "asPercent"
|
53
|
+
x.call
|
54
|
+
when "diffSeries", "ratio"
|
55
|
+
z.call
|
56
|
+
# filters
|
57
|
+
when "mostDeviant"
|
58
|
+
z.call("#{arg}, #{str}")
|
59
|
+
when "highestCurrent", "lowestCurrent", "nPercentile", "currentAbove",
|
60
|
+
"currentBelow", "highestAverage", "lowestAverage", "averageAbove",
|
61
|
+
"averageBelow", "maximumAbove", "maximumBelow"
|
62
|
+
x.call
|
63
|
+
when "sortByMaxima", "minimalist"
|
64
|
+
z.call
|
65
|
+
when "limit", "exclude"
|
66
|
+
x.call
|
67
|
+
when "key", "alias"
|
68
|
+
"alias(#{str}, \"#{arg}\")"
|
69
|
+
when "cumulative", "drawAsInfinite"
|
70
|
+
z.call
|
71
|
+
when "lineWidth"
|
72
|
+
x.call
|
73
|
+
when "dashed", "keepLastValue"
|
74
|
+
z.call
|
75
|
+
when "substr", "threshold"
|
76
|
+
x.call
|
77
|
+
when "color"
|
78
|
+
str #color is handled elsewhere
|
79
|
+
else
|
80
|
+
raise "BAD FUNC #{func}" unless pass
|
81
|
+
str
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# inner means we're dealing with a complex key; @params will be applied
|
86
|
+
# later on
|
87
|
+
def handle_metric(name, opts, inner=false)
|
88
|
+
ret = name.dup
|
89
|
+
if inner
|
90
|
+
@params.each do |k, v|
|
91
|
+
ret = translate(k, ret, v, true)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
(opts||{}).each do |k, v|
|
95
|
+
#puts "#{k} => #{v}"
|
96
|
+
ret = translate(k, ret, v)
|
97
|
+
end
|
98
|
+
ret
|
99
|
+
end
|
100
|
+
|
101
|
+
def render_url(hosts, clusters, opts={})
|
102
|
+
opts = {
|
103
|
+
:sum => nil,
|
104
|
+
:title => @params["title"],
|
105
|
+
}.merge(opts)
|
106
|
+
|
107
|
+
if ! [:global, :cluster, nil].member?(opts[:sum])
|
108
|
+
raise ArgumentError, "render graph #{name}: invalid :sum - #{opts[:sum]}"
|
109
|
+
end
|
110
|
+
|
111
|
+
sym_hash = {}
|
112
|
+
(opts[:dynamic_url_opts]||[]).each do |k,v|
|
113
|
+
sym_hash[k.to_sym] = v
|
114
|
+
end
|
115
|
+
|
116
|
+
# fixme key checking may be necessary
|
117
|
+
url_opts = {
|
118
|
+
:title => opts[:title],
|
119
|
+
}.merge(@params[:url_opts]).merge(sym_hash)
|
120
|
+
|
121
|
+
url_opts[:from] = url_opts.delete(:stime) || ""
|
122
|
+
url_opts[:until] = url_opts.delete(:etime) || ""
|
123
|
+
url_opts.delete(:start)
|
124
|
+
url_opts.delete(:duration)
|
125
|
+
|
126
|
+
graphite_opts = [ "vtitle", "yMin", "yMax", "lineWidth", "areaMode",
|
127
|
+
"template", "lineMode", "bgcolor", "graphOnly", "hideAxes", "hideGrid",
|
128
|
+
"hideLegend", "fgcolor", "fontSize", "fontName", "fontItalic",
|
129
|
+
"fontBold" ]
|
130
|
+
|
131
|
+
@params.each do |k, v|
|
132
|
+
if graphite_opts.member?(k)
|
133
|
+
url_opts[k.to_sym] = v
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
target = []
|
138
|
+
colors = []
|
139
|
+
#fixme code duplication
|
140
|
+
if opts[:sum] == :global
|
141
|
+
@params["targets"].each do |stat_name, opts|
|
142
|
+
z = opts.dup
|
143
|
+
# opts['key'] ||= stat_name
|
144
|
+
z[:key] ||= stat_name
|
145
|
+
#######################
|
146
|
+
if stat_name.instance_of?(Array)
|
147
|
+
metric = stat_name.map do |m|
|
148
|
+
mm = compose_metric(m.keys.first, clusters.to_a.join(","),
|
149
|
+
hosts.to_a.join(","))
|
150
|
+
handle_metric(mm, m[m.keys.first], true)
|
151
|
+
end.join(",")
|
152
|
+
else
|
153
|
+
metric = "#{stat_name}.{#{clusters.to_a.join(',')}}" +
|
154
|
+
".{#{hosts.to_a.join(',')}}"
|
155
|
+
metric = handle_metric(metric, {}, true)
|
156
|
+
end
|
157
|
+
#######################
|
158
|
+
z[:key] = "global #{z[:key]}"
|
159
|
+
target << handle_metric("sumSeries(#{metric})", z)
|
160
|
+
colors << next_color(colors, z[:color])
|
161
|
+
end # @params["targets"].each
|
162
|
+
elsif opts[:sum] == :cluster # one line per cluster/metric
|
163
|
+
clusters.each do |cluster|
|
164
|
+
@params["targets"].each do |stat_name, opts|
|
165
|
+
z = opts.dup
|
166
|
+
metrics = []
|
167
|
+
hosts.each do |host|
|
168
|
+
#######################
|
169
|
+
if stat_name.instance_of?(Array)
|
170
|
+
metrics << stat_name.map do |m|
|
171
|
+
mm = compose_metric(m.keys.first, cluster, host)
|
172
|
+
handle_metric(mm, m[m.keys.first], true)
|
173
|
+
end.join(",")
|
174
|
+
else
|
175
|
+
metrics << handle_metric(compose_metric(stat_name, cluster, host), {}, true)
|
176
|
+
end
|
177
|
+
#######################
|
178
|
+
end # hosts.each
|
179
|
+
|
180
|
+
z[:key] = "#{cluster} #{z[:key]}"
|
181
|
+
target << handle_metric("sumSeries(#{metrics.join(',')})", z)
|
182
|
+
colors << next_color(colors, z[:color])
|
183
|
+
end # metrics.each
|
184
|
+
end # clusters.each
|
185
|
+
else # one line per {metric,host,colo}
|
186
|
+
@params["targets"].each do |stat_name, opts|
|
187
|
+
clusters.each do |cluster|
|
188
|
+
hosts.each do |host|
|
189
|
+
label = "#{host} #{opts[:key]}"
|
190
|
+
#################
|
191
|
+
if stat_name.instance_of?(Array)
|
192
|
+
metric = stat_name.map do |m|
|
193
|
+
mm = compose_metric(m.keys.first, cluster, host)
|
194
|
+
handle_metric(mm, m[m.keys.first], true)
|
195
|
+
end.join(",")
|
196
|
+
else
|
197
|
+
metric = handle_metric(compose_metric(stat_name, cluster, host), {}, true)
|
198
|
+
end
|
199
|
+
#################
|
200
|
+
|
201
|
+
if label =~ /\*/
|
202
|
+
# for this particular type of graph, don't display a legend
|
203
|
+
url_opts[:hideLegend] = true
|
204
|
+
z = opts.dup
|
205
|
+
# fixme proper labeling... maybe
|
206
|
+
# With wildcards let graphite construct the legend (or not).
|
207
|
+
# Since we're handling wildcards we don't know how many
|
208
|
+
# hosts will match, so just put in the default color list.
|
209
|
+
# technically we do know, so this can be fixed
|
210
|
+
z.delete(:key)
|
211
|
+
target << handle_metric(metric, z)
|
212
|
+
colors.concat(@params[:default_colors]) if colors.empty?
|
213
|
+
else
|
214
|
+
z = opts.dup
|
215
|
+
z[:key] = "#{host}/#{cluster} #{opts[:key]}"
|
216
|
+
target << handle_metric(metric, z)
|
217
|
+
colors << next_color(colors, opts[:color])
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end # @params["targets"].each
|
222
|
+
end # if opts[:sum]
|
223
|
+
|
224
|
+
url_opts[:target] = target
|
225
|
+
url_opts[:colorList] = colors.join(",")
|
226
|
+
|
227
|
+
url = URI.join(@params[:graphite_url], "/render/?").to_s
|
228
|
+
url_parts = []
|
229
|
+
url_opts.each do |k, v|
|
230
|
+
[v].flatten.each do |v|
|
231
|
+
url_parts << "#{URI.escape(k.to_s)}=#{URI.escape(v.to_s)}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
url += url_parts.join("&")
|
235
|
+
return url
|
236
|
+
end
|
237
|
+
|
238
|
+
# return an array of all metrics matching the specifications in
|
239
|
+
# @params["targets"]
|
240
|
+
# metrics are arrays of fields (once delimited by periods)
|
241
|
+
def expand
|
242
|
+
url = URI.join(@params[:graphite_url], "/metrics/expand/?query=").to_s
|
243
|
+
metrics = []
|
244
|
+
|
245
|
+
@params["targets"].each do |metric|
|
246
|
+
if metric.first.instance_of?(Array)
|
247
|
+
metric.first.each do |m|
|
248
|
+
composed = compose_metric(m.first.first, "*", "*")
|
249
|
+
query = open("#{url}#{composed}").read
|
250
|
+
metrics << JSON.parse(query)["results"]
|
251
|
+
end
|
252
|
+
else
|
253
|
+
composed = compose_metric(metric.first.first, "*", "*")
|
254
|
+
query = open("#{url}#{composed}").read
|
255
|
+
metrics << JSON.parse(query)["results"]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
return metrics.flatten.map { |x| x.split(".") }
|
260
|
+
end
|
261
|
+
|
262
|
+
def hosts_clusters
|
263
|
+
metrics = expand
|
264
|
+
clusters = Set.new
|
265
|
+
|
266
|
+
# field -1 is the host name, and -2 is its cluster
|
267
|
+
hosts = metrics.map do |x|
|
268
|
+
Host.new(x[-1], @params.merge({ "cluster" => x[-2] }))
|
269
|
+
end.uniq
|
270
|
+
|
271
|
+
# filter by what matches the graph definition
|
272
|
+
hosts = hosts.select { |h| h.multi_match(@params["hosts"]) }
|
273
|
+
hosts.each { |h| clusters << h.cluster }
|
274
|
+
|
275
|
+
return hosts, clusters
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
def next_color(colors, preferred_color=nil)
|
280
|
+
default_colors = @params[:default_colors].clone
|
281
|
+
|
282
|
+
if preferred_color and !colors.member?(preferred_color)
|
283
|
+
return preferred_color
|
284
|
+
end
|
285
|
+
|
286
|
+
if preferred_color and ! default_colors.member?(preferred_color)
|
287
|
+
default_colors << preferred_color
|
288
|
+
end
|
289
|
+
|
290
|
+
weights = Hash.new { |h, k| h[k] = 0 }
|
291
|
+
colors.each do |c|
|
292
|
+
weights[c] += 1
|
293
|
+
end
|
294
|
+
|
295
|
+
i = 0
|
296
|
+
loop do
|
297
|
+
default_colors.each do |c|
|
298
|
+
return c if weights[c] == i
|
299
|
+
end
|
300
|
+
i += 1
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end # Dash::Models::Graph
|
304
|
+
end # Dash::Models
|
data/lib/models/host.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require "models/base"
|
2
|
+
require "models/graph"
|
3
|
+
|
4
|
+
module Dash::Models
|
5
|
+
class Host < Base
|
6
|
+
attr_accessor :graphs
|
7
|
+
|
8
|
+
def initialize(name, params={})
|
9
|
+
super
|
10
|
+
|
11
|
+
@graphs = []
|
12
|
+
Graph.each do |graph_name, graph|
|
13
|
+
graph["hosts"].each do |h|
|
14
|
+
if match(h)
|
15
|
+
@graphs << graph
|
16
|
+
break
|
17
|
+
end
|
18
|
+
end # graph["hosts"].each
|
19
|
+
end # Graph.each
|
20
|
+
end
|
21
|
+
|
22
|
+
def cluster
|
23
|
+
return @params["cluster"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def key
|
27
|
+
"#{@cluster}#{@name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def eql?(other)
|
31
|
+
key == other.key
|
32
|
+
end
|
33
|
+
|
34
|
+
def ==(other)
|
35
|
+
key == other.key
|
36
|
+
end
|
37
|
+
|
38
|
+
def <=>(other)
|
39
|
+
if @params[:host_sort] == "builtin"
|
40
|
+
return key <=> other.key
|
41
|
+
elsif @params[:host_sort] == "numeric"
|
42
|
+
regex = /\d+/
|
43
|
+
match = @name.match(regex)
|
44
|
+
match2 = other.name.match(regex)
|
45
|
+
if match.pre_match != match2.pre_match
|
46
|
+
return match.pre_match <=> match2.pre_match
|
47
|
+
else
|
48
|
+
return match[0].to_i <=> match2[0].to_i
|
49
|
+
end
|
50
|
+
else
|
51
|
+
# http://www.bofh.org.uk/2007/12/16/comprehensible-sorting-in-ruby
|
52
|
+
sensible = lambda do |k|
|
53
|
+
k.to_s.split(
|
54
|
+
/((?:(?:^|\s)[-+])?(?:\.\d+|\d+(?:\.\d+?(?:[eE]\d+)?(?:$|(?![eE\.])))?))/ms
|
55
|
+
).map { |v| Float(v) rescue v.downcase }
|
56
|
+
end
|
57
|
+
return sensible.call(self) <=> sensible.call(other)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def hash
|
62
|
+
key.hash
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.find_by_name_and_cluster(name, cluster)
|
66
|
+
Host.each do |host_name, host|
|
67
|
+
next unless host_name = name
|
68
|
+
return host if host.cluster == cluster
|
69
|
+
end
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.find_by_cluster(cluster)
|
74
|
+
ret = []
|
75
|
+
Host.each do |name, host|
|
76
|
+
ret << host if host.cluster == cluster
|
77
|
+
end
|
78
|
+
return ret
|
79
|
+
end
|
80
|
+
|
81
|
+
end # Dash::Models::Host
|
82
|
+
end # Dash::Models
|
data/lib/models.rb
ADDED
data/lib/namespace.rb
ADDED
Binary file
|
@@ -0,0 +1,97 @@
|
|
1
|
+
h2 {
|
2
|
+
color: #ADFF2F;
|
3
|
+
}
|
4
|
+
|
5
|
+
h3 {
|
6
|
+
color: #32cd32;
|
7
|
+
}
|
8
|
+
|
9
|
+
.graphtitle {
|
10
|
+
text-shadow: 1px 1px 1px #333333;
|
11
|
+
}
|
12
|
+
|
13
|
+
.dashlinks {
|
14
|
+
color: green;
|
15
|
+
font-size: small;
|
16
|
+
}
|
17
|
+
|
18
|
+
a {
|
19
|
+
color: #15317E;
|
20
|
+
}
|
21
|
+
|
22
|
+
a:visited {
|
23
|
+
color: #15317E;
|
24
|
+
}
|
25
|
+
|
26
|
+
.graphsection {
|
27
|
+
border: dotted 2px #333333;
|
28
|
+
width: 500;
|
29
|
+
margin-left: 5px;
|
30
|
+
margin-top: 10px;
|
31
|
+
}
|
32
|
+
|
33
|
+
div#wrap {
|
34
|
+
width: 100%;
|
35
|
+
margin-top: 10px;
|
36
|
+
margin-bottom: 10px;
|
37
|
+
margin-left: auto;
|
38
|
+
margin-right: auto;
|
39
|
+
padding: 0px;
|
40
|
+
}
|
41
|
+
|
42
|
+
div#header {
|
43
|
+
padding: 15px;
|
44
|
+
margin: 0px;
|
45
|
+
margin-left: 23%;
|
46
|
+
}
|
47
|
+
|
48
|
+
div#nav {
|
49
|
+
width: 20%;
|
50
|
+
padding: 10px;
|
51
|
+
margin-top: 1px;
|
52
|
+
float: left;
|
53
|
+
color: green;
|
54
|
+
}
|
55
|
+
|
56
|
+
div#main {
|
57
|
+
margin-left: 23%;
|
58
|
+
margin-top: 1px;
|
59
|
+
padding: 10px;
|
60
|
+
color: #4CBB17;
|
61
|
+
}
|
62
|
+
|
63
|
+
div#footer {
|
64
|
+
padding: 15px;
|
65
|
+
margin: 0px;
|
66
|
+
border-top: thin solid #808080;
|
67
|
+
}
|
68
|
+
|
69
|
+
table {
|
70
|
+
color: green;
|
71
|
+
}
|
72
|
+
|
73
|
+
input {
|
74
|
+
border: solid 1px #15317E;
|
75
|
+
color: green;
|
76
|
+
background-color: #1f1f1f;
|
77
|
+
font-size: small;
|
78
|
+
}
|
79
|
+
|
80
|
+
.invisible {
|
81
|
+
display: none;
|
82
|
+
}
|
83
|
+
|
84
|
+
select {
|
85
|
+
border: solid 1px #15317E;
|
86
|
+
color: green;
|
87
|
+
background-color: #1f1f1f;
|
88
|
+
}
|
89
|
+
|
90
|
+
.select2 {
|
91
|
+
color: #4CBB17;
|
92
|
+
font-size: large;
|
93
|
+
}
|
94
|
+
|
95
|
+
.error {
|
96
|
+
color: red;
|
97
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h2>List of dashboards for <%= cluster_selector %></h2>
|
2
|
+
|
3
|
+
<% hosts = Host.all.select { |h| h.cluster == @cluster }
|
4
|
+
boards = @dashboards.select { |d| d.clusters.member?(@cluster) }
|
5
|
+
seen_hosts = Set.new %>
|
6
|
+
|
7
|
+
<% boards.each do |b| %>
|
8
|
+
<h2><a href="/dash/<%= "#{@cluster}/#{append_query_string(b.name)}" %>"><%= b.name %></a>
|
9
|
+
<% dash_hosts = b.get_all_hosts(@cluster)[0]
|
10
|
+
seen_hosts += dash_hosts %>
|
11
|
+
<%= hosts_selector(dash_hosts) %>
|
12
|
+
</h2>
|
13
|
+
<%= b.graphs.collect do |g|
|
14
|
+
href = append_query_string("/dash/#{@cluster}/#{b.name}/#{g}")
|
15
|
+
"<li><a href=\"#{href}\">#{g}</a><br></li>"
|
16
|
+
end %>
|
17
|
+
<br>
|
18
|
+
<% end %>
|
19
|
+
<h3>Other Hosts (not associated with a dashboard)</h3>
|
20
|
+
<%= (hosts.to_set - seen_hosts).sort.collect do |h|
|
21
|
+
href = append_query_string("/host/#{@cluster}/#{h}")
|
22
|
+
"<li><a href=\"#{href}\">#{h}</a></li>"
|
23
|
+
end %>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<% hosts = @dash.get_valid_hosts(@zoom, @cluster)[0].sort %>
|
2
|
+
|
3
|
+
<%= cluster_switcher %>
|
4
|
+
<%= graph_switcher %>
|
5
|
+
<%= graph_uplink %>
|
6
|
+
<br>
|
7
|
+
<br>
|
8
|
+
<a href="#by_host">by_host</a>
|
9
|
+
<%= hosts.collect { |h| "<a href=\"##{h}\">#{h}</a>" }.join(" ") %>
|
10
|
+
|
11
|
+
<div class="graphsection" style="width:<%= @zoom.width(merge_opts) %>;">
|
12
|
+
<span class="graphtitle"><%= @zoom.name %> / <%= @cluster %> :: summary</span>
|
13
|
+
<div class="graph">
|
14
|
+
<img src="<%= @dash.render_cluster_graph(@zoom, @cluster, :dynamic_url_opts => merge_opts) %>">
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="graphsection" style="width:<%= @zoom.width(merge_opts) %>;">
|
19
|
+
<a name="by_host">
|
20
|
+
<span class="graphtitle"><%= @zoom.name %> / <%= @cluster %> :: by host</span>
|
21
|
+
<div class="graph">
|
22
|
+
<img src="<%= @dash.render_cluster_graph(@zoom, @cluster, :zoom => true, :dynamic_url_opts => merge_opts) %>">
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<% hosts.each do |host| %>
|
27
|
+
<div class="graphsection" style="width:<%= @zoom.width(merge_opts) %>;">
|
28
|
+
<a name="<%= host %>">
|
29
|
+
<% image_url, zoom_url = cluster_zoom_graph(@zoom, @cluster, host, "#{@zoom.name} / #{@cluster} / #{host}") %>
|
30
|
+
<span class="graphtitle"><%= @zoom.name %> / <%= @cluster %> / <%= host %></span>
|
31
|
+
<span class="dashlinks">(<a href="<%= zoom_url %>">host</a>)</span>
|
32
|
+
<div class="graph">
|
33
|
+
<a href="<%= zoom_url %>"><img src="<%= image_url %>"></a>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%= cluster_switcher %>
|
2
|
+
<%= dash_switcher %>
|
3
|
+
<%= dash_uplink %>
|
4
|
+
<br>
|
5
|
+
<br>
|
6
|
+
<%= @dash.graphs.collect { |g| "<a href=\"##{g.name}\">#{g.name}</a>" }.join(" ") %>
|
7
|
+
|
8
|
+
<% @dash.graphs.each do |g| %>
|
9
|
+
<div class="graphsection" style="width:<%= g.width(merge_opts) %>;">
|
10
|
+
<a name="<%= g.name %>">
|
11
|
+
<% image_url, zoom_url = cluster_graph(g, @cluster, "#{g.name} / #{@cluster}") %>
|
12
|
+
<span class="graphtitle"><%= g.name %></span>
|
13
|
+
<span class="dashlinks">(<a href="<%= zoom_url %>">zoom</a>)</span>
|
14
|
+
<div class="graph">
|
15
|
+
<a href="<%= zoom_url %>"><img src="<%= image_url %>"></a>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
<% end %>
|