aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2011-06-09 18:09:52 +0100
committerPatrick J Cherry <patrick@bytemark.co.uk>2011-06-09 18:09:52 +0100
commit495c44445642cfae8f23fadde299ad5307f5be58 (patch)
tree0104c9eef164235aa5ab05b126c8f63e52fb8624
parent0c88fcc91db1b003cd5d5311f62700c7867b4099 (diff)
Big commit
--HG-- rename : views/please_authenticate.haml => views/login.haml
-rw-r--r--.hgtags5
-rwxr-xr-xbin/mauveclient1
-rwxr-xr-xbytemark_example_alerts.sh24
-rw-r--r--debian/changelog10
-rw-r--r--debian/mauvealert-server.install1
-rw-r--r--lib/mauve/alert.rb82
-rw-r--r--lib/mauve/alert_group.rb11
-rw-r--r--lib/mauve/auth_bytemark.rb10
-rw-r--r--lib/mauve/calendar_interface.rb8
-rw-r--r--lib/mauve/configuration.rb14
-rw-r--r--lib/mauve/http_server.rb16
-rw-r--r--lib/mauve/notification.rb7
-rw-r--r--lib/mauve/notifier.rb31
-rw-r--r--lib/mauve/notifiers/email.rb22
-rw-r--r--lib/mauve/notifiers/xmpp.rb462
-rw-r--r--lib/mauve/people_list.rb6
-rw-r--r--lib/mauve/person.rb45
-rw-r--r--lib/mauve/server.rb52
-rw-r--r--lib/mauve/source_list.rb2
-rw-r--r--lib/mauve/timer.rb8
-rw-r--r--lib/mauve/udp_server.rb10
-rw-r--r--lib/mauve/web_interface.rb220
l---------static/common1
-rw-r--r--static/images/BytemarkLogo180.pngbin0 -> 4982 bytes
l---------static/javascript1
-rw-r--r--static/mauve_utils.js152
-rw-r--r--static/mauve_utils.js.old150
-rw-r--r--static/stylesheets/bytemark.css183
-rw-r--r--static/stylesheets/mauve.css41
-rw-r--r--views/_detail.haml3
-rw-r--r--views/_header.haml9
-rw-r--r--views/_navbar.haml15
-rw-r--r--views/_navigation.haml2
-rw-r--r--views/alert.haml12
-rw-r--r--views/alerts.haml84
-rw-r--r--views/login.haml27
-rw-r--r--views/please_authenticate.haml25
37 files changed, 1263 insertions, 489 deletions
diff --git a/.hgtags b/.hgtags
new file mode 100644
index 0000000..d15cef9
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,5 @@
+aaab1eab14333f0de39bf1a6763ecfc70bb04a75 0.13
+f552a7440699fbe85523bf97e334cdee627773b3 0.17
+83f5ecdbdcd8c93c7e38780887a968642b924974 1.0.0.beta
+429621063b4e3833832b2ee2cc637178d72e9125 1.0.beta2
+0f9cca9cb958c339e9e3c987909f2abe10911824 Jruby compatible version
diff --git a/bin/mauveclient b/bin/mauveclient
index 1c90741..b51d687 100755
--- a/bin/mauveclient
+++ b/bin/mauveclient
@@ -162,7 +162,6 @@ if help
exit 0
end
-
error "No alerts specified" unless !update.alert.empty? || update.replace
update.transmission_id = rand(2**63)
diff --git a/bytemark_example_alerts.sh b/bytemark_example_alerts.sh
index e0d9205..6e90c5c 100755
--- a/bytemark_example_alerts.sh
+++ b/bytemark_example_alerts.sh
@@ -1,17 +1,17 @@
#!/bin/sh
-PRE="./mauve_starter.rb ./bin/mauvesend 127.0.0.1 "
+PRE="ruby -I lib ./bin/mauveclient 127.0.0.1 "
$PRE -o supportbot -i 173123 \
- -s "My server is not responding" \
- -d "<strong>From:</strong> John Smith &lt;john@smith.name><br/>
-<strong>To:</strong> support@support.bytemark.co.uk</br/>
-<br/>
-<pre>It has been several hours now since I have been able to contact my server
-foo.bar.bytemark.co.uk. I am very upset that blah blah blah blah
-and furthermore by business is under threat because &pound;15.00 per month
-is far too much blah blah blah</pre>
-"
+ -s "My server is not responding" \
+ -d "<strong>From:</strong> John Smith &lt;john@smith.name><br/>
+#<strong>To:</strong> support@support.bytemark.co.uk</br/>
+#<br/>
+#<pre>It has been several hours now since I have been able to contact my server
+#foo.bar.bytemark.co.uk. I am very upset that blah blah blah blah
+#and furthermore by business is under threat because &pound;15.00 per month
+#is far too much blah blah blah</pre>
+#"
$PRE -o networkmonitor -i 1 -u cr01.man.bytemark.co.uk \
-s "cr01.man.bytemark.co.uk did not respond to pings"
@@ -23,8 +23,10 @@ $PRE -o networkmonitor -i 2 -u cr01.thn.bytemark.co.uk \
$PRE -o vmhs -i 12346 -u ventham.bytemark.co.uk \
-s "ventham.bytemark.co.uk heartbeat not received" -r +5
+
$PRE -o vmhs -i 12345 -u partridge.bytemark.co.uk \
- -s "partridge.bytemark.co.uk heartbeat not received" -r +2
+ -s "partridge.bytemark.co.uk heartbeat not received" -r +10 -c now
+
$PRE -o vmhs -i 12347 -u eider.bytemark.co.uk \
-s "eider.bytemark.co.uk heartbeat not received" -r +2
diff --git a/debian/changelog b/debian/changelog
index d8b7263..cdd758a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+mauvealert (3.0.3) stable; urgency=low
+
+ * Added get_all back to front-end
+ * Fixed up logging for more classes
+ * Catch empty notification lists
+ * Added console to server package
+ * Fixed document_root location
+
+ -- Patrick J Cherry <patrick@bytemark.co.uk> Tue, 19 Apr 2011 11:31:10 +0100
+
mauvealert (3.0.2) stable; urgency=low
* Now checks for transmission time set to zero.
diff --git a/debian/mauvealert-server.install b/debian/mauvealert-server.install
index 47a3add..c23596d 100644
--- a/debian/mauvealert-server.install
+++ b/debian/mauvealert-server.install
@@ -1,4 +1,5 @@
bin/mauveserver usr/sbin/
+bin/mauveconsole usr/sbin/
lib/dm-sqlite-adapter-with-mutex.rb usr/lib/ruby/1.8/
lib/mauve/alert.rb usr/lib/ruby/1.8/mauve/
lib/mauve/alert_changed.rb usr/lib/ruby/1.8/mauve/
diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb
index 30d50bb..b98866c 100644
--- a/lib/mauve/alert.rb
+++ b/lib/mauve/alert.rb
@@ -103,7 +103,7 @@ module Mauve
def logger
Log4r::Logger.new(self.class.to_s)
end
-
+
def time_relative(secs)
secs = secs.to_i.abs
case secs
@@ -167,6 +167,10 @@ module Mauve
def alert_group
AlertGroup.matches(self)[0]
end
+
+ def level
+ self.alert_group.level
+ end
def subject
attribute_get(:subject) || source
@@ -174,7 +178,8 @@ module Mauve
def subject=(subject); set_changed_if_different(:subject, subject); end
def summary=(summary); set_changed_if_different(:summary, summary); end
- def detail=(detail); set_changed_if_different(:detail, detail); end
+# def detail=(detail); set_changed_if_different(:detail, detail); end
+ def detail=(detail); attribute_set(:detail, detail) ; end
protected
def set_changed_if_different(attribute, value)
@@ -252,10 +257,33 @@ module Mauve
class << self
- def all_current
- all(:cleared_at => nil)
+ def all_raised
+ all(:raised_at.not => nil, :cleared_at => nil)
end
-
+
+ def all_acknowledged
+ all(:acknowledged_at.not => nil)
+ end
+
+ def all_cleared
+ all(:cleared_at.not => nil)
+ end
+
+ # Returns a hash of all the :urgent, :normal and :low alerts.
+ #
+ # @return [Hash] A hash with the relevant alerts per level
+ def get_all ()
+ hash = Hash.new
+ hash[:urgent] = Array.new
+ hash[:normal] = Array.new
+ hash[:low] = Array.new
+ all().each do |iter|
+ next if true == iter.cleared?
+ hash[AlertGroup.matches(iter)[0].level] << iter
+ end
+ return hash
+ end
+
# Returns the next Alert that will have a timed action due on it, or nil
# if none are pending.
#
@@ -273,8 +301,9 @@ module Mauve
# Receive an AlertUpdate buffer from the wire.
#
def receive_update(update, reception_time = MauveTime.now)
- update = Proto::AlertUpdate.parse_from_string(update) unless
- update.kind_of?(Proto::AlertUpdate)
+
+ update = Proto::AlertUpdate.parse_from_string(update) unless update.kind_of?(Proto::AlertUpdate)
+
alerts_updated = []
logger.debug("Alert update received from wire: #{update.inspect.split.join(", ")}")
@@ -289,7 +318,8 @@ module Mauve
end
time_offset = (reception_time - transmission_time).round
- logger.debug("Update received from a host #{time_offset}s behind") if time_offset.abs > 0
+
+ logger.debug("Update received from a host #{time_offset}s behind") if time_offset.abs > 5
# Update each alert supplied
#
@@ -297,8 +327,17 @@ module Mauve
# Infer some actions from our pure data structure (hmm, wonder if
# this belongs in our protobuf-derived class?
#
- raise_time = alert.raise_time == 0 ? nil : MauveTime.at(alert.raise_time + time_offset)
clear_time = alert.clear_time == 0 ? nil : MauveTime.at(alert.clear_time + time_offset)
+ raise_time = alert.raise_time == 0 ? nil : MauveTime.at(alert.raise_time + time_offset)
+
+ if raise_time.nil? && clear_time.nil?
+ #
+ # Make sure that we raise if neither raise nor clear is set
+ #
+ logger.warn("No clear time or raise time set. Assuming raised!")
+
+ raise_time = reception_time
+ end
logger.debug("received at #{reception_time}, transmitted at #{transmission_time}, raised at #{raise_time}, clear at #{clear_time}")
@@ -316,10 +355,10 @@ module Mauve
##
#
- # Allow a 15s offset in timings.
+ # Allow a 5s offset in timings.
#
if raise_time
- if raise_time <= (reception_time + 15)
+ if raise_time <= (reception_time + 5)
alert_db.raised_at = raise_time
else
alert_db.will_raise_at = raise_time
@@ -327,19 +366,24 @@ module Mauve
end
if clear_time
- if clear_time <= (reception_time + 15)
+ if clear_time <= (reception_time + 5)
alert_db.cleared_at = clear_time
else
alert_db.will_clear_at = clear_time
end
end
- # re-raise
+ #
+ # Re-raise if raised_at and cleared_at are set.
+ #
if alert_db.cleared_at && alert_db.raised_at && alert_db.cleared_at < alert_db.raised_at
alert_db.cleared_at = nil
end
- if pre_cleared && alert_db.raised?
+ #
+ #
+ #
+ if (pre_raised or pre_cleared) && alert_db.raised?
alert_db.update_type = :raised
elsif pre_raised && alert_db.cleared?
alert_db.update_type = :cleared
@@ -355,8 +399,8 @@ module Mauve
# These updates happen but do not sent the alert back to the
# notification system.
#
- alert_db.importance = alert.importance if alert.importance != 0
-
+ alert_db.importance = alert.importance if alert.importance != 0
+
# FIXME: this logic ought to be clearer as it may get more complicated
#
if alert_db.update_type
@@ -369,6 +413,8 @@ module Mauve
alert_db.update_type = :changed
end
+ logger.debug "Saving #{alert_db}"
+
if !alert_db.save
if alert_db.errors.respond_to?("full_messages")
msg = alert_db.errors.full_messages
@@ -394,7 +440,9 @@ module Mauve
alerts_updated << alert_db
end
end
-
+
+ logger.debug "Got #{alerts_updated.length} alerts to notify about"
+
AlertGroup.notify(alerts_updated)
end
diff --git a/lib/mauve/alert_group.rb b/lib/mauve/alert_group.rb
index d8156fa..288b263 100644
--- a/lib/mauve/alert_group.rb
+++ b/lib/mauve/alert_group.rb
@@ -29,11 +29,14 @@ module Mauve
logger.warn "no groups found for #{alert.id}" if groups.empty?
#
- # Notify each group.
+ # Notify just the group that thinks this alert is the most urgent.
#
- groups.each do |grp|
- logger.info("notifying group #{groups[0]} of AlertID.#{alert.id}.")
- grp.notify(alert)
+ %w(urgent normal low).each do |lvl|
+ this_group = groups.find{|grp| grp.level.to_s == lvl}
+ next if this_group.nil?
+ logger.info("notifying group #{this_group} of AlertID.#{alert.id} (matching #{lvl})")
+ this_group.notify(alert)
+ break
end
end
end
diff --git a/lib/mauve/auth_bytemark.rb b/lib/mauve/auth_bytemark.rb
index 7419d10..9e0a6d1 100644
--- a/lib/mauve/auth_bytemark.rb
+++ b/lib/mauve/auth_bytemark.rb
@@ -3,7 +3,7 @@ require 'sha1'
require 'xmlrpc/client'
require 'timeout'
-class AuthSourceBytemark
+class AuthBytemark
def initialize (srv='auth.bytemark.co.uk', port=443)
raise ArgumentError.new("Server must be a String, not a #{srv.class}") if String != srv.class
@@ -11,6 +11,7 @@ class AuthSourceBytemark
@srv = srv
@port = port
@timeout = 7
+ @logger = Log4r::Logger.new(self.class.to_s)
end
## Not really needed.
@@ -33,15 +34,16 @@ class AuthSourceBytemark
raise ArgumentError.new("Login must be a string, not a #{login.class}") if String != login.class
raise ArgumentError.new("Password must be a string, not a #{password.class}") if String != password.class
raise ArgumentError.new("Login or/and password is/are empty.") if login.empty? || password.empty?
+
client = XMLRPC::Client.new(@srv,"/",@port,nil,nil,nil,nil,true,@timeout).proxy("bytemark.auth")
+
begin
challenge = client.getChallengeForUser(login)
response = Digest::SHA1.new.update(challenge).update(password).hexdigest
client.login(login, response)
- rescue XMLRPC::FaultException => fault
- return "Fault code is #{fault.faultCode} stating #{fault.faultString}"
+ rescue Exception => ex
+ return false
end
- return true
end
end
diff --git a/lib/mauve/calendar_interface.rb b/lib/mauve/calendar_interface.rb
index 08cfab3..ab2bc5b 100644
--- a/lib/mauve/calendar_interface.rb
+++ b/lib/mauve/calendar_interface.rb
@@ -27,7 +27,7 @@ module Mauve
# @return [Array] A list of all the username on support.
def self.get_users_on_support(url)
result = get_URL(url)
- logger = Log4r::Logger.new "mauve::CalendarInterface"
+ logger = Log4r::Logger.new "Mauve::CalendarInterface"
logger.debug("Cheching who is on support: #{result}")
return result
end
@@ -40,7 +40,7 @@ module Mauve
# @param [String] usr User single sign on.
# @return [Boolean] True if on support, false otherwise.
def self.is_user_on_support?(url, usr)
- logger = Log4r::Logger.new "mauve::CalendarInterface"
+ logger = Log4r::Logger.new "Mauve::CalendarInterface"
list = get_URL(url)
if true == list.include?("nobody")
logger.error("Nobody is on support thus alerts are ignored.")
@@ -63,7 +63,7 @@ module Mauve
return false if true == list.nil? or true == list.empty?
pattern = /[\d]{4}-[\d]{2}-[\d]{2}\s[\d]{2}:[\d]{2}:[\d]{2}/
result = (list[0].match(pattern))? true : false
- logger = Log4r::Logger.new "mauve::CalendarInterface"
+ logger = Log4r::Logger.new "Mauve::CalendarInterface"
logger.debug("Cheching if #{usr} is on holiday: #{result}")
return result
end
@@ -83,7 +83,7 @@ module Mauve
# @retur [Array] An array of strings, each newline creates an new item.
def self.get_URL (uri_str, limit = 11)
- logger = Log4r::Logger.new "mauve::CalendarInterface"
+ logger = Log4r::Logger.new "Mauve::CalendarInterface"
if 0 == limit
logger.warn("HTTP redirect deeper than 11 on #{uri_str}.")
diff --git a/lib/mauve/configuration.rb b/lib/mauve/configuration.rb
index b11e1b5..4b7717d 100644
--- a/lib/mauve/configuration.rb
+++ b/lib/mauve/configuration.rb
@@ -212,6 +212,7 @@ module Mauve
is_attribute "port"
is_attribute "ip"
is_attribute "document_root"
+ is_attribute "session_secret"
def builder_setup
@result = HTTPServer.instance
@@ -322,9 +323,17 @@ module Mauve
end
def holiday_url (url)
- @result.holiday_url = url
+ @result.holiday_url = url.to_s
end
-
+
+ def email(e)
+ @result.email = e.to_s
+ end
+
+ def xmpp(x)
+ @result.xmpp = x.to_s
+ end
+
def suppress_notifications_after(h)
raise ArgumentError.new("notification_threshold must be specified as e.g. (10 => 1.minute)") unless
h.kind_of?(Hash) && h.keys[0].kind_of?(Integer) && h.values[0].kind_of?(Integer)
@@ -384,7 +393,6 @@ module Mauve
# Create a new instance and adds it.
def builder_setup(label)
- pp label
@result = PeopleList.new(label)
end
diff --git a/lib/mauve/http_server.rb b/lib/mauve/http_server.rb
index 69b566b..4fd8b60 100644
--- a/lib/mauve/http_server.rb
+++ b/lib/mauve/http_server.rb
@@ -2,15 +2,15 @@
#
# Bleuurrgggggh! Bleurrrrrgghh!
#
+require 'mauve/auth_bytemark'
+require 'mauve/web_interface'
+require 'mauve/mauve_thread'
require 'digest/sha1'
require 'log4r'
require 'thin'
require 'rack'
require 'rack-flash'
require 'rack/handler/webrick'
-require 'mauve/auth_bytemark'
-require 'mauve/web_interface'
-require 'mauve/mauve_thread'
################################################################################
#
@@ -87,19 +87,19 @@ module Mauve
attr_accessor :session_secret # not used yet
def initialize
- @port = 32761
+ @port = 1288
@ip = "127.0.0.1"
- @document_root = "."
- @session_secret = rand(2**100).to_s
+ @document_root = "/usr/share/mauvealert"
+ @session_secret = "%x" % rand(2**100)
end
def main_loop
- @server = ::Thin::Server.new(@ip, @port, Rack::CommonLogger.new(Rack::Chunked.new(Rack::ContentLength.new(WebInterface.new)), RackErrorsProxy.new(logger)), :signals => false)
+ @server = ::Thin::Server.new(@ip, @port, Rack::Session::Cookie.new(WebInterface.new, {:key => "mauvealert", :secret => @session_secret, :expire_after => 691200}), :signals => false)
@server.start
end
def stop
- @server.stop
+ @server.stop if @server
super
end
end
diff --git a/lib/mauve/notification.rb b/lib/mauve/notification.rb
index 2220211..02bf6fd 100644
--- a/lib/mauve/notification.rb
+++ b/lib/mauve/notification.rb
@@ -31,7 +31,7 @@ module Mauve
@time = time
@alert = alert
@during = during || Proc.new { true }
- @logger = Log4r::Logger.new "mauve::DuringRunner"
+ @logger = Log4r::Logger.new "Mauve::DuringRunner"
end
def now?
@@ -137,6 +137,11 @@ module Mauve
#
def alert_changed(alert)
+ if people.nil? or people.empty?
+ logger.warn "No people found in for notification #{list}"
+ return
+ end
+
# Should we notificy at all?
is_relevant = DuringRunner.new(MauveTime.now, alert, &during).now?
diff --git a/lib/mauve/notifier.rb b/lib/mauve/notifier.rb
index e0692f6..0127b6b 100644
--- a/lib/mauve/notifier.rb
+++ b/lib/mauve/notifier.rb
@@ -40,11 +40,38 @@ module Mauve
def start
super
- Configuration.current.notification_methods['xmpp'].connect if Configuration.current.notification_methods['xmpp']
+ if Configuration.current.notification_methods['xmpp']
+ #
+ # Connect to XMPP server
+ #
+ xmpp = Configuration.current.notification_methods['xmpp']
+ xmpp.connect
+
+ Configuration.current.people.each do |username, person|
+ #
+ # Ignore people without XMPP stanzas.
+ #
+ next unless person.xmpp
+
+ #
+ # For each JID, either ensure they're on our roster, or that we're in
+ # that chat room.
+ #
+ jid = if xmpp.is_muc?(person.xmpp)
+ xmpp.join_muc(person.xmpp)
+ else
+ xmpp.ensure_roster_and_subscription!(person.xmpp)
+ end
+
+ Configuration.current.people[username].xmpp = jid unless jid.nil?
+ end
+ end
end
def stop
- Configuration.current.notification_methods['xmpp'].close
+ if Configuration.current.notification_methods['xmpp']
+ Configuration.current.notification_methods['xmpp'].close
+ end
super
end
diff --git a/lib/mauve/notifiers/email.rb b/lib/mauve/notifiers/email.rb
index 2c14a54..f3b9a0f 100644
--- a/lib/mauve/notifiers/email.rb
+++ b/lib/mauve/notifiers/email.rb
@@ -38,6 +38,11 @@ module Mauve
@suppressed_changed = nil
end
+ def logger
+ @logger ||= Log4r::Logger.new self.class.to_s.sub(/::Default$/,"")
+
+ end
+
def send_alert(destination, alert, all_alerts, conditions = nil)
message = prepare_message(destination, alert, all_alerts, conditions)
args = [@server, @port]
@@ -46,14 +51,11 @@ module Mauve
Net::SMTP.start(*args) do |smtp|
smtp.send_message(message, @from, destination)
end
- rescue Errno::ECONNREFUSED => e
- @logger = Log4r::Logger.new "mauve::email_send_alert"
- @logger.error("#{e.class}: #{e.message} raised. " +
- "args = #{args.inspect} "
- )
- raise e
- rescue => e
- raise e
+ true
+ rescue StandardError => ex
+ logger.error "SMTP failure: #{ex.to_s}"
+ logger.debug ex.backtrace.join("\n")
+ false
end
end
@@ -98,8 +100,8 @@ module Mauve
# FIXME: include alert.detail as multipart mime
##Thread.abort_on_exception = true
m.body += "\n" + '-'*10 + " This is the detail field " + '-'*44 + "\n\n"
- m.body += alert.get_details()
- m.body += alert.get_details_plain_text()
+ m.body += alert.detail.to_s
+#' m.body += alert.get_details_plain_text()
m.body += "\n" + '-'*80 + "\n\n"
if @suppressed_changed == true
diff --git a/lib/mauve/notifiers/xmpp.rb b/lib/mauve/notifiers/xmpp.rb
index 18df6b2..fbc9640 100644
--- a/lib/mauve/notifiers/xmpp.rb
+++ b/lib/mauve/notifiers/xmpp.rb
@@ -1,68 +1,106 @@
require 'log4r'
require 'xmpp4r'
-require 'xmpp4r/xhtml'
require 'xmpp4r/roster'
-require 'xmpp4r/muc/helper/simplemucclient'
+require 'xmpp4r/muc'
+# require 'xmpp4r/xhtml'
+# require 'xmpp4r/discovery/helper/helper'
require 'mauve/notifiers/debug'
-#Jabber::debug = true
-module Mauve
- module Notifiers
- module Xmpp
-
- class CountingMUCClient < Jabber::MUC::SimpleMUCClient
- attr_reader :participants
- def initialize(*a)
- super(*a)
- @participants = 0
- self.on_join { @participants += 1 }
- self.on_leave { @participants -= 1 }
- end
+#
+# A couple of monkey patches to fix up all this nonsense.
+#
+module Jabber
+ class Stream
+ def close
+ #
+ # Just close
+ #
+ close!
+ end
+ def close!
+ 10.times do
+ pr = 0
+ @tbcbmutex.synchronize { pr = @processing }
+ break if pr = 0
+ Thread::pass if pr > 0
+ sleep 1
end
+
+ # Order Matters here! If this method is called from within
+ # @parser_thread then killing @parser_thread first would
+ # mean the other parts of the method fail to execute.
+ # That would be bad. So kill parser_thread last
+ @tbcbmutex.synchronize { @processing = 0 }
+ @fd.close if @fd and !@fd.closed?
+ @status = DISCONNECTED
+ stop
+ end
+ end
+end
+
+
+
+
+module Mauve
+ module Notifiers
+ module Xmpp
+# class CountingMUCClient < Jabber::MUC::SimpleMUCClient
+#
+# attr_reader :participants
+#
+# def initialize(*a)
+# super(*a)
+# @participants = 0
+# self.on_join { @participants += 1 }
+# self.on_leave { @participants -= 1 }
+# end
+#
+# end
+#
class Default
include Jabber
-
+
# Atrtribute.
attr_reader :name
# Atrtribute.
attr_accessor :jid, :password
- # Atrtribute.
- attr_accessor :initial_jid
-
- # Atrtribute.
- attr_accessor :initial_messages
-
def initialize(name)
+ Jabber::logger = self.logger
+ #Jabber::debug = true
+ #Jabber::warnings = true
+
@name = name
@mucs = {}
@roster = nil
+ @closing = false
+
end
def logger
- @logger ||= Log4r::Logger.new self.class.to_s
+ # Give the logger a sane name
+ @logger ||= Log4r::Logger.new self.class.to_s.sub(/::Default$/,"")
+ end
+
+ def jid=(jid)
+ @jid = JID.new(jid)
end
+
+ def connect
+ logger.info "Jabber starting connection to #{@jid}"
- def reconnect
- if @client
- begin
- logger.debug "Jabber closing old client connection"
- @client.close
- @client = nil
- @roster = nil
- rescue Exception => ex
- logger.error "#{ex} when reconnecting"
- end
- end
+ # Make sure we're disconnected.
+ self.close if @client.is_a?(Client)
+
+ @client = Client.new(@jid)
- logger.debug "Jabber starting connection to #{@jid}"
- @client = Client.new(JID::new(@jid))
+ @closing = false
@client.connect
@client.auth_nonsasl(@password, false)
@roster = Roster::Helper.new(@client)
@@ -70,11 +108,17 @@ module Mauve
# Unconditionally accept all roster add requests, and respond with a
# roster add + subscription request of our own if we're not subscribed
# already
- @roster.add_subscription_request_callback do |ri, stanza|
+ @roster.add_subscription_request_callback do |ri, presence|
Thread.new do
- logger.debug("Accepting subscription request from #{stanza.from}")
- @roster.accept_subscription(stanza.from)
- ensure_roster_and_subscription!(stanza.from)
+ logger.debug "Known? #{is_known_contact?(presence.from).inspect}"
+ if is_known_contact?(presence.from)
+ logger.info("Accepting subscription request from #{presence.from}")
+ @roster.accept_subscription(presence.from)
+ ensure_roster_and_subscription!(presence.from)
+ else
+ logger.info("Declining subscription request from #{presence.from}")
+ @roster.decline_subscription(presence.from)
+ end
end.join
end
@@ -85,37 +129,40 @@ module Mauve
@roster.wait_for_roster
logger.debug "Jabber authenticated, setting presence"
- @client.send(Presence.new.set_type(:available))
-
- @mucs = {}
-
- logger.debug "Jabber is ready in theory"
- end
-
- def reconnect_and_retry_on_error
- @already_reconnected = false
- begin
- yield
- rescue StandardError => ex
- logger.error "#{ex} during notification\n"
- logger.debug ex.backtrace
- if !@already_reconnected
- reconnect
- @already_reconnected = true
- retry
- else
- raise ex
+ @client.send(Presence.new(nil, "Woo!").set_type(nil))
+
+ @client.on_exception do |ex, stream, where|
+ #
+ # The XMPP4R exception clauses in Stream all close the stream, so
+ # we just need to reconnect.
+ #
+ unless ex.nil? or @closing
+ logger.warn(["Caught",ex.class,ex.to_s,"during XMPP",where].join(" "))
+ connect
+ @mucs.each do |jid, muc|
+ @mucs.delete(jid)
+ join_muc(jid)
+ end
end
end
end
- def connect
- self.reconnect_and_retry_on_error { self.send_msg(@initial_jid, "Hello!") }
+ #
+ # Kills the processor thread
+ #
+ def stop
+ @client.stop
end
def close
- self.send_msg(@initial_jid, "Goodbye!")
- @client.close
+ @closing = true
+ if @client and @client.is_connected?
+ @mucs.each do |jid, muc|
+ muc.exit("Goodbye!") if muc.active?
+ end
+ @client.send(Presence.new(nil, "Goodbye!").set_type(:unavailable))
+ @client.close!
+ end
end
# Takes an alert and converts it into a message.
@@ -144,97 +191,232 @@ module Mauve
# or more of the choices - see +check_jid_has_presence+ for options.
def send_alert(destination, alert, all_alerts, conditions = nil)
- #message = Message.new(nil, alert.summary_two_lines.join("\n"))
- message = Message.new(nil, convert_alert_to_message(alert))
-
+ destination_jid = JID.new(destination)
+
if conditions
@suppressed_changed = conditions[:suppressed_changed]
end
- # MUC JIDs are prefixed with muc: - we need to strip this out.
- destination_is_muc, dest_jid = self.is_muc?(destination)
-
- begin
- xhtml = XHTML::HTML.new("<p>" +
- convert_alert_to_message(alert)+
-# alert.summary_three_lines.join("<br />") +
- #alert.summary_two_lines.join("<br />") +
- "</p>")
- message.add_element(xhtml)
- rescue REXML::ParseException => ex
- logger.warn("Can't send XMPP alert as valid XHTML-IM, falling back to plaintext")
- logger.debug(ex)
- end
-
- logger.debug "Jabber sending #{message} to #{destination}"
- reconnect unless @client
-
- ensure_roster_and_subscription!(dest_jid) unless destination_is_muc
-
- if conditions && !check_alert_conditions(dest_jid, conditions)
- logger.debug("Alert conditions not met, not sending XMPP alert to #{jid}")
+ if conditions && !check_alert_conditions(destination_jid, conditions)
+ logger.info("Alert conditions not met, not sending XMPP alert to #{destination_jid}")
return false
end
- if destination_is_muc
- if !@mucs[dest_jid]
- @mucs[dest_jid] = CountingMUCClient.new(@client)
- @mucs[dest_jid].join(JID.new(dest_jid))
- end
- reconnect_and_retry_on_error { @mucs[dest_jid].send(message, nil) ; true }
- else
- message.to = dest_jid
- reconnect_and_retry_on_error { @client.send(message) ; true }
- end
+ send_message(destination_jid, convert_alert_to_message(alert))
end
# Sends a message to the destionation.
#
- # @param [String] destionation The (full) JID to send to.
+ # @param [String] destination The (full) JID to send to.
# @param [String] msg The (formatted) message to send.
# @return [NIL] nada.
- def send_msg(destination, msg)
- reconnect unless @client
- message = Message.new(nil, msg)
- destination_is_muc, dest_jid = self.is_muc?(destination)
- if destination_is_muc
- if !@mucs[dest_jid]
- @mucs[dest_jid] = CountingMUCClient.new(@client)
- @mucs[dest_jid].join(JID.new(dest_jid))
+ def send_message(jid, msg)
+ jid = JID.new(jid) unless jid.is_a?(JID)
+
+ message = Message.new(jid)
+
+ #if msg.is_a?(XHTML::HTML)
+ # message.add_element(msg)
+ #else
+ message.body = msg
+ #end
+
+ if is_muc?(jid)
+ jid = join_muc(jid.strip)
+ muc = @mucs[jid]
+
+ if muc
+ message.to = muc.jid
+ muc.send(message)
+ true
+ else
+ logger.warn "Failed to join MUC #{jid} when trying to send a message"
+ false
end
- reconnect_and_retry_on_error { @mucs[dest_jid].send(message, nil) ; true }
else
- message.to = dest_jid
- reconnect_and_retry_on_error { @client.send(message) ; true }
+ #
+ # We aren't interested in sending things to people who aren't online.
+ #
+ ensure_roster_and_subscription!(jid)
+
+ if check_jid_has_presence(jid)
+ #
+ # We set the chat type to chat
+ #
+ message.type = :chat
+ message.to = jid
+ @client.send(message)
+ true
+ else
+ false
+ end
end
- return nil
end
- protected
+ #
+ # Joins a chat, and returns the stripped JID of the chat joined.
+ #
+ def join_muc(jid, password=nil)
+ if jid.is_a?(String)
+ jid = JID.new($1) if jid =~ /^muc:(.*)/
+ end
+
+ unless jid.is_a?(JID)
+ logger.warn "I don't think #{jid} is a MUC"
+ return
+ end
+
+ jid.resource = @client.jid.resource if jid.resource.to_s.empty?
+
+ if !@mucs[jid.strip]
+
+ logger.info("Adding new MUC client for #{jid}")
+
+ @mucs[jid.strip] = Jabber::MUC::MUCClient.new(@client)
+
+ # Add some callbacks
+ @mucs[jid.strip].add_message_callback do |m|
+ receive_message(m)
+ end
+
+ @mucs[jid.strip].add_private_message_callback do |m|
+ receive_message(m)
+ end
+
+ end
+
+ if !@mucs[jid.strip].active?
+ logger.info("Joining #{jid}")
+ #
+ # Make sure we have a resource.
+ #
+ @mucs[jid.strip].join(jid, password)
- # Checks whether the destination JID is a MUC.
- # Returns [true/false, destination]
- def is_muc?(destination)
- if /^muc:(.*)/.match(destination)
- [true, $1]
else
- [false, destination]
+ logger.debug("Already joined #{jid}.")
end
- end
+
+ #
+ # Return the JID object
+ #
+ jid.strip
+ end
+ #
+ # Checks whether the destination JID is a MUC.
+ #
+ def is_muc?(jid)
+ (jid.is_a?(JID) and @mucs.keys.include?(jid.strip)) or
+ (jid.is_a?(String) and jid =~ /^muc:(.*)/)
+
+ #
+ # It would be nice to use service discovery to determin this, but it
+ # turns out that it is shite in xmpp4r. It doesn't return straight
+ # away with an answer, making it a bit useless. Some sort of weird
+ # threading issue, I think.
+ #
+ # begin
+ # logger.warn caller.join("\n")
+ # cl = Discovery::Helper.new(@client)
+ # res = cl.get_info_for(jid.strip)
+ # @client.wait
+ # logger.warn "hello #{res.inspect}"
+ # res.is_a?(Discovery::IqQueryDiscoInfo) and res.identity.category == :conference
+ # rescue Jabber::ServerError => ex
+ # false
+ # end
+ end
+
+ #
# Checks to see if the JID is in our roster, and whether we are
# subscribed to it or not. Will add to the roster and subscribe as
# is necessary to ensure both are true.
+ #
def ensure_roster_and_subscription!(jid)
- jid = JID.new(jid)
- ri = @roster.find(jid)[jid]
- if ri.nil?
- @roster.add(jid, nil, true)
- else
- ri.subscribe unless [:to, :both, :remove].include?(ri.subscription)
- end
- rescue Exception => ex
+ return jid if is_muc?(jid)
+
+ jid = JID.new(jid) unless jid.is_a?(JID)
+
+ ri = @roster.find(jid).values.first
+ @roster.add(jid, nil, true) if ri.nil?
+
+ ri = @roster.find(jid).values.first
+ ri.subscribe unless [:to, :both, :remove].include?(ri.subscription)
+ ri.jid
+ rescue StandardError => ex
logger.error("Problem ensuring that #{jid} is subscribed and in mauve's roster: #{ex.inspect}")
+ nil
+ end
+
+ protected
+
+ def receive_message(msg)
+ # We only want to hear messages from known contacts.
+ unless is_known_contact?(msg.from)
+ # ignore message
+ logger.info "Ignoring message from unknown contact #{msg.from}"
+ return nil
+ end
+
+ case msg.type
+ when :error
+ receive_error_message(msg)
+ when :groupchat
+ receive_groupchat_message(msg)
+ else
+ receive_normal_message(msg)
+ end
+ end
+
+ def receive_error_message(msg)
+ logger.warn("Caught XMPP error #{msg}")
+ nil
+ end
+
+ def receive_normal_message(msg)
+ #
+ # Treat invites specially
+ #
+ if msg.x("jabber:x:conference")
+ #
+ # recieved an invite. Need to mangle the jid.
+ #
+ jid =JID.new(msg.x("jabber:x:conference").attribute("jid"))
+ # jid.resource = @client.jid.resource
+ logger.info "Received an invite to #{jid}"
+ unless join_muc(jid)
+ logger.warn "Failed to join MUC #{jid} following invitation"
+ return nil
+ end
+ elsif msg.body
+ #
+ # Received a message with a body.
+ #
+ jid = msg.from
+ end
+
+ #
+ # I don't have time to talk to myself!
+ #
+ if jid and jid.strip != @client.jid.strip
+ txt = File.executable?('/usr/games/fortune') ? `/usr/games/fortune -s -n 60`.chomp : "I'd love to stay and chat, but I'm really too busy."
+ send_message(jid, txt)
+ end
+ end
+
+ def receive_groupchat_message(msg)
+ #
+ # We only want group chat messages from MUCs we're already joined to,
+ # that we've not sent ourselves, that are not historical, and that
+ # match our resource or node in the body.
+ #
+ if @mucs[msg.from.strip].is_a?(MUC::MUCClient) and
+ msg.from != @mucs[msg.from.strip].jid and
+ msg.x("jabber:x:delay") == nil and
+ (msg.body =~ /\b#{Regexp.escape(@client.jid.resource)}\b/i or
+ msg.body =~ /\b#{Regexp.escape(@client.jid.node)}\b/i)
+ receive_normal_message(msg)
+ end
end
def check_alert_conditions(destination, conditions)
@@ -264,8 +446,11 @@ module Mauve
# Returns true if at least one of the presence specifiers for the jid
# is met, false otherwise. Note that if the alerter can't see the alertee's
# presence, only 'unknown' will match - generally, you'll want [:online, :unknown]
- def check_jid_has_presence(jid, presence_or_presences)
- return true if jid.match(/^muc:/)
+ def check_jid_has_presence(jid, presence_or_presences = [:online, :unknown])
+ jid = JID.new(jid) unless jid.is_a?(JID)
+
+
+ return true if is_muc?(jid)
reconnect unless @client
@@ -291,15 +476,16 @@ module Mauve
end
results.include?(true)
end
-
- end
- #
- # TODO parse message and ack as needed..? The trick is here to
- # understand what the person sending the message wants. Could be
- # difficult.
- def receive_message(message)
- @logger.debug "Received message from #{message.from}.. Ignoring for now."
+ def is_known_contact?(jid)
+ jid = JID.new(jid) unless jid.is_a?(JID)
+
+ Configuration.current.people.any? do |username, person|
+ next unless person.xmpp.is_a?(JID)
+ person.xmpp.strip == jid.strip
+ end
+ end
+
end
end
end
diff --git a/lib/mauve/people_list.rb b/lib/mauve/people_list.rb
index 2e4c737..23e0c1e 100644
--- a/lib/mauve/people_list.rb
+++ b/lib/mauve/people_list.rb
@@ -21,19 +21,21 @@ module Mauve
alias username label
def list
- self[:list]
+ self[:list] || []
end
#
# Set up the logger
def logger
- @logger ||= Log4r::Logger.new self.class
+ @logger ||= Log4r::Logger.new self.class.to_s
end
#
# Return the array of people
#
def people
+ logger.warn "No-one found in the people list for #{self.label}" if self.list.empty?
+
list.collect do |name|
Configuration.current.people.has_key?(name) ? Configuration.current.people[name] : nil
end.reject{|person| person.nil?}
diff --git a/lib/mauve/person.rb b/lib/mauve/person.rb
index da3aa13..42b6baf 100644
--- a/lib/mauve/person.rb
+++ b/lib/mauve/person.rb
@@ -3,7 +3,7 @@ require 'timeout'
require 'log4r'
module Mauve
- class Person < Struct.new(:username, :password, :holiday_url, :urgent, :normal, :low)
+ class Person < Struct.new(:username, :password, :holiday_url, :urgent, :normal, :low, :email, :xmpp, :sms)
attr_reader :notification_thresholds
@@ -24,26 +24,50 @@ module Mauve
#
class NotificationCaller
- def initialize(alert, other_alerts, notification_methods, base_conditions={})
- logger = Log4r::Logger.new "mauve::NotificationCaller"
+ def initialize(person, alert, other_alerts, notification_methods, base_conditions={})
+ @person = person
@alert = alert
@other_alerts = other_alerts
@notification_methods = notification_methods
@base_conditions = base_conditions
end
- def method_missing(name, destination, *args)
- conditions = @base_conditions.merge(args[0] ? args[0] : {})
- notification_method = @notification_methods[name.to_s]
+ def logger ; @logger ||= Log4r::Logger.new self.class.to_s ; end
+
+ #
+ # This method makes sure things liek
+ #
+ # xmpp
+ # works
+ #
+ def method_missing(name, *args)
+ #
+ # Work out the destination
+ #
+ if args.first.is_a?(String)
+ destination = args.pop
+ else
+ destination = @person.__send__(name)
+ end
- unless notification_method
- raise NoMethodError.new("#{name} not defined as a notification method")
+ if args.first.is_a?(Array)
+ conditions = @base_conditions.merge(args[0])
end
+
+ notification_method = Configuration.current.notification_methods[name.to_s]
+
+ raise NoMethodError.new("#{name} not defined as a notification method") unless notification_method
+
# Methods are expected to return true or false so the user can chain
# them together with || as fallbacks. So we have to catch exceptions
# and turn them into false.
#
- notification_method.send_alert(destination, @alert, @other_alerts, conditions)
+ res = notification_method.send_alert(destination, @alert, @other_alerts, conditions)
+ #
+ # Log the result
+ logger.debug "Notification " + (res ? "succeeded" : "failed" ) + " for #{@person.username} using notifier '#{name}' to '#{destination}'"
+
+ res
end
end
@@ -174,6 +198,7 @@ module Mauve
return if suppressed? or this_alert_suppressed
result = NotificationCaller.new(
+ self,
alert,
current_alerts,
Configuration.current.notification_methods,
@@ -197,7 +222,7 @@ module Mauve
# Returns the subset of current alerts that are relevant to this Person.
#
def current_alerts
- Alert.all_current.select do |alert|
+ Alert.all_raised.select do |alert|
my_last_update = AlertChanged.first(:person => username, :alert_id => alert.id)
my_last_update && my_last_update.update_type != :cleared
end
diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb
index 30536bb..ac24da4 100644
--- a/lib/mauve/server.rb
+++ b/lib/mauve/server.rb
@@ -12,7 +12,6 @@ require 'mauve/processor'
require 'mauve/http_server'
require 'log4r'
-
module Mauve
class Server
@@ -70,7 +69,7 @@ module Mauve
#
DataMapper.setup(:default, @config[:database])
- DataObjects::Sqlite3.logger = Log4r::Logger.new("Mauve::DataMapper")
+ # DataObjects::Sqlite3.logger = Log4r::Logger.new("Mauve::DataMapper")
#
# Update any tables.
@@ -82,7 +81,7 @@ module Mauve
#
# Work out when the server was last stopped
#
- @stopped_at = self.last_heartbeat
+ # topped_at = self.last_heartbeat
end
def last_heartbeat
@@ -91,7 +90,7 @@ module Mauve
#
[ Alert.last(:order => :updated_at.asc),
AlertChanged.last(:order => :updated_at.asc) ].
- reject{|a| a.nil?}.
+ reject{|a| a.nil? or a.updated_at.nil? }.
collect{|a| a.updated_at.to_time}.
sort.
last
@@ -108,26 +107,61 @@ module Mauve
def stop
@stop = true
+ thread_list = Thread.list
+
+ thread_list.delete(Thread.current)
+
THREAD_CLASSES.reverse.each do |klass|
- klass.instance.stop
+ thread_list.delete(klass.instance)
+ klass.instance.stop unless klass.instance.nil?
end
-
+
+ thread_list.each do |t|
+ t.exit
+ end
+
@logger.info("All threads stopped")
end
def run
loop do
+ thread_list = Thread.list
+
+ thread_list.delete(Thread.current)
+
THREAD_CLASSES.each do |klass|
+ thread_list.delete(klass.instance)
+
next if @frozen or @stop
-
+
unless klass.instance.alive?
- # ugh something has died.
- klass.instance.join
+ # ugh something has died.
+ #
+ begin
+ klass.instance.join
+ rescue StandardError => ex
+ @logger.warn "Caught #{ex.to_s} whilst checking #{klass} thread"
+ @logger.debug ex.backtrace.join("\n")
+ end
+ #
+ # Start the stuff.
klass.instance.start unless @stop
end
end
+ thread_list.each do |t|
+ next unless t.alive?
+ begin
+ t.join
+ rescue StandardError => ex
+ @logger.fatal "Caught #{ex.to_s} whilst checking threads"
+ @logger.debug ex.backtrace.join("\n")
+ self.stop
+ break
+ end
+ end
+
break if @stop
sleep 1
diff --git a/lib/mauve/source_list.rb b/lib/mauve/source_list.rb
index 4ffef15..23f5ae1 100644
--- a/lib/mauve/source_list.rb
+++ b/lib/mauve/source_list.rb
@@ -27,7 +27,7 @@ module Mauve
## Default contructor.
def initialize ()
- @logger = Log4r::Logger.new "mauve::SourceList"
+ @logger = Log4r::Logger.new "Mauve::SourceList"
@hash = Hash.new
@http_head = Regexp.compile(/^http[s]?:\/\//)
@http_tail = Regexp.compile(/\/.*$/)
diff --git a/lib/mauve/timer.rb b/lib/mauve/timer.rb
index 5355dcc..60b541c 100644
--- a/lib/mauve/timer.rb
+++ b/lib/mauve/timer.rb
@@ -21,7 +21,6 @@ module Mauve
end
def main_loop
- @logger.debug "hello"
#
# Get the next alert.
#
@@ -73,7 +72,12 @@ module Mauve
#
# This is a rate-limiting step for alerts.
#
- Kernel.sleep 0.2
+ Kernel.sleep 0.1
+ #
+ # Not sure if this is needed or not. But the timer thread seems to
+ # freeze here, apparently stuck on a select() statement.
+ #
+ Thread.pass
end
return if self.should_stop? or next_to_notify.nil?
diff --git a/lib/mauve/udp_server.rb b/lib/mauve/udp_server.rb
index a570e8a..a873e77 100644
--- a/lib/mauve/udp_server.rb
+++ b/lib/mauve/udp_server.rb
@@ -68,11 +68,13 @@ module Mauve
#
# TODO: why is/isn't this non-block?
#
+ i = 0
begin
- # packet = @socket.recvfrom_nonblock(65535)
- packet = @socket.recvfrom(65535)
+ packet = @socket.recvfrom_nonblock(65535)
+# packet = @socket.recvfrom(65535)
received_at = MauveTime.now
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK => ex
+ puts "#{i += 1} + #{ex}"
IO.select([@socket])
retry unless self.should_stop?
end
@@ -99,7 +101,7 @@ module Mauve
#
# Triggers loop to close socket.
#
- UDPSocket.open.send("", 0, @socket.addr[2], @socket.addr[1]) unless @socket.closed?
+ UDPSocket.open.send("", 0, @socket.addr[2], @socket.addr[1]) unless @socket.nil? or @socket.closed?
super
end
diff --git a/lib/mauve/web_interface.rb b/lib/mauve/web_interface.rb
index 4569cb6..210f88a 100644
--- a/lib/mauve/web_interface.rb
+++ b/lib/mauve/web_interface.rb
@@ -1,7 +1,11 @@
# encoding: UTF-8
+require 'haml'
+require 'redcloth'
+
+require 'sinatra/tilt'
require 'sinatra/base'
require 'sinatra-partials'
-require 'haml'
+
require 'rack'
require 'rack-flash'
@@ -16,15 +20,18 @@ module Mauve
class PleaseAuthenticate < Exception; end
- use Rack::Session::Cookie, :expire_after => 604800 # 7 days in seconds
+ use Rack::CommonLogger
+ use Rack::Chunked
+ use Rack::ContentLength
+ use Rack::Flash
- enable :sessions
+# Tilt.register :textile, RedClothTemplate
- use Rack::Flash
-
- set :root, "/usr/share/mauve"
- set :views, "#{root}/views"
- set :public, "#{root}/static"
+
+ # Ugh.. hacky way to dynamically configure the document root.
+ set :root, Proc.new{ HTTPServer.instance.document_root }
+ set :views, Proc.new{ root && File.join(root, 'views') }
+ set :public, Proc.new{ root && File.join(root, 'static') }
set :static, true
set :show_exceptions, true
@@ -39,12 +46,43 @@ module Mauve
########################################################################
before do
- @person = Configuration.current.people[session['username']]
@title = "Mauve alert panel"
+ @person = nil
+ #
+ # Make sure we're authenticated.
+ #
+
+ if session.has_key?('username') and Configuration.current.people.has_key?(session['username'].to_s)
+ #
+ # Phew, we're authenticated
+ #
+ @person = Configuration.current.people[session['username']]
+
+ #
+ # A bit wasteful maybe..?
+ #
+ @alerts_raised = Alert.all_raised
+ @alerts_cleared = Alert.all_cleared
+ @alerts_ackd = Alert.all_acknowledged
+ @group_by = "subject"
+ else
+ # Uh-oh.. Intruder alert!
+ #
+ ok_urls = %w(/ /login /logout)
+
+ unless ok_urls.include?(request.path_info)
+ flash['error'] = "You must be logged in to access that page."
+ redirect "/login?next_page=#{request.path_info}"
+ end
+ end
end
get '/' do
- redirect '/alerts'
+ if @person.nil?
+ redirect '/login'
+ else
+ redirect '/alerts'
+ end
end
########################################################################
@@ -53,36 +91,69 @@ module Mauve
#
# The password can be either the SSO or a local one defined
# in the configuration file.
- #
+
+ get '/login' do
+ if @person
+ redirect '/'
+ else
+ @next_page = params[:next_page] || '/'
+ haml :login
+ end
+ end
+
post '/login' do
usr = params['username']
pwd = params['password']
- ret_sso = helper_auth_SSO(usr, pwd)
- ret_loc = helper_auth_local(usr, pwd)
- if "success" == ret_sso or "success" == ret_loc
+ next_page = params['next_page']
+ #
+ # Make sure we don't magically logout automatically :)
+ #
+ next_page = '/' if next_page == '/logout'
+
+ if auth_helper(usr, pwd)
session['username'] = usr
+ redirect next_page
else
- flash['error'] =<<__MSG
-<hr /> <img src="/images/error.png" /> <br />
-ACCESS DENIED <br />
-#{ret_sso} <br />
-#{ret_loc} <hr />
-__MSG
+ flash['error'] = "Access denied."
end
- redirect '/alerts'
end
get '/logout' do
session.delete('username')
- redirect '/alerts'
+ redirect '/login'
end
get '/alerts' do
- #now = MauveTime.now.to_f
- please_authenticate()
- find_active_alerts()
- #pp MauveTime.now.to_f - now
- haml(:alerts2)
+ redirect '/alerts/raised'
+ end
+
+ get '/alerts/:alert_type' do
+ redirect "/alerts/#{params[:alert_type]}/subject"
+ end
+
+ get '/alerts/:alert_type/:group_by' do
+ if %w(raised cleared acknowledged).include?(params[:alert_type])
+ @alert_type = params[:alert_type]
+ else
+ @alert_type = "raised"
+ end
+
+ if %w(subject source summary id alert_id level).include?(params[:group_by])
+ @group_by = params[:group_by]
+ else
+ @group_by = "subject"
+ end
+
+ case @alert_type
+ when "raised"
+ @grouped_alerts = group_by(@alerts_raised, @group_by)
+ when "cleared"
+ @grouped_alerts = group_by(@alerts_cleared, @group_by)
+ when "acknowledged"
+ @grouped_alerts = group_by(@alerts_ackd, @group_by)
+ end
+
+ haml(:alerts)
end
get '/_alert_summary' do
@@ -98,22 +169,20 @@ __MSG
partial("head")
end
- get '/alert/:id/detail' do
- please_authenticate
-
- content_type("text/html") # I think
- Alert.get(params[:id]).detail
+ get '/alert/:id/_detail' do
+ content_type "text/html"
+ alert = Alert.get(params[:id])
+
+ haml :_detail, :locals => { :alert => alert } unless alert.nil?
end
get '/alert/:id' do
- please_authenticate
find_active_alerts
@alert = Alert.get(params['id'])
haml :alert
end
post '/alert/:id/acknowledge' do
- please_authenticate
alert = Alert.get(params[:id])
if alert.acknowledged?
@@ -128,7 +197,6 @@ __MSG
# Note that :until must be in seconds.
post '/alert/acknowledge/:id/:until' do
#now = MauveTime.now.to_f
- please_authenticate
alert = Alert.get(params[:id])
alert.acknowledge!(@person, params[:until].to_i())
@@ -140,7 +208,6 @@ __MSG
post '/alert/:id/raise' do
#now = MauveTime.now.to_f
- please_authenticate
alert = Alert.get(params[:id])
alert.raise!
@@ -150,7 +217,6 @@ __MSG
end
post '/alert/:id/clear' do
- please_authenticate
alert = Alert.get(params[:id])
alert.clear!
@@ -159,7 +225,6 @@ __MSG
end
post '/alert/:id/toggleDetailView' do
- please_authenticate
alert = Alert.get(params[:id])
if nil != alert
@@ -171,8 +236,6 @@ __MSG
end
post '/alert/fold/:subject' do
- please_authenticate
-
session[:display_folding][params[:subject]] = (true == session[:display_folding][params[:subject]])? false : true
content_type("application/json")
'all is good'.to_json
@@ -181,7 +244,6 @@ __MSG
########################################################################
get '/preferences' do
- please_authenticate
find_active_alerts
haml :preferences
end
@@ -189,7 +251,6 @@ __MSG
########################################################################
get '/events' do
- please_authenticate
find_active_alerts
find_recent_alerts
haml :events
@@ -199,11 +260,21 @@ __MSG
helpers do
include Sinatra::Partials
-
- def please_authenticate
- raise PleaseAuthenticate.new unless @person
+
+ def group_by(things, meth)
+ return {} if things.empty?
+
+ raise ArgumentError.new "#{things.first.class} does not respond to #{meth}" unless things.first.respond_to?(meth)
+
+ results = Hash.new{|h,k| h[k] = Array.new}
+
+ things.each do |thing|
+ results[thing.__send__(meth)] << thing
+ end
+
+ results
end
-
+
def find_active_alerts
# FIXME: make sure alerts only appear once some better way
@@ -287,25 +358,45 @@ __MSG
## Test for authentication with SSO.
#
- def helper_auth_SSO (usr, pwd)
- auth = AuthSourceBytemark.new()
- begin
- return "success" if true == auth.authenticate(usr,pwd)
- return "SSO did not regcognise your login/password combination."
- rescue ArgumentError => ex
- return "SSO argument error: #{ex.message}"
- rescue => ex
- return "SSO generic error: #{ex.message}"
+ def auth_helper (usr, pwd)
+ # First try Bytemark
+ #
+ auth = AuthBytemark.new()
+ result = begin
+ auth.authenticate(usr,pwd)
+ rescue Exception => ex
+ @logger.debug "Caught exception during Bytemark auth for #{usr} (#{ex.to_s})"
+ false
end
- end
- ## Test for authentication with configuration file parameter.
- #
- def helper_auth_local (usr, pwd)
- person = Configuration.current.people[params['username']]
- return "I did not recognise your local login details." if !person
- return "I did not recognise your local password." if Digest::SHA1.hexdigest(params['password']) != person.password
- return "success"
+ if true == result
+ return true
+ else
+ @logger.debug "Bytemark authentication failed for #{usr}"
+ end
+
+ #
+ # OK now try local auth
+ #
+ result = begin
+ if Configuration.current.people.has_key?(usr)
+ Digest::SHA1.hexdigest(params['password']) == Configuration.current.people[usr].password
+ end
+ rescue Exception => ex
+ @logger.debug "Caught exception during local auth for #{usr} (#{ex.to_s})"
+ false
+ end
+
+ if true == result
+ return true
+ else
+ @logger.debug "Local authentication failed for #{usr}"
+ end
+ #
+ # Rate limit logins.
+ #
+ sleep 5
+ false
end
end
@@ -316,14 +407,13 @@ __MSG
status 403
session[:display_alerts] = Hash.new()
session[:display_folding] = Hash.new()
- haml :please_authenticate
end
########################################################################
# @see http://stackoverflow.com/questions/2239240/use-rackcommonlogger-in-sinatra
def call(env)
if true == @logger.nil?
- @logger = Log4r::Logger.new("mauve::Rack")
+ @logger = Log4r::Logger.new("Mauve::Rack")
end
env['rack.errors'] = RackErrorsProxy.new(@logger)
super(env)
diff --git a/static/common b/static/common
new file mode 120000
index 0000000..945c9b4
--- /dev/null
+++ b/static/common
@@ -0,0 +1 @@
+. \ No newline at end of file
diff --git a/static/images/BytemarkLogo180.png b/static/images/BytemarkLogo180.png
new file mode 100644
index 0000000..d3cb3c4
--- /dev/null
+++ b/static/images/BytemarkLogo180.png
Binary files differ
diff --git a/static/javascript b/static/javascript
new file mode 120000
index 0000000..e3b95b4
--- /dev/null
+++ b/static/javascript
@@ -0,0 +1 @@
+/usr/share/javascript \ No newline at end of file
diff --git a/static/mauve_utils.js b/static/mauve_utils.js
index 2a14dc2..8a4a9db 100644
--- a/static/mauve_utils.js
+++ b/static/mauve_utils.js
@@ -1,150 +1,24 @@
-// rather simple first stab at automating image rollovers - any image with
-// a class of auto_hover will set its source to be the original name + _hover.png
-// when rolled over, and back again when the mouse moves away.
-//
-// need to initialise by calling addAutoHover() after document has loaded.
-//
-function addAutoHover() {
- $$('img.auto_hover').each(function(image) {
- image.observe('mouseover', function(event) {
- image.src = image.src.gsub(".png", "_hover.png");
- });
- image.observe('mouseout', function(event) {
- image.src = image.src.gsub("_hover.png", ".png");
- });
- preload = new Image();
- preload.src = image.src.gsub(".png", "_hover.png");
- });
-};
-function addRefresh() {
- updater1 = new Ajax.PeriodicalUpdater("alert_summary", "/_alert_summary",
- { method: 'get', frequency: 120 });
- updater2 = new Ajax.PeriodicalUpdater("alert_counts", "/_alert_counts",
- { method: 'get', frequency: 120 });
-}
+// Controls the showing of details on alerts.
-// Pop up the big white box at the top when something goes wrong, scroll so
-// user can see it.
-//
-function reportError(message) {
- $('errors_list').insert('<li>'+message+'</li>');
- $('errors').show();
- $('errors').scrollTo();
+function next_date(n, d, when) {
+ switch(when) {
+ case "daytime"
+ next_daytime_hour(d) + n;
+ case "working"
+ next_working_hour(d) + n;
+ default
+ d + n;
+ }
}
-// Hide the big white box again
-//
-function clearErrors() { $('errors').hide(); }
-// Wrapper around reportError to report an error in updating a particular
-// alert.
-//
-function acknowledgeFailed(id, message) {
- if (message)
- reportError("<strong>Couldn't update alert "+id+":</strong> "+message);
- else
- reportError("<strong>Couldn't update alert "+id+"</strong>");
+function is_daytime_hour(d) {
+ return (d.getHours() => 8 and d.getHours() <= 17);
}
-// Updates the page from a JSON representation of a particular alert.
-//
-function updateAlert(alert) {
- var strip = $('alert_'+alert.id);
- if (!strip) {
- reportError("Alert "+id+" not rendered - bug?");
- return;
- }
-
- image = strip.down(".acknowledge img");
- image.src = alert.acknowledged_at ?
- "/images/acknowledge_acknowledged.png" :
- "/images/acknowledge_unacknowledged.png"
-
- if (strip.down(".source"))
- strip.down(".source").update(alert.source);
- if (strip.down(".subject"))
- strip.down(".subject").update(alert.subject);
- if (strip.down(".summary") && strip.down(".summary").down())
- strip.down(".summary").down().update(alert.summary);
- strip.next().update(alert.detail);
-
- if (alert.acknowledged_at)
- strip.next().hide();
-}
-// called when user hits the acknowledge button for an alert - makes a callback
-// to the server to communicate the change, and updates the button
-// appropriately.
-//
-function toggleAcknowledge(id) {
- updater = new Ajax.Request('/alert/'+id+'/acknowledge', {
-
- method: 'post',
-
- // ignored by server, see http://www.ruby-forum.com/topic/162976 for why
- postBody: 'x',
-
- onFailure: function(xhr) { acknowledgeFailed(id, "Failure - "+xhr.statusText); },
-
- onException: function(xhr, ex) { acknowledgeFailed(id, Dumper(ex)); },
-
- onSuccess: function(xhr) {
- if (xhr.status == 200) {
- content_type = xhr.getResponseHeader("Content-Type");
- if (content_type != "application/json") {
- acknowledgeFailed(id, "Got "+content_type+" not application/json from server");
- } else {
- updateAlert(xhr.responseText.evalJSON());
- }
- } else {
- acknowledgeFailed(id, "Connection problem");
- }
- }
- });
-};
+function next_working_hour(d) {
-// Controls the showing of details on alerts.
-function toggleDetailView(id) {
- updater = new Ajax.Request('/alert/'+id+'/toggleDetailView', {
- method: 'post',
- postBody: 'x',
- onFailure: function(xhr) { acknowledgeFailed(id, "Failure - "+xhr.statusText); },
- onException: function(xhr, ex) { acknowledgeFailed(id, Dumper(ex)); },
- onSuccess: function(xhr) {
- if (xhr.status == 200) {
- content_type = xhr.getResponseHeader("Content-Type");
- if (content_type != "application/json") {
- acknowledgeFailed(id, "Got "+content_type+" not application/json from server");
- } else {
- //updateAlert(xhr.responseText.evalJSON());
- }
- } else {
- acknowledgeFailed(id, "Connection problem");
- }
- }
- });
}
-
-// Controls the showing of folding on alerts.
-function toggleFoldingView(subject) {
- updater = new Ajax.Request('/alert/fold/'+subject, {
- method: 'post',
- postBody: 'x',
- onFailure: function(xhr) { acknowledgeFailed(subject, "Failure - "+xhr.statusText); },
- onException: function(xhr, ex) { acknowledgeFailed(subject, Dumper(ex)); },
- onSuccess: function(xhr) {
- if (xhr.status == 200) {
- content_type = xhr.getResponseHeader("Content-Type");
- if (content_type != "application/json") {
- acknowledgeFailed(subject, "Got "+content_type+" not application/json from server");
- } else {
- //updateAlert(xhr.responseText.evalJSON());
- }
- } else {
- acknowledgeFailed(subject, "Connection problem");
- }
- }
- });
-}
diff --git a/static/mauve_utils.js.old b/static/mauve_utils.js.old
new file mode 100644
index 0000000..2a14dc2
--- /dev/null
+++ b/static/mauve_utils.js.old
@@ -0,0 +1,150 @@
+// rather simple first stab at automating image rollovers - any image with
+// a class of auto_hover will set its source to be the original name + _hover.png
+// when rolled over, and back again when the mouse moves away.
+//
+// need to initialise by calling addAutoHover() after document has loaded.
+//
+function addAutoHover() {
+ $$('img.auto_hover').each(function(image) {
+ image.observe('mouseover', function(event) {
+ image.src = image.src.gsub(".png", "_hover.png");
+ });
+ image.observe('mouseout', function(event) {
+ image.src = image.src.gsub("_hover.png", ".png");
+ });
+ preload = new Image();
+ preload.src = image.src.gsub(".png", "_hover.png");
+ });
+};
+
+function addRefresh() {
+ updater1 = new Ajax.PeriodicalUpdater("alert_summary", "/_alert_summary",
+ { method: 'get', frequency: 120 });
+ updater2 = new Ajax.PeriodicalUpdater("alert_counts", "/_alert_counts",
+ { method: 'get', frequency: 120 });
+}
+
+// Pop up the big white box at the top when something goes wrong, scroll so
+// user can see it.
+//
+function reportError(message) {
+ $('errors_list').insert('<li>'+message+'</li>');
+ $('errors').show();
+ $('errors').scrollTo();
+}
+// Hide the big white box again
+//
+function clearErrors() { $('errors').hide(); }
+
+// Wrapper around reportError to report an error in updating a particular
+// alert.
+//
+function acknowledgeFailed(id, message) {
+ if (message)
+ reportError("<strong>Couldn't update alert "+id+":</strong> "+message);
+ else
+ reportError("<strong>Couldn't update alert "+id+"</strong>");
+}
+
+// Updates the page from a JSON representation of a particular alert.
+//
+function updateAlert(alert) {
+ var strip = $('alert_'+alert.id);
+ if (!strip) {
+ reportError("Alert "+id+" not rendered - bug?");
+ return;
+ }
+
+ image = strip.down(".acknowledge img");
+ image.src = alert.acknowledged_at ?
+ "/images/acknowledge_acknowledged.png" :
+ "/images/acknowledge_unacknowledged.png"
+
+ if (strip.down(".source"))
+ strip.down(".source").update(alert.source);
+ if (strip.down(".subject"))
+ strip.down(".subject").update(alert.subject);
+ if (strip.down(".summary") && strip.down(".summary").down())
+ strip.down(".summary").down().update(alert.summary);
+ strip.next().update(alert.detail);
+
+ if (alert.acknowledged_at)
+ strip.next().hide();
+}
+
+// called when user hits the acknowledge button for an alert - makes a callback
+// to the server to communicate the change, and updates the button
+// appropriately.
+//
+function toggleAcknowledge(id) {
+ updater = new Ajax.Request('/alert/'+id+'/acknowledge', {
+
+ method: 'post',
+
+ // ignored by server, see http://www.ruby-forum.com/topic/162976 for why
+ postBody: 'x',
+
+ onFailure: function(xhr) { acknowledgeFailed(id, "Failure - "+xhr.statusText); },
+
+ onException: function(xhr, ex) { acknowledgeFailed(id, Dumper(ex)); },
+
+ onSuccess: function(xhr) {
+ if (xhr.status == 200) {
+ content_type = xhr.getResponseHeader("Content-Type");
+ if (content_type != "application/json") {
+ acknowledgeFailed(id, "Got "+content_type+" not application/json from server");
+ } else {
+ updateAlert(xhr.responseText.evalJSON());
+ }
+ } else {
+ acknowledgeFailed(id, "Connection problem");
+ }
+ }
+ });
+};
+
+
+// Controls the showing of details on alerts.
+function toggleDetailView(id) {
+ updater = new Ajax.Request('/alert/'+id+'/toggleDetailView', {
+ method: 'post',
+ postBody: 'x',
+ onFailure: function(xhr) { acknowledgeFailed(id, "Failure - "+xhr.statusText); },
+ onException: function(xhr, ex) { acknowledgeFailed(id, Dumper(ex)); },
+ onSuccess: function(xhr) {
+ if (xhr.status == 200) {
+ content_type = xhr.getResponseHeader("Content-Type");
+ if (content_type != "application/json") {
+ acknowledgeFailed(id, "Got "+content_type+" not application/json from server");
+ } else {
+ //updateAlert(xhr.responseText.evalJSON());
+ }
+ } else {
+ acknowledgeFailed(id, "Connection problem");
+ }
+ }
+ });
+}
+
+
+// Controls the showing of folding on alerts.
+function toggleFoldingView(subject) {
+ updater = new Ajax.Request('/alert/fold/'+subject, {
+ method: 'post',
+ postBody: 'x',
+ onFailure: function(xhr) { acknowledgeFailed(subject, "Failure - "+xhr.statusText); },
+ onException: function(xhr, ex) { acknowledgeFailed(subject, Dumper(ex)); },
+ onSuccess: function(xhr) {
+ if (xhr.status == 200) {
+ content_type = xhr.getResponseHeader("Content-Type");
+ if (content_type != "application/json") {
+ acknowledgeFailed(subject, "Got "+content_type+" not application/json from server");
+ } else {
+ //updateAlert(xhr.responseText.evalJSON());
+ }
+ } else {
+ acknowledgeFailed(subject, "Connection problem");
+ }
+ }
+ });
+}
diff --git a/static/stylesheets/bytemark.css b/static/stylesheets/bytemark.css
new file mode 100644
index 0000000..c0fd54a
--- /dev/null
+++ b/static/stylesheets/bytemark.css
@@ -0,0 +1,183 @@
+/*----------------------------\
+| Bytemark Internal Stylesheet|
+\-----------------------------/
+
+To give your page a nice feel, make sure it starts:
+ <html><head>
+ <link rel="stylesheet"
+ href="https://admin.bytemark.co.uk/common/bytemark.css"
+ type="text/css"
+ />
+ </head><body>
+ .
+ .
+ .
+ .
+ </body></head>
+
+Using headers in the correct order (i.e. h1, h2, h3) also helps.
+
+Patrick
+2004-06-38
+*/
+
+pre, code {
+ font-family: monospace;
+}
+
+ul {
+ background-color: white;
+ color: black;
+}
+
+.OK {
+ background-color: LawnGreen;
+ color: black;
+}
+
+.Failed {
+ background-color: OrangeRed;
+ color: black;
+}
+
+h1 {
+ background-color: #ffea05;
+ color: black;
+ padding: 10px;
+ margin: 5px;
+ font-size: x-large;
+ font-weight: normal;
+}
+h2 {
+ background-color: #ABABAB;
+ color: white;
+ padding: 5px 5px;
+ margin: 5px 5px 0px 5px;
+ font-weight: bold;
+ font-size: large;
+}
+h2 a {
+ color: white;
+ font-weight: bold;
+ font-size: large;
+}
+
+h3, th {
+ font-weight: bold;
+ font-size: small;
+ background-color: #E4E4E4;
+ color: black;
+}
+h3 {
+ padding: 3px 5px;
+ margin: 5px 5px 0px 5px;
+}
+h4 {
+ padding: 3px 5px;
+ margin: 5px 5px 0px 5px;
+}
+html {
+ background-color: #666;
+ color: white;
+ padding: 5px 5px;
+ font-family: arial, sans-serif;
+}
+
+body {
+ margin: 0px;
+ padding: 5px 5px 5px 5px;
+ background-color: white;
+ color: black;
+ border: none;
+}
+
+div#navbar {
+ /* try to cancel out the body padding above, leaving a bit of a margin beneath */
+ margin: -5px -5px 5px -5px;
+ padding-left: 15px;
+ width: 100%;
+ background-color: #666;
+ font-size: 14px;
+}
+
+div#navbar ul {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+}
+
+div#navbar ul li {
+ float: left;
+ margin-right: 2px;
+ padding: 4px 4px;
+ background: none;
+ color: #333;
+ background: #ccc;
+ border: 1px solid #9b8748;
+ border-bottom: none;
+}
+
+div#navbar a {
+ display: block;
+ text-decoration: none;
+ margin: 0px;
+ min-height: 16px;
+ color: inherit;
+ vertical-align: bottom;
+}
+
+div#navbar li#nav_index a {
+ background-image: url(/common/images/BytemarkLogo180.png);
+ background-repeat:no-repeat;
+ background-position:left center;
+ padding-left: 180px;
+ width: 0px;
+ overflow: hidden;
+}
+
+div#navbar li#nav_index, div#navbar li#nav_selected {
+ background-color: white;
+ color: #333;
+}
+
+div#navbar br {
+ clear: both;
+}
+
+p {
+ margin: 0px 5px;
+ padding: 7px;
+ background-color: white;
+ color: black;
+}
+table {
+ margin: 5px 0px;
+ padding: 0px;
+}
+table.full {
+ width: 100%;
+}
+tr {
+ margin: 0px;
+ padding: 0px;
+}
+td {
+ vertical-align: top;
+ margin: 5px;
+ padding: 5px;
+}
+ul {
+ padding: 0px 20px;
+}
+a {
+ text-decoration: none;
+}
+a:link {
+ color: #0061ab;
+}
+
+div#nav form {
+ display: inline;
+}
+
+
diff --git a/static/stylesheets/mauve.css b/static/stylesheets/mauve.css
new file mode 100644
index 0000000..d15e588
--- /dev/null
+++ b/static/stylesheets/mauve.css
@@ -0,0 +1,41 @@
+tr.hilight {
+ background-color: #eee;
+}
+
+tr.triggered.hilight {
+ background-color: #f98;
+}
+
+tr.acknowledged.triggered.hilight {
+ background-color: #fa5;
+}
+
+
+.triggered {
+ background-color: #fba;
+}
+
+.acknowledged {
+ color: #444;
+}
+
+.acknowledged.triggered {
+ background-color: #fb6;
+}
+
+.missing_data {
+
+}
+
+body {
+ min-width: 800px;
+}
+
+.out_of_date {
+ font-style: italic;
+}
+
+.detail {
+ background-color: white
+}
+
diff --git a/views/_detail.haml b/views/_detail.haml
new file mode 100644
index 0000000..282e454
--- /dev/null
+++ b/views/_detail.haml
@@ -0,0 +1,3 @@
+.detail
+ :textile
+ #{alert.detail}
diff --git a/views/_header.haml b/views/_header.haml
new file mode 100644
index 0000000..4f5c12a
--- /dev/null
+++ b/views/_header.haml
@@ -0,0 +1,9 @@
+!!! XML
+!!!
+%html
+ %head
+ %meta{:name=>"viewport", :content=>"width=device-width"}/
+ %title #{@title}: Authentication required
+ %link{:rel => "stylesheet", :href => "/stylesheets/bytemark.css"}/
+ %link{:rel => "stylesheet", :href => "/stylesheets/mauve.css"}/
+ %script{:src => 'javascript/jquery/jquery.js', :type => 'text/javascript'}
diff --git a/views/_navbar.haml b/views/_navbar.haml
new file mode 100644
index 0000000..2384baf
--- /dev/null
+++ b/views/_navbar.haml
@@ -0,0 +1,15 @@
+#navbar
+ %ul
+ %li
+ %a{:href => "/"} Mauve
+ - if @person
+ %li{:id => [ @alert_type == "raised" && "nav_selected"]}
+ %a{:href => '/alerts/raised/'+@group_by} Raised (#{@alerts_raised.length})
+ %li{:id => [ @alert_type == "acknowledged" && "nav_selected"]}
+ %a{:href => '/alerts/acknowledged/'+@group_by} Ack'd (#{@alerts_ackd.length})
+ %li{:id => [ @alert_type == "cleared" && "nav_selected"]}
+ %a{:href => '/alerts/cleared/'+@group_by} Cleared (#{@alerts_cleared.length})
+ %li
+ %a{:href => '/logout'} Log out
+ %br
+
diff --git a/views/_navigation.haml b/views/_navigation.haml
index 6c47b28..c3301b2 100644
--- a/views/_navigation.haml
+++ b/views/_navigation.haml
@@ -5,7 +5,7 @@
%a{:href => "/logout"} Logout <strong>(#{@person.username})</strong>
- else
%form{:action => '/login', :method => :POST}
- Username
+ UsernameArse
%input{:name => 'username', :type => 'text', :size => 10}
Password
%input{:name => 'password', :type => 'password', :size => 10}
diff --git a/views/alert.haml b/views/alert.haml
index 4310ddc..9d25c47 100644
--- a/views/alert.haml
+++ b/views/alert.haml
@@ -2,18 +2,18 @@
%html
%head
%title #{@title}: Alert #{@alert.id}: #{@alert.get_safe_html_summary}
- %link{:rel => "stylesheet", :href => "/alerts2.css"}
- %link{:rel => "stylesheet", :href => "/alerts-mobil.css", :media => "handheld"}
- %script{:type => "text/javascript", :src => "/prototype.js"}
- %script{:type => "text/javascript", :src => "/mauve_utils.js"}
- %script{:type => "text/javascript", :src => "/datadumper.js"}
+ %link{:rel => "stylesheet", :href => "/alerts2.css"}/
+ %link{:rel => "stylesheet", :href => "/alerts-mobil.css", :media => "handheld"} /
+ %script{:type => "text/javascript", :src => "/prototype.js"}/
+ %script{:type => "text/javascript", :src => "/mauve_utils.js"}/
+ %script{:type => "text/javascript", :src => "/datadumper.js"}/
%body{:onLoad => "addAutoHover(); addRefresh();"}
.head= partial("head3")
#about_alert
%h1.summary #{@alert.get_safe_html_subject}: #{@alert.get_safe_html_summary}
%h2.details Alert Details
- if @alert.detail
- .detail= @alert.get_details
+ .arse= partial("detail")
- if @alert.source != @alert.subject
%h2.source Source
.source= @alert.get_safe_html_source
diff --git a/views/alerts.haml b/views/alerts.haml
index af65dea..e2a8c82 100644
--- a/views/alerts.haml
+++ b/views/alerts.haml
@@ -1,24 +1,64 @@
-!!!
+!!! HTML5
%html
%head
- %title #{@title}: Current alerts (logged in as #{@person.username})
- %link{:rel => "stylesheet", :href => "/alerts.css"}
- %link{:rel => "stylesheet", :href => "/alerts-mobil.css", :media => "handheld"}
- %script{:type => "text/javascript", :src => "/prototype.js"}
- %script{:type => "text/javascript", :src => "/mauve_utils.js"}
- %script{:type => "text/javascript", :src => "/datadumper.js"}
- %body{:onLoad => "addAutoHover(); addRefresh();"}
- #header
- %h1= @title
- #errors{:style => "display: none;"}
- %h1
- Trouble!
- (
- %a{:href=>"#", :onClick => "clearErrors();"} hide
- )
- %ul#errors_list
- %p Either the alert server or your internet connection is malfunctioning, so you may want to try refreshing the page.
- #alert_counts= partial("alert_counts")
- #navigation= partial("navigation")
- #alert_summary= partial("alert_summary")
- //%p The session is #{session.inspect()}
+ %meta{:name=>"viewport", :content=>"width=device-width"}/
+ %title #{@title}: Alerts
+ %link{:rel => "stylesheet", :href => "/stylesheets/bytemark.css"}/
+ %link{:rel => "stylesheet", :href => "/stylesheets/mauve.css"}/
+ %script{:src => '/javascript/prototype/prototype.js', :type => 'text/javascript'}
+ %script{:src => '/javascript/scriptaculous/scriptaculous.js', :type => 'text/javascript'}
+ %body
+ = partial('navbar')
+ %h1 Mauve Alerts
+ %form
+ %table
+ %tr
+ %th
+ %input{ :type => "checkbox", :name => "all" }
+ %th Lvl
+ %th Subject
+ %th Summary
+ %th Raised at
+ %th Actions
+ - count = 0
+ - @grouped_alerts.each do |group, alerts|
+ - count += 1
+ - row_class = [ count % 2 == 0 && "hilight", alerts.first.raised? && "triggered", alerts.first.acknowledged? && "acknowledged"]
+ - first_n = (alerts.length > 2 ? 1 : 2)
+ - alerts.first(first_n).each do |alert|
+ %tr{ :class => row_class, :id => ["summary", alert.id] }
+ %td
+ %input{ :type => "checkbox", :name => alert.id }
+ %td #{alert.level}
+ %td #{alert.subject}
+ %td
+ #{alert.summary}
+ %a{ :href => "/alert/#{alert.id}", :onclick => "$('detail_#{alert.id}').toggle(); return false;"} show details &darr;
+ %td #{alert.raised_at}
+ %td arse
+ %tr{ :class => %w(detail) + row_class, :id => ["detail", alert.id], :style => "display: none;" }
+ %td &nbsp;
+ %td{:colspan => 5}
+ = partial(:detail, :locals => {:alert => alert})
+ %p
+ Source: #{alert.source} &bull;
+ %a{ :href => "/alert/#{alert.id}" } More details
+ &bull;
+ %a{ :href => "#", :onclick => "$('detail_#{alert.id}').hide(); return false;"} Hide details &uarr;
+ - if alerts.length > 2
+ %tr{ :class => [ count % 2 == 0 && "hilight", alerts.first.raised? && "triggered", alerts.first.acknowledged? && "acknowledged"]}
+ %td &nbsp;
+ %td{:colspan => 5} There are #{alerts.length - 1} more alerts in this group.
+ %tr
+ %td{:colspan => 6}
+ Acknowledge these alerts for
+ %input{ :name => 'hours', :type => "number", :min => 1, :max => 24, :value => 2}
+ %select
+ %option{ :value => "daytime" } daytime
+ %option{ :value => "working" } working
+ %option{ :value => "wall" } wall
+ hours (until
+ %span{:id => "ack_until"}
+ = Time.now + 2
+ )
+ %input{ :type => 'submit' }
diff --git a/views/login.haml b/views/login.haml
new file mode 100644
index 0000000..225c2e8
--- /dev/null
+++ b/views/login.haml
@@ -0,0 +1,27 @@
+!!! XML
+!!!
+%html
+ %head
+ %meta{:name=>"viewport", :content=>"width=device-width"}/
+ %title #{@title}: Authentication required
+ %link{:rel => "stylesheet", :href => "/stylesheets/bytemark.css"}/
+ %link{:rel => "stylesheet", :href => "/stylesheets/mauve.css"}/
+ %script{:src => 'javascript/jquery/jquery.js', :type => 'text/javascript'}
+ %body
+ = partial('navbar')
+ %h1 Mauve Alerts
+ - if flash['error']
+ %p.error= flash['error']
+ %form{:action => '/login', :method => :POST}
+ %fieldset
+ %legend Please log in
+ %label{:for => "username"} Username
+ %input{:name => 'username', :type => 'text'}/
+ %br
+ %label{:for => "password", :title => "This is either your Single Sign On password or a PIN."} Password / PIN
+ %input{:name => 'password', :type => 'password'}/
+ %br
+ %input{:type => 'hidden', :name => 'next_page', :value => @next_page}/
+ %input{:type => 'submit', :value => 'Log in'}/
+
+
diff --git a/views/please_authenticate.haml b/views/please_authenticate.haml
deleted file mode 100644
index 9416c8e..0000000
--- a/views/please_authenticate.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-!!!
-%html
- %head
- %meta{:name=>"viewport", :content=>"width=device-width"}
- %title #{@title}: Authentication required
- %link{:rel => "stylesheet", :href => "/alerts2.css"}
- %body
- .loginForm
- %form{:action => '/login', :method => :POST}
- Username
- %input{:name => 'username', :type => 'text'}
- %br
- Password
- %input{:name => 'password', :type => 'password'}
- %br
- %input{:type => 'submit', :value => 'Log into the Mauve alert panel', :class=>"submitLoginButton"}
- .loginNotes
- This is either your single sign on or a PIN.
- %br
- You
- %strong
- must
- be logged in to view alerts.
-
-