sensu-dashboard 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.org +4 -0
  5. data/Rakefile +1 -0
  6. data/bin/sensu-dashboard +9 -0
  7. data/lib/sensu-dashboard/app.rb +275 -0
  8. data/lib/sensu-dashboard/public/css/autoSuggest.css +217 -0
  9. data/lib/sensu-dashboard/public/css/style.css +177 -0
  10. data/lib/sensu-dashboard/public/img/footer_bg.png +0 -0
  11. data/lib/sensu-dashboard/public/img/header_bg.png +0 -0
  12. data/lib/sensu-dashboard/public/img/loading_circle.gif +0 -0
  13. data/lib/sensu-dashboard/public/img/main_content_bg.png +0 -0
  14. data/lib/sensu-dashboard/public/img/megaphone_icon.png +0 -0
  15. data/lib/sensu-dashboard/public/img/megaphone_icon_off.png +0 -0
  16. data/lib/sensu-dashboard/public/js/FABridge.js +604 -0
  17. data/lib/sensu-dashboard/public/js/functions.js +198 -0
  18. data/lib/sensu-dashboard/public/js/jquery-1.5.1.min.js +16 -0
  19. data/lib/sensu-dashboard/public/js/jquery.autoSuggest.js +375 -0
  20. data/lib/sensu-dashboard/public/js/jquery.leanModal.min.js +1 -0
  21. data/lib/sensu-dashboard/public/js/jquery.sortElements.js +69 -0
  22. data/lib/sensu-dashboard/public/js/jquery.tmpl.min.js +1 -0
  23. data/lib/sensu-dashboard/public/js/jquery.zclip.min.js +12 -0
  24. data/lib/sensu-dashboard/public/js/modernizr-1.7.min.js +2 -0
  25. data/lib/sensu-dashboard/public/js/swfobject.js +4 -0
  26. data/lib/sensu-dashboard/public/js/web_socket.js +312 -0
  27. data/lib/sensu-dashboard/public/swf/WebSocketMain.swf +0 -0
  28. data/lib/sensu-dashboard/public/swf/ZeroClipboard.swf +0 -0
  29. data/lib/sensu-dashboard/version.rb +5 -0
  30. data/lib/sensu-dashboard/views/client_templates.erb +29 -0
  31. data/lib/sensu-dashboard/views/clients.erb +23 -0
  32. data/lib/sensu-dashboard/views/event_templates.erb +142 -0
  33. data/lib/sensu-dashboard/views/index.erb +41 -0
  34. data/lib/sensu-dashboard/views/layout.erb +120 -0
  35. data/lib/sensu-dashboard/views/sonian.sass +206 -0
  36. data/sensu-dashboard.gemspec +25 -0
  37. metadata +163 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ .sass-cache/
3
+ .gem
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sensu-dashboard.gemspec
4
+ gemspec
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Sonian Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,4 @@
1
+ * Sonian Monitoring Dashboard
2
+ - Lists current alerts from the monitoring server
3
+ - Lists clients subscribed to AMQP queues
4
+ - Uses WebSockets to receive new information
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'sensu-dashboard/app.rb'
4
+ rescue LoadError => e
5
+ require 'rubygems'
6
+ path = File.expand_path '../../lib', __FILE__
7
+ $:.unshift(path) if File.directory?(path) && !$:.include?(path)
8
+ require 'sensu-dashboard/app.rb'
9
+ end
@@ -0,0 +1,275 @@
1
+ require 'eventmachine'
2
+ require 'sinatra/async'
3
+ require 'em-http-request'
4
+ require 'em-websocket'
5
+ require 'sass'
6
+ require 'json'
7
+ require 'sensu/config'
8
+
9
+ options = Sensu::Config.read_arguments(ARGV)
10
+ config = Sensu::Config.new(options)
11
+ SETTINGS = config.settings
12
+
13
+ EventMachine.run do
14
+
15
+ class DashboardServer < Sinatra::Base
16
+
17
+ register Sinatra::Async
18
+ set :root, File.dirname(__FILE__)
19
+ set :static, true
20
+ set :public_folder, Proc.new { File.join(root, "public") }
21
+
22
+ api_server = 'http://' + SETTINGS['api']['host'] + ':' + SETTINGS['api']['port'].to_s
23
+
24
+ use Rack::Auth::Basic do |user, password|
25
+ user == SETTINGS['dashboard']['user'] && password == SETTINGS['dashboard']['password']
26
+ end
27
+
28
+ before do
29
+ content_type 'application/json'
30
+ end
31
+
32
+ aget '/' do
33
+ content_type 'text/html'
34
+ @js = erb :event_templates, :layout => false
35
+ body erb :index
36
+ end
37
+
38
+ aget '/clients' do
39
+ content_type 'text/html'
40
+ @js = erb :client_templates, :layout => false
41
+ body erb :clients
42
+ end
43
+
44
+ aget '/css/sonian.css' do
45
+ content_type 'text/css'
46
+ body sass :sonian
47
+ end
48
+
49
+ # api proxy
50
+ aget '/events.json' do
51
+ begin
52
+ http = EventMachine::HttpRequest.new("#{api_server}/events").get
53
+ rescue => e
54
+ puts e
55
+ status 404
56
+ body '{"error":"could not retrieve events from the sensu api"}'
57
+ end
58
+
59
+ http.errback do
60
+ status 404
61
+ body '{"error":"could not retrieve events from the sensu api"}'
62
+ end
63
+
64
+ http.callback do
65
+ status http.response_header.status
66
+ body http.response
67
+ end
68
+ end
69
+
70
+ # api proxy
71
+ aget '/autocomplete.json' do
72
+ multi = EventMachine::MultiRequest.new
73
+
74
+ requests = [
75
+ "#{api_server}/events",
76
+ "#{api_server}/clients"
77
+ ]
78
+
79
+ requests.each do |url|
80
+ multi.add EventMachine::HttpRequest.new(url).get
81
+ end
82
+
83
+ multi.callback do
84
+ events = {}
85
+ clients = []
86
+
87
+ multi.responses[:succeeded].each do |request|
88
+ body = JSON.parse(request.response)
89
+ case body
90
+ when Hash
91
+ events = body
92
+ when Array
93
+ clients = body
94
+ end
95
+ end
96
+
97
+ if events && clients
98
+ autocomplete = []
99
+ statuses = {:warning => [], :critical => [], :unknown => []}
100
+ subscriptions = {}
101
+
102
+ clients.each do |client|
103
+ client_name = client['name']
104
+ if events.include?(client_name)
105
+ autocomplete.push({:value => client_name, :name => client_name})
106
+ client['subscriptions'].each do |subscription|
107
+ subscriptions[subscription] ||= []
108
+ subscriptions[subscription].push(client_name)
109
+ end
110
+ events[client_name].each do |check, event|
111
+ case event["status"]
112
+ when 1
113
+ statuses[:warning].push(client_name)
114
+ when 2
115
+ statuses[:critical].push(client_name)
116
+ else
117
+ statuses[:unknown].push(client_name)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # searching by subscription
124
+ subscriptions.each do |k, v|
125
+ autocomplete.push({:value => v.join(','), :name => k})
126
+ end
127
+
128
+ # searching by status
129
+ statuses.each do |k, v|
130
+ autocomplete.push({:value => v.join(','), :name => k})
131
+ end
132
+
133
+ body autocomplete.to_json
134
+ else
135
+ status 404
136
+ body '{"error":"could not retrieve events and/or clients from the sensu api"}'
137
+ end
138
+ end
139
+ end
140
+
141
+ aget '/clients.json' do
142
+ begin
143
+ http = EventMachine::HttpRequest.new("#{api_server}/clients").get
144
+ rescue => e
145
+ puts e
146
+ status 404
147
+ body '{"error":"could not retrieve clients from the sensu api"}'
148
+ end
149
+
150
+ http.errback do
151
+ status 404
152
+ body '{"error":"could not retrieve clients from the sensu api"}'
153
+ end
154
+
155
+ http.callback do
156
+ status http.response_header.status
157
+ body http.response
158
+ end
159
+ end
160
+
161
+ aget '/client/:id.json' do |id|
162
+ begin
163
+ http = EventMachine::HttpRequest.new("#{api_server}/client/#{id}").get
164
+ rescue => e
165
+ puts e
166
+ status 404
167
+ body '{"error":"could not retrieve clients from the sensu api"}'
168
+ end
169
+
170
+ http.errback do
171
+ status 404
172
+ body '{"error":"could not retrieve clients from the sensu api"}'
173
+ end
174
+
175
+ http.callback do
176
+ status http.response_header.status
177
+ body http.response
178
+ end
179
+ end
180
+
181
+ aget '/stash/*.json' do |path|
182
+ begin
183
+ http = EventMachine::HttpRequest.new("#{api_server}/stash/#{path}").get
184
+ rescue => e
185
+ puts e
186
+ status 404
187
+ body '{"error":"could not retrieve a stash from the sensu api"}'
188
+ end
189
+
190
+ http.errback do
191
+ status 404
192
+ body '{"error":"could not retrieve a stash from the sensu api"}'
193
+ end
194
+
195
+ http.callback do
196
+ status http.response_header.status
197
+ body http.response
198
+ end
199
+ end
200
+
201
+ apost '/stash/*.json' do |path|
202
+ begin
203
+ http = EventMachine::HttpRequest.new("#{api_server}/stash/#{path}").post :body => request.body.read
204
+ rescue => e
205
+ puts e
206
+ status 404
207
+ body '{"error":"could not create a stash with the sensu api"}'
208
+ end
209
+
210
+ http.errback do
211
+ status 404
212
+ body '{"error":"could not create a stash with the sensu api"}'
213
+ end
214
+
215
+ http.callback do
216
+ status http.response_header.status
217
+ body http.response
218
+ end
219
+ end
220
+
221
+ adelete '/stash/*.json' do |path|
222
+ begin
223
+ http = EventMachine::HttpRequest.new("#{api_server}/stash/#{path}").delete
224
+ rescue => e
225
+ puts e
226
+ status 404
227
+ body '{"error":"could not delete a stash with the sensu api"}'
228
+ end
229
+
230
+ http.errback do
231
+ status 404
232
+ body '{"error":"could not delete a stash with the sensu api"}'
233
+ end
234
+
235
+ http.callback do
236
+ status http.response_header.status
237
+ body http.response
238
+ end
239
+ end
240
+
241
+ websocket_connections = Array.new
242
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 9000) do |websocket|
243
+ websocket.onopen do
244
+ websocket_connections.push websocket
245
+ puts 'client connected to websocket'
246
+ end
247
+ websocket.onclose do
248
+ websocket_connections.delete websocket
249
+ puts 'client disconnected from websocket'
250
+ end
251
+ end
252
+
253
+ apost '/events.json' do
254
+ unless websocket_connections.empty?
255
+ websocket_connections.each do |websocket|
256
+ websocket.send '{"update":"true"}'
257
+ end
258
+ end
259
+ body '{"success":"triggered dashboard refresh"}'
260
+ end
261
+ end
262
+
263
+ DashboardServer.run!({:port => SETTINGS['dashboard']['port']})
264
+
265
+ #
266
+ # Recognize exit command
267
+ #
268
+ Signal.trap("INT") do
269
+ EM.stop
270
+ end
271
+ Signal.trap("TERM") do
272
+ EM.stop
273
+ end
274
+
275
+ end
@@ -0,0 +1,217 @@
1
+ /* AutoSuggest CSS - Version 1.2 */
2
+
3
+ ul.as-selections {
4
+ list-style-type: none;
5
+ border-top: 1px solid #888;
6
+ border-bottom: 1px solid #b6b6b6;
7
+ border-left: 1px solid #aaa;
8
+ border-right: 1px solid #aaa;
9
+ padding: 4px 0 4px 4px;
10
+ margin: 0;
11
+ overflow: auto;
12
+ background-color: #fff;
13
+ box-shadow:inset 0 1px 2px #888;
14
+ -webkit-box-shadow:inset 0 1px 2px #888;
15
+ -moz-box-shadow:inset 0 1px 2px #888;
16
+ }
17
+
18
+ ul.as-selections.loading {
19
+ background-color: #eee;
20
+ }
21
+
22
+ ul.as-selections li {
23
+ float: left;
24
+ margin: 1px 4px 1px 0;
25
+ }
26
+
27
+ ul.as-selections li.as-selection-item {
28
+ color: #2b3840;
29
+ font-size: 13px;
30
+ font-family: "Lucida Grande", arial, sans-serif;
31
+ text-shadow: 0 1px 1px #fff;
32
+ background-color: #ddeefe;
33
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ddeefe), to(#bfe0f1));
34
+ border: 1px solid #acc3ec;
35
+ border-top-color: #c0d9e9;
36
+ padding: 2px 7px 2px 10px;
37
+ border-radius: 12px;
38
+ -webkit-border-radius: 12px;
39
+ -moz-border-radius: 12px;
40
+ box-shadow: 0 1px 1px #e4edf2;
41
+ -webkit-box-shadow: 0 1px 1px #e4edf2;
42
+ -moz-box-shadow: 0 1px 1px #e4edf2;
43
+ }
44
+
45
+ ul.as-selections li.as-selection-item:last-child {
46
+ margin-left: 30px;
47
+ }
48
+
49
+ ul.as-selections li.as-selection-item a.as-close {
50
+ float: right;
51
+ margin: 1px 0 0 7px;
52
+ padding: 0 2px;
53
+ cursor: pointer;
54
+ color: #5491be;
55
+ font-family: "Helvetica", helvetica, arial, sans-serif;
56
+ font-size: 14px;
57
+ font-weight: bold;
58
+ text-shadow: 0 1px 1px #fff;
59
+ -webkit-transition: color .1s ease-in;
60
+ }
61
+
62
+ ul.as-selections li.as-selection-item.blur {
63
+ color: #666666;
64
+ background-color: #f4f4f4;
65
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f4f4f4), to(#d5d5d5));
66
+ border-color: #bbb;
67
+ border-top-color: #ccc;
68
+ box-shadow: 0 1px 1px #e9e9e9;
69
+ -webkit-box-shadow: 0 1px 1px #e9e9e9;
70
+ -moz-box-shadow: 0 1px 1px #e9e9e9;
71
+ }
72
+
73
+ ul.as-selections li.as-selection-item.blur a.as-close {
74
+ color: #999;
75
+ }
76
+
77
+ ul.as-selections li:hover.as-selection-item {
78
+ color: #2b3840;
79
+ background-color: #bbd4f1;
80
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bbd4f1), to(#a3c2e5));
81
+ border-color: #6da0e0;
82
+ border-top-color: #8bb7ed;
83
+ }
84
+
85
+ ul.as-selections li:hover.as-selection-item a.as-close {
86
+ color: #4d70b0;
87
+ }
88
+
89
+ ul.as-selections li.as-selection-item.selected {
90
+ border-color: #1f30e4;
91
+ }
92
+
93
+ ul.as-selections li.as-selection-item a:hover.as-close {
94
+ color: #1b3c65;
95
+ }
96
+
97
+ ul.as-selections li.as-selection-item a:active.as-close {
98
+ color: #4d70b0;
99
+ }
100
+
101
+ ul.as-selections li.as-original {
102
+ margin-left: 0;
103
+ }
104
+
105
+ ul.as-selections li.as-original input {
106
+ border: none;
107
+ outline: none;
108
+ font-size: 13px;
109
+ width: 120px;
110
+ height: 18px;
111
+ padding-top: 3px;
112
+ }
113
+
114
+ ul.as-list {
115
+ position: absolute;
116
+ list-style-type: none;
117
+ margin: 2px 0 0 0;
118
+ padding: 0;
119
+ font-size: 14px;
120
+ color: #000;
121
+ font-family: "Lucida Grande", arial, sans-serif;
122
+ background-color: #fff;
123
+ background-color: rgba(255,255,255,0.95);
124
+ z-index: 2;
125
+ box-shadow: 0 2px 12px #222;
126
+ -webkit-box-shadow: 0 2px 12px #222;
127
+ -moz-box-shadow: 0 2px 12px #222;
128
+ border-radius: 5px;
129
+ -webkit-border-radius: 5px;
130
+ -moz-border-radius: 5px;
131
+ }
132
+
133
+ li.as-result-item, li.as-message {
134
+ margin: 0 0 0 0;
135
+ padding: 5px 12px;
136
+ background-color: transparent;
137
+ border: 1px solid #fff;
138
+ border-bottom: 1px solid #ddd;
139
+ cursor: pointer;
140
+ border-radius: 5px;
141
+ -webkit-border-radius: 5px;
142
+ -moz-border-radius: 5px;
143
+ }
144
+
145
+ li:first-child.as-result-item {
146
+ margin: 0;
147
+ }
148
+
149
+ li.as-message {
150
+ margin: 0;
151
+ cursor: default;
152
+ }
153
+
154
+ li.as-result-item.active {
155
+ background-color: #3668d9;
156
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 64%, from(rgb(110, 129, 245)), to(rgb(62, 82, 242)));
157
+ border-color: #3342e8;
158
+ color: #fff;
159
+ text-shadow: 0 1px 2px #122042;
160
+ }
161
+
162
+ li.as-result-item em {
163
+ font-style: normal;
164
+ background: #444;
165
+ padding: 0 2px;
166
+ color: #fff;
167
+ }
168
+
169
+ li.as-result-item.active em {
170
+ background: #253f7a;
171
+ color: #fff;
172
+ }
173
+
174
+ /* Webkit Hacks */
175
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
176
+ ul.as-selections {
177
+ border-top-width: 2px;
178
+ }
179
+ ul.as-selections li.as-selection-item {
180
+ padding-top: 3px;
181
+ padding-bottom: 3px;
182
+ }
183
+ ul.as-selections li.as-selection-item a.as-close {
184
+ margin-top: -1px;
185
+ }
186
+ ul.as-selections li.as-original input {
187
+ height: 19px;
188
+ }
189
+ }
190
+
191
+ /* Opera Hacks */
192
+ @media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0) {
193
+ ul.as-list {
194
+ border: 1px solid #888;
195
+ }
196
+ ul.as-selections li.as-selection-item a.as-close {
197
+ margin-left: 4px;
198
+ margin-top: 0;
199
+ }
200
+ }
201
+
202
+ /* IE Hacks */
203
+ ul.as-list {
204
+ border: 1px solid #888\9;
205
+ }
206
+ ul.as-selections li.as-selection-item a.as-close {
207
+ margin-left: 4px\9;
208
+ margin-top: 0\9;
209
+ }
210
+
211
+ /* Firefox 3.0 Hacks */
212
+ ul.as-list, x:-moz-any-link, x:default {
213
+ border: 1px solid #888;
214
+ }
215
+ BODY:first-of-type ul.as-list, x:-moz-any-link, x:default { /* Target FF 3.5+ */
216
+ border: none;
217
+ }