diff --git a/README.md b/README.md new file mode 100755 index 0000000..dee1710 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +StatusBoard +============= + +StatusBoard is a simple PHP web-based tool for displaying the status of services. Administrators can manually record incidents and provide estimated end times and simple descriptions. This tool is suitable for exposing status information to customers or other third parties and does not need to be connected to internal systems. + +Features +------- + +* Customisable list of Services and Sites. +* Manual reporting and status changes for Incidents. +* Multiple severity levels. +* Full admin UI. + +Requirements +------------ + +* PHP +* MYSQL +* Smarty +* sihnon-php-lib: https://github.com/optiz0r/sihnon-php-lib + diff --git a/build/schema/mysql.demo.sql b/build/schema/mysql.demo.sql new file mode 100644 index 0000000..73e82d8 --- /dev/null +++ b/build/schema/mysql.demo.sql @@ -0,0 +1,106 @@ +-- phpMyAdmin SQL Dump +-- version 3.1.4 +-- http://www.phpmyadmin.net +-- +-- Host: localhost:3306 +-- Generation Time: Dec 16, 2011 at 01:27 AM +-- Server version: 5.1.53 +-- PHP Version: 5.3.6-pl1-gentoo + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + +-- +-- Database: `status-board` +-- + +-- -------------------------------------------------------- + +-- +-- Dumping data for table `settings` +-- + +UPDATE `settings` SET `value`='Example Status Board' WHERE `name`='site.title'; + +-- +-- Dumping data for table `service` +-- + +INSERT INTO `service` (`id`, `name`, `description`) VALUES +(1, 'Internet', 'Shared Internet connection.'), +(2, 'Web', 'Hosted web servers'), +(3, 'Email', 'Hosted email services'), +(4, 'DNS', 'Hosted DNS services'), +(5, 'LDAP', 'Hosted directory services'); + +-- +-- Dumping data for table `site` +-- + +INSERT INTO `site` (`id`, `service`, `name`, `description`) VALUES +(1, 1, 'Local', 'Local Internet access'), +(2, 2, 'Offsite', 'Offsite web services'), +(3, 3, 'Offsite', 'Offsite email services'), +(4, 4, 'Local', 'Primary DNS services'), +(5, 4, 'Offsite', 'Backup DNS services'), +(6, 5, 'Local', 'Local LDAP services'), +(7, 5, 'Offsite', 'Offsite LDAP services'); + +-- +-- Dumping data for table `incident` +-- + +INSERT INTO `incident` (`id`, `site`, `reference`, `description`, `start_time`, `estimated_end_time`, `actual_end_time`) VALUES +(1, 1, 'UK:0001', 'Intermittent packetloss on primary internet connection', 1324079805, 1324079805, NULL), +(2, 1, 'UK:0002', 'Full outage', 1324079805, 1324079805, NULL), +(3, 4, 'UK:0003', 'DNS zone maintenance', 1324082411, 1324082411, NULL); + +-- +-- Dumping data for table `incidentstatus` +-- + +INSERT INTO `incidentstatus` (`id`, `incident`, `status`, `description`, `ctime`) VALUES +(1, 1, 2, 'Initial classification', 1324079864), +(2, 2, 4, 'Initial classification', 1324079864), +(3, 1, 3, 'Status upgraded due to increasing impact from the ongoing issue.', 1324080307), +(4, 3, 1, 'Initial classification', 1324082426); + +-- +-- Dumping data for table `user` +-- + +INSERT INTO `user` (`id`, `username`, `password`, `fullname`, `email`, `last_login`, `last_password_change`) VALUES +(2, 'guest', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Guest', NULL, NULL, 1324211553); + +-- +-- Dumping data for table `group` +-- + +INSERT INTO `group` (`id`, `name`, `description`) VALUES +(2, 'readonly', 'Basic group with read only access to the status boards.'); + +-- +-- Dumping data for table `usergroup` +-- + +INSERT INTO `usergroup` (`id`, `user`, `group`, `added`) VALUES +(2, 2, 2, 1324211572); + +-- +-- Dumping data for table `permission` +-- + +INSERT INTO `permission` (`id`, `name`, `description`) VALUES +(2, 'Update Status Boards', 'Permission to add/edit/delete any service or site.'), +(3, 'Update Incidents', 'Permission to create and update the status of any incident.'), +(4, 'View Status Boards', 'Permission to view the status of all services and sites, and details of any incident.'); + +-- +-- Dumping data for table `grouppermission` +-- + +INSERT INTO `grouppermission` (`id`, `group`, `permission`, `added`) VALUES +(2, 1, 2, 1324211935), +(3, 1, 3, 1324211935), +(4, 1, 4, 1324211935), +(5, 2, 4, 1324211935); + diff --git a/build/schema/mysql.sql b/build/schema/mysql.sql index 28c3ceb..bf99125 100644 --- a/build/schema/mysql.sql +++ b/build/schema/mysql.sql @@ -18,11 +18,6 @@ SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- Table structure for table `settings` -- --- Creation: Sep 24, 2010 at 07:22 PM --- Last update: Dec 04, 2011 at 01:19 PM --- Last check: Aug 20, 2011 at 10:32 PM --- - DROP TABLE IF EXISTS `settings`; CREATE TABLE IF NOT EXISTS `settings` ( `name` varchar(255) NOT NULL, @@ -34,10 +29,10 @@ CREATE TABLE IF NOT EXISTS `settings` ( -- -- Dumping data for table `settings` -- - INSERT INTO `settings` (`name`, `value`, `type`) VALUES ('debug.display_exceptions', '1', 'bool'), ('cache.base_dir', '/dev/shm/status-board/', 'string'), +('auth', 'Database', 'string'), ('logging.plugins', 'Database\nFlatFile', 'array(string)'), ('logging.Database', 'webui', 'array(string)'), ('logging.Database.webui.table', 'log', 'string'), @@ -48,14 +43,14 @@ INSERT INTO `settings` (`name`, `value`, `type`) VALUES ('logging.FlatFile.tmp.format', '%timestamp% %hostname%:%pid% %progname%:%file%[%line%] %message%', 'string'), ('logging.FlatFile.tmp.severity', 'debug\ninfo\nwarning\nerror', 'array(string)'), ('logging.FlatFile.tmp.category', 'webui\ndefault', 'array(string)'), -('templates.tmp_path', '/var/tmp/status-board/', 'string'); +('templates.tmp_path', '/var/tmp/status-board/', 'string'), +('site.title', 'Status Board', 'string'), +('sessions', 1, 'bool'), +('sessions.path', '/', 'string'); -- -- Table structure for table `log` -- --- Creation: Aug 20, 2011 at 10:32 PM --- - DROP TABLE IF EXISTS `log`; CREATE TABLE IF NOT EXISTS `log` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -71,3 +66,307 @@ CREATE TABLE IF NOT EXISTS `log` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; +-- +-- Table structure for table `service` +-- +DROP TABLE IF EXISTS `service`; +CREATE TABLE IF NOT EXISTS `service` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(32) NOT NULL, + `description` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Table structure for table `service` +-- +DROP TABLE IF EXISTS `site`; +CREATE TABLE IF NOT EXISTS `site` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `service` int(10) unsigned NOT NULL, + `name` varchar(32) NOT NULL, + `description` text NOT NULL, + PRIMARY KEY (`id`), + KEY `service` (`service`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Table structure for table `incident` +-- +DROP TABLE IF EXISTS `incident`; +CREATE TABLE IF NOT EXISTS `incident` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `site` int(10) unsigned NOT NULL, + `reference` varchar(32) NOT NULL, + `description` text NOT NULL, + `start_time` int(10) NOT NULL, + `estimated_end_time` int(10) NULL, + `actual_end_time` int(10) NULL, + PRIMARY KEY (`id`), + KEY `site` (`site`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Table structure for table `incidentstatus` +-- +DROP TABLE IF EXISTS `incidentstatus`; +CREATE TABLE IF NOT EXISTS `incidentstatus` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `incident` int(10) unsigned NOT NULL, + `status` int(10) unsigned NOT NULL, + `description` text NOT NULL, + `ctime` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `incident` (`incident`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Table structure for view `incidentstatus_current_int` +-- +DROP VIEW IF EXISTS `incidentstatus_current_int`; +CREATE VIEW `incidentstatus_current_int` AS ( + SELECT + `incidentstatus`.`incident` AS `incident`, + MAX(`incidentstatus`.`id`) AS `latest` + FROM + `incidentstatus` + GROUP BY + `incidentstatus`.`incident` +); + +-- +-- Table structure for view `incidentstatus_current` +-- +DROP VIEW IF EXISTS `incidentstatus_current`; +CREATE VIEW `incidentstatus_current` AS ( + SELECT + `is`.`id` AS `id`, + `is`.`incident` AS `incident`, + `is`.`status` AS `status`, + `is`.`ctime` AS `ctime` + FROM ( + `incidentstatus` AS `is` + JOIN `incidentstatus_current_int` AS `isci` + ) + WHERE ( + (`isci`.`incident` = `is`.`incident`) + AND (`is`.`id` = `isci`.`latest`) + ) +); + +-- +-- Table structure for view `incident_open` +-- +DROP VIEW IF EXISTS `incident_open`; +CREATE VIEW `incident_open` AS ( + SELECT + `i`.*, + `isc`.`ctime` + FROM + `incident` AS `i` + JOIN `incidentstatus_current` AS `isc` + ON `i`.`id` = `isc`.`incident` + WHERE + `isc`.`status` IN (1,2,3,4) +); + +-- +-- Table structure for view `incident_closedtime` +-- +DROP VIEW IF EXISTS `incident_closedtime`; +CREATE VIEW `incident_closedtime` AS ( + SELECT + `incident` AS `incident`, + `ctime` AS `ctime` + FROM + `incidentstatus` + WHERE + `status` = 0 +); + +-- +-- Table structure for view `incident_opentimes` +-- +DROP VIEW IF EXISTS `incident_opentimes`; +CREATE VIEW `incident_opentimes` AS ( + SELECT + `i`.*, + IFNULL(`t`.`ctime`, 0xffffffff+0) AS `ctime` + FROM + `incident` as `i` + LEFT JOIN `incident_closedtime` AS `t` ON `i`.`id`=`t`.`incident` +); + +-- +-- Table structure for table `user` +-- +DROP TABLE IF EXISTS `user`; +CREATE TABLE IF NOT EXISTS `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(255) NOT NULL, + `password` char(40) NOT NULL, + `fullname` varchar(255) NULL, + `email` varchar(255) NULL, + `last_login` int(10) NULL, + `last_password_change` int(10) NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Dumping data for table `user` +-- +INSERT INTO `user` (`id`, `username`, `password`, `fullname`, `email`, `last_login`, `last_password_change`) VALUES +(1, 'admin', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 'Administrator', NULL, NULL, 1324211456); + +-- +-- Table structure for table `group` +-- +DROP TABLE IF EXISTS `group`; +CREATE TABLE IF NOT EXISTS `group` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `description` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Dumping data for table `group` +-- +INSERT INTO `group` (`id`, `name`, `description`) VALUES +(1, 'admins', 'Administrative users will full control over the status boards.'); + +-- +-- Table structure for table `usergroup` +-- +DROP TABLE IF EXISTS `usergroup`; +CREATE TABLE IF NOT EXISTS `usergroup` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user` int(10) unsigned NOT NULL, + `group` int(10) unsigned NOT NULL, + `added` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `user` (`user`,`group`), + KEY `group` (`group`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Dumping data for table `usergroup` +-- +INSERT INTO `usergroup` (`id`, `user`, `group`, `added`) VALUES +(1, 1, 1, 1324211572); + +-- +-- Table structure for view `groups_by_user` +-- +DROP VIEW IF EXISTS `groups_by_user`; +CREATE VIEW `groups_by_user` AS ( + SELECT + `u`.`id` AS `user`, + `g`.* + FROM + `usergroup` as `ug` + LEFT JOIN `user` AS `u` ON `ug`.`user`=`u`.`id` + LEFT JOIN `group` AS `g` ON `ug`.`group`=`g`.`id` +); + +-- +-- Table structure for table `permission` +-- +DROP TABLE IF EXISTS `permission`; +CREATE TABLE IF NOT EXISTS `permission` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `description` text NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Dumping data for table `permission` +-- +INSERT INTO `permission` (`id`, `name`, `description`) VALUES +(1, 'Administrator', 'Full administrative rights.'); + + +-- +-- Table structure for table `grouppermission` +-- +DROP TABLE IF EXISTS `grouppermission`; +CREATE TABLE IF NOT EXISTS `grouppermission` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `group` int(10) unsigned NOT NULL, + `permission` int(10) unsigned NOT NULL, + `added` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `group` (`group`,`permission`), + KEY `permission` (`permission`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; + +-- +-- Dumping data for table `grouppermission` +-- +INSERT INTO `grouppermission` (`id`, `group`, `permission`, `added`) VALUES +(1, 1, 1, 1324211935); + +-- +-- Table structure for view `permissions_by_group` +-- +DROP VIEW IF EXISTS `permissions_by_group`; +CREATE VIEW `permissions_by_group` AS ( + SELECT + `g`.`id` AS `group`, + `p`.* + FROM + `grouppermission` as `gp` + LEFT JOIN `group` AS `g` ON `gp`.`group`=`g`.`id` + LEFT JOIN `permission` AS `p` on `gp`.`permission`=`p`.`id` +); + +-- +-- Table structure for view `permissions_by_user` +-- +DROP VIEW IF EXISTS `permissions_by_user`; +CREATE VIEW `permissions_by_user` AS ( + SELECT + `u`.`id` AS `user`, + `p`.* + FROM + `usergroup` as `ug` + LEFT JOIN `user` AS `u` ON `ug`.`user`=`u`.`id` + LEFT JOIN `permissions_by_group` AS `p` on `ug`.`group`=`p`.`group` +); + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `grouppermission` +-- +ALTER TABLE `grouppermission` + ADD CONSTRAINT `grouppermission_ibfk_2` FOREIGN KEY (`permission`) REFERENCES `permission` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `grouppermission_ibfk_1` FOREIGN KEY (`group`) REFERENCES `group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `incident` +-- +ALTER TABLE `incident` + ADD CONSTRAINT `incident_ibfk_1` FOREIGN KEY (`site`) REFERENCES `site` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `incidentstatus` +-- +ALTER TABLE `incidentstatus` + ADD CONSTRAINT `incidentstatus_ibfk_1` FOREIGN KEY (`incident`) REFERENCES `incident` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `site` +-- +ALTER TABLE `site` + ADD CONSTRAINT `site_ibfk_1` FOREIGN KEY (`service`) REFERENCES `service` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `usergroup` +-- +ALTER TABLE `usergroup` + ADD CONSTRAINT `usergroup_ibfk_2` FOREIGN KEY (`group`) REFERENCES `group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `usergroup_ibfk_1` FOREIGN KEY (`user`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/public/images/Status_Icons/cross-circle.png b/public/images/Status_Icons/cross-circle.png new file mode 100755 index 0000000..20d6f5e Binary files /dev/null and b/public/images/Status_Icons/cross-circle.png differ diff --git a/public/images/Status_Icons/exclamation.png b/public/images/Status_Icons/exclamation.png new file mode 100755 index 0000000..9b0460e Binary files /dev/null and b/public/images/Status_Icons/exclamation.png differ diff --git a/public/images/Status_Icons/tick-circle.png b/public/images/Status_Icons/tick-circle.png new file mode 100755 index 0000000..210b1a6 Binary files /dev/null and b/public/images/Status_Icons/tick-circle.png differ diff --git a/public/images/Status_Icons/traffic-cone.png b/public/images/Status_Icons/traffic-cone.png new file mode 100755 index 0000000..394dba0 Binary files /dev/null and b/public/images/Status_Icons/traffic-cone.png differ diff --git a/public/images/favicon.ico b/public/images/favicon.ico new file mode 100755 index 0000000..210b1a6 Binary files /dev/null and b/public/images/favicon.ico differ diff --git a/public/less/bootstrap.less b/public/less/bootstrap.less new file mode 100644 index 0000000..e69de29 diff --git a/public/scripts/3rdparty/bootstrap-alerts.js b/public/scripts/3rdparty/bootstrap-alerts.js new file mode 100644 index 0000000..37bb430 --- /dev/null +++ b/public/scripts/3rdparty/bootstrap-alerts.js @@ -0,0 +1,113 @@ +/* ========================================================== + * bootstrap-alerts.js v1.4.0 + * http://twitter.github.com/bootstrap/javascript.html#alerts + * ========================================================== + * Copyright 2011 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function( $ ){ + + "use strict" + + /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) + * ======================================================= */ + + var transitionEnd + + $(document).ready(function () { + + $.support.transition = (function () { + var thisBody = document.body || document.documentElement + , thisStyle = thisBody.style + , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined + return support + })() + + // set CSS transition event type + if ( $.support.transition ) { + transitionEnd = "TransitionEnd" + if ( $.browser.webkit ) { + transitionEnd = "webkitTransitionEnd" + } else if ( $.browser.mozilla ) { + transitionEnd = "transitionend" + } else if ( $.browser.opera ) { + transitionEnd = "oTransitionEnd" + } + } + + }) + + /* ALERT CLASS DEFINITION + * ====================== */ + + var Alert = function ( content, options ) { + this.settings = $.extend({}, $.fn.alert.defaults, options) + this.$element = $(content) + .delegate(this.settings.selector, 'click', this.close) + } + + Alert.prototype = { + + close: function (e) { + var $element = $(this).parent('.alert-message') + + e && e.preventDefault() + $element.removeClass('in') + + function removeElement () { + $element.remove() + } + + $.support.transition && $element.hasClass('fade') ? + $element.bind(transitionEnd, removeElement) : + removeElement() + } + + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + $.fn.alert = function ( options ) { + + if ( options === true ) { + return this.data('alert') + } + + return this.each(function () { + var $this = $(this) + + if ( typeof options == 'string' ) { + return $this.data('alert')[options]() + } + + $(this).data('alert', new Alert( this, options )) + + }) + } + + $.fn.alert.defaults = { + selector: '.close' + } + + $(document).ready(function () { + new Alert($('body'), { + selector: '.alert-message[data-alert] .close' + }) + }) + +}( window.jQuery || window.ender ); \ No newline at end of file diff --git a/public/scripts/3rdparty/bootstrap-dropdown.js b/public/scripts/3rdparty/bootstrap-dropdown.js new file mode 100644 index 0000000..cab0ec2 --- /dev/null +++ b/public/scripts/3rdparty/bootstrap-dropdown.js @@ -0,0 +1,55 @@ +/* ============================================================ + * bootstrap-dropdown.js v1.4.0 + * http://twitter.github.com/bootstrap/javascript.html#dropdown + * ============================================================ + * Copyright 2011 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function( $ ){ + + "use strict" + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function ( selector ) { + return this.each(function () { + $(this).delegate(selector || d, 'click', function (e) { + var li = $(this).parent('li') + , isActive = li.hasClass('open') + + clearMenus() + !isActive && li.toggleClass('open') + return false + }) + }) + } + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + var d = 'a.menu, .dropdown-toggle' + + function clearMenus() { + $(d).parent('li').removeClass('open') + } + + $(function () { + $('html').bind("click", clearMenus) + $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' ) + }) + +}( window.jQuery || window.ender ); \ No newline at end of file diff --git a/public/scripts/3rdparty/bootstrap-modal.js b/public/scripts/3rdparty/bootstrap-modal.js new file mode 100644 index 0000000..be2315a --- /dev/null +++ b/public/scripts/3rdparty/bootstrap-modal.js @@ -0,0 +1,260 @@ +/* ========================================================= + * bootstrap-modal.js v1.4.0 + * http://twitter.github.com/bootstrap/javascript.html#modal + * ========================================================= + * Copyright 2011 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function( $ ){ + + "use strict" + + /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) + * ======================================================= */ + + var transitionEnd + + $(document).ready(function () { + + $.support.transition = (function () { + var thisBody = document.body || document.documentElement + , thisStyle = thisBody.style + , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined + return support + })() + + // set CSS transition event type + if ( $.support.transition ) { + transitionEnd = "TransitionEnd" + if ( $.browser.webkit ) { + transitionEnd = "webkitTransitionEnd" + } else if ( $.browser.mozilla ) { + transitionEnd = "transitionend" + } else if ( $.browser.opera ) { + transitionEnd = "oTransitionEnd" + } + } + + }) + + + /* MODAL PUBLIC CLASS DEFINITION + * ============================= */ + + var Modal = function ( content, options ) { + this.settings = $.extend({}, $.fn.modal.defaults, options) + this.$element = $(content) + .delegate('.close', 'click.modal', $.proxy(this.hide, this)) + + if ( this.settings.show ) { + this.show() + } + + return this + } + + Modal.prototype = { + + toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + this.isShown = true + this.$element.trigger('show') + + escape.call(this) + backdrop.call(this, function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + that.$element + .appendTo(document.body) + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + transition ? + that.$element.one(transitionEnd, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + + return this + } + + , hide: function (e) { + e && e.preventDefault() + + if ( !this.isShown ) { + return this + } + + var that = this + this.isShown = false + + escape.call(this) + + this.$element + .trigger('hide') + .removeClass('in') + + $.support.transition && this.$element.hasClass('fade') ? + hideWithTransition.call(this) : + hideModal.call(this) + + return this + } + + } + + + /* MODAL PRIVATE METHODS + * ===================== */ + + function hideWithTransition() { + // firefox drops transitionEnd events :{o + var that = this + , timeout = setTimeout(function () { + that.$element.unbind(transitionEnd) + hideModal.call(that) + }, 500) + + this.$element.one(transitionEnd, function () { + clearTimeout(timeout) + hideModal.call(that) + }) + } + + function hideModal (that) { + this.$element + .hide() + .trigger('hidden') + + backdrop.call(this) + } + + function backdrop ( callback ) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + if ( this.isShown && this.settings.backdrop ) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('