├── .gitignore ├── MIT-LICENSE.txt ├── README.md ├── design └── redis-live.png ├── requirements.txt └── src ├── __init__.py ├── api ├── __init__.py ├── controller │ ├── BaseController.py │ ├── BaseStaticFileHandler.py │ ├── CommandsController.py │ ├── InfoController.py │ ├── MemoryController.py │ ├── ServerListController.py │ ├── TopCommandsController.py │ ├── TopKeysController.py │ └── __init__.py └── util │ ├── RDP.py │ ├── __init__.py │ ├── settings.py │ └── timeutils.py ├── dataprovider ├── __init__.py ├── dataprovider.py ├── redisprovider.py └── sqliteprovider.py ├── db ├── redislive.sqlite └── schema.sql ├── redis-live.conf.example ├── redis-live.py ├── redis-monitor.py ├── util ├── redis-fill-data.py └── redis-fill-data2.py └── www ├── images └── logo.png ├── index.html └── js ├── app.js ├── google ├── chart.js ├── chart.js~ ├── jsapi.css ├── jsapi.js └── visualization.js ├── libs ├── backbone │ └── backbone-min.js ├── bootstrap │ ├── bootstrap.css │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ ├── js │ │ ├── README.md │ │ ├── bootstrap-alert.js │ │ ├── bootstrap-button.js │ │ ├── bootstrap-carousel.js │ │ ├── bootstrap-collapse.js │ │ ├── bootstrap-dropdown.js │ │ ├── bootstrap-modal.js │ │ ├── bootstrap-popover.js │ │ ├── bootstrap-scrollspy.js │ │ ├── bootstrap-tab.js │ │ ├── bootstrap-tooltip.js │ │ ├── bootstrap-transition.js │ │ ├── bootstrap-typeahead.js │ │ └── tests │ │ │ ├── index.html │ │ │ ├── unit │ │ │ ├── bootstrap-alert.js │ │ │ ├── bootstrap-button.js │ │ │ ├── bootstrap-collapse.js │ │ │ ├── bootstrap-dropdown.js │ │ │ ├── bootstrap-modal.js │ │ │ ├── bootstrap-popover.js │ │ │ ├── bootstrap-scrollspy.js │ │ │ ├── bootstrap-tab.js │ │ │ ├── bootstrap-tooltip.js │ │ │ ├── bootstrap-transition.js │ │ │ └── bootstrap-typeahead.js │ │ │ └── vendor │ │ │ ├── jquery.js │ │ │ ├── qunit.css │ │ │ └── qunit.js │ └── less │ │ ├── accordion.less │ │ ├── alerts.less │ │ ├── badges.less │ │ ├── bootstrap.less │ │ ├── breadcrumbs.less │ │ ├── button-groups.less │ │ ├── buttons.less │ │ ├── carousel.less │ │ ├── close.less │ │ ├── code.less │ │ ├── component-animations.less │ │ ├── dropdowns.less │ │ ├── forms.less │ │ ├── grid.less │ │ ├── hero-unit.less │ │ ├── labels.less │ │ ├── layouts.less │ │ ├── mixins.less │ │ ├── modals.less │ │ ├── navbar.less │ │ ├── navs.less │ │ ├── pager.less │ │ ├── pagination.less │ │ ├── popovers.less │ │ ├── progress-bars.less │ │ ├── reset.less │ │ ├── responsive.less │ │ ├── scaffolding.less │ │ ├── sprites.less │ │ ├── tables.less │ │ ├── thumbnails.less │ │ ├── tooltip.less │ │ ├── type.less │ │ ├── utilities.less │ │ ├── variables.less │ │ ├── wells.less │ │ └── widget.less ├── handlebars │ └── handlebars-1.0.0.beta.6.js ├── jquery │ └── jquery-1.7.2.min.js ├── less │ └── less-1.3.0.min.js └── underscore │ └── underscore-min.js ├── models ├── commands-widget-model.js ├── info-widget-model.js ├── memory-widget-model.js ├── serverlist-model.js ├── top-commands-widget-model.js └── top-keys-widget-model.js └── views ├── base-widget-view.js ├── commands-widget-view.js ├── info-widget-view.js ├── memory-widget-view.js ├── serverlist-view.js ├── top-commands-widget-view.js └── top-keys-widget-view.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | 29 | .DS_Store 30 | 31 | *.sublime-workspace 32 | *.sublime-project 33 | src/redis-live.conf 34 | 35 | 36 | # Vim 37 | *.sw[po] 38 | venv 39 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Nitin Kumar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to 8 | do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RedisLive 2 | --------- 3 | original project and loading address: https://github.com/nkrode/RedisLive 4 | 5 | Visualize your redis instances, analyze query patterns and spikes. 6 | 7 |  8 | 9 | Setup Instructions 10 | ------------------ 11 | #### Installation 12 | 13 | Install Dependencies 14 | 15 | + [tornado](https://github.com/facebook/tornado) `pip install tornado` 16 | + [redis.py](https://github.com/andymccurdy/redis-py) `pip install redis` 17 | + [python-dateutil](http://labix.org/python-dateutil) `pip install python-dateutil` 18 | 19 | You'll also need argparse if you're running Python < 2.7: 20 | 21 | + [argparse](http://code.google.com/p/argparse/) `pip install argparse` 22 | 23 | 24 | Get RedisLive 25 | 26 | + Clone the repo `git clone https://github.com/kumarnitin/RedisLive.git` , or [download the latest release](https://github.com/kumarnitin/RedisLive/zipball/master) 27 | 28 | #### Configuration 29 | 30 | + edit redis-live.conf : 31 | + update the value of the key `RedisServers` to the redis instances you want to monitor. You can monitor multiple instances by appending more values to the RedisServers list. 32 | + update the value of the key `RedisStatsServer` to the redis instance you will use to store RedisLive data (this redis instance is different from the redis instances you are monitoring). 33 | + passwords can be added as an optional parameter for any redis instance 34 | 35 | if you don't have a spare redis instance to use to store Redis Live data, then you can configure to use sqlite by changing `"DataStoreType" : "sqlite"` 36 | 37 | #### Start RedisLive 38 | 39 | + start the monitoring script `./redis-monitor.py --duration=120` duration is in seconds (see caveat) 40 | + start the webserver `./redis-live.py` 41 | + RedisLive is now running @ `http://localhost:8888/index.html` 42 | 43 | 44 | #### Caveat on monitoring redis 45 | 46 | Currently the only hook into monitoring a redis instance is Redis [MONITOR](http://redis.io/commands/monitor) command, which streams back every command processed and reduces the throughput of the redis instance. It is recommended to run redis-monitor with --duration suitable for your redis deployment and scheduling it to run periodically as a cron job. 47 | 48 | 49 | Authors 50 | ------- 51 | 52 | **Nitin Kumar** 53 | 54 | + http://twitter.com/nkrode 55 | 56 | Contributors 57 | ------------ 58 | + [splee](https://github.com/splee) (Lee McFadden) 59 | + [bialecki](https://github.com/bialecki) (Andrew Bialecki) 60 | + [reustle](https://github.com/reustle) (Shane Reustle) 61 | + [markdube](https://github.com/markdube) (Mark Dube) 62 | + [skreuzer](https://github.com/skreuzer) (Steven Kreuzer) 63 | + [snikch](https://github.com/snikch) (Mal Curtis) 64 | + [quiver](https://github.com/quiver) (George) 65 | 66 | License 67 | ------- 68 | RedisLive is released under the MIT license: 69 | + www.opensource.org/licenses/MIT 70 | -------------------------------------------------------------------------------- /design/redis-live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/design/redis-live.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse==1.2.1 2 | python-dateutil==1.5 3 | redis 4 | tornado==2.1.1 5 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/__init__.py -------------------------------------------------------------------------------- /src/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/api/__init__.py -------------------------------------------------------------------------------- /src/api/controller/BaseController.py: -------------------------------------------------------------------------------- 1 | from dataprovider.dataprovider import RedisLiveDataProvider 2 | import tornado.ioloop 3 | import tornado.web 4 | import dateutil.parser 5 | 6 | 7 | class BaseController(tornado.web.RequestHandler): 8 | 9 | stats_provider = RedisLiveDataProvider.get_provider() 10 | 11 | def datetime_to_list(self, datetime): 12 | """Converts a datetime to a list. 13 | 14 | Args: 15 | datetime (datetime): The datetime to convert. 16 | """ 17 | parsed_date = dateutil.parser.parse(datetime) 18 | # don't return the last two fields, we don't want them. 19 | return tuple(parsed_date.timetuple())[:-2] 20 | 21 | # todo : fix this 22 | def average_data(self, data): 23 | """Averages data. 24 | 25 | TODO: More docstring here, once functionality is understood. 26 | """ 27 | average = [] 28 | 29 | deviation=1024*1024 30 | 31 | start = dateutil.parser.parse(data[0][0]) 32 | end = dateutil.parser.parse(data[-1][0]) 33 | difference = end - start 34 | weeks, days = divmod(difference.days, 7) 35 | minutes, seconds = divmod(difference.seconds, 60) 36 | hours, minutes = divmod(minutes, 60) 37 | 38 | # TODO: These if/elif/else branches chould probably be broken out into 39 | # individual functions to make it easier to follow what's going on. 40 | if difference.days > 0: 41 | current_max = 0 42 | current_current = 0 43 | current_d = 0 44 | 45 | for dt, max_memory, current_memory in data: 46 | d = dateutil.parser.parse(dt) 47 | if d.day != current_d: 48 | current_d = d.day 49 | average.append([dt, max_memory, current_memory]) 50 | current_max = max_memory 51 | current_current = current_memory 52 | else: 53 | if max_memory > current_max or \ 54 | current_memory > current_current: 55 | average.pop() 56 | average.append([dt, max_memory, current_memory]) 57 | current_max=max_memory 58 | current_current=current_memory 59 | elif hours > 0: 60 | current_max = 0 61 | current_current = 0 62 | current = -1 63 | keep_flag = False 64 | 65 | for dt, max_memory, current_memory in data: 66 | d = dateutil.parser.parse(dt) 67 | if d.hour != current: 68 | current = d.hour 69 | average.append([dt, max_memory, current_memory]) 70 | current_max=max_memory 71 | current_current=current_memory 72 | keep_flag=False 73 | elif abs(max_memory - current_max) > deviation or \ 74 | abs(current_memory - current_current) > deviation: 75 | #average.pop() 76 | average.append([dt, max_memory, current_memory]) 77 | current_max = max_memory 78 | current_current = current_memory 79 | keep_flag = True 80 | elif max_memory > current_max or \ 81 | current_memory > current_current: 82 | if keep_flag != True: 83 | average.pop() 84 | average.append([dt, max_memory, current_memory]) 85 | current_max = max_memory 86 | current_current = current_memory 87 | keep_flag = False 88 | else: 89 | current_max = 0 90 | current_current = 0 91 | current_m = -1 92 | keep_flag = False 93 | for dt, max_memory, current_memory in data: 94 | d = dateutil.parser.parse(dt) 95 | if d.minute != current_m: 96 | current_m = d.minute 97 | average.append([dt, max_memory, current_memory]) 98 | current_max = max_memory 99 | current_current = current_memory 100 | keep_flag = False 101 | elif abs(max_memory - current_max) > deviation or \ 102 | abs(current_memory - current_current) > deviation: 103 | #average.pop() 104 | average.append([dt, max_memory, current_memory]) 105 | current_max = max_memory 106 | current_current = current_memory 107 | keep_flag = True 108 | elif max_memory > current_max or \ 109 | current_memory > current_current: 110 | if keep_flag!=True: 111 | average.pop() 112 | average.append([dt,max_memory,current_memory]) 113 | current_max=max_memory 114 | current_current=current_memory 115 | keep_flag=False 116 | 117 | return average 118 | -------------------------------------------------------------------------------- /src/api/controller/BaseStaticFileHandler.py: -------------------------------------------------------------------------------- 1 | import tornado.web 2 | 3 | class BaseStaticFileHandler(tornado.web.StaticFileHandler): 4 | def compute_etag(self): 5 | return None 6 | 7 | def get_cache_time(self, path, modified, mime_type): 8 | return None 9 | 10 | -------------------------------------------------------------------------------- /src/api/controller/CommandsController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | import tornado.ioloop 3 | import tornado.web 4 | import dateutil.parser 5 | from datetime import datetime, timedelta 6 | 7 | 8 | class CommandsController(BaseController): 9 | 10 | def get(self): 11 | """Serves a GET request. 12 | """ 13 | return_data = dict(data=[], timestamp=datetime.now().isoformat()) 14 | 15 | server = self.get_argument("server") 16 | from_date = self.get_argument("from", None) 17 | to_date = self.get_argument("to", None) 18 | 19 | if not from_date or not to_date: 20 | end = datetime.now() 21 | delta = timedelta(seconds=120) 22 | start = end - delta 23 | else: 24 | start = dateutil.parser.parse(from_date) 25 | end = dateutil.parser.parse(to_date) 26 | 27 | difference = end - start 28 | # added to support python version < 2.7, otherwise timedelta has 29 | # total_seconds() 30 | difference_total_seconds = difference.days * 24 * 3600 31 | difference_total_seconds += difference.seconds 32 | difference_total_seconds += difference.microseconds / 1e6 33 | 34 | minutes = difference_total_seconds / 60 35 | hours = minutes / 60 36 | seconds = difference_total_seconds 37 | 38 | if hours > 120: 39 | group_by = "day" 40 | elif minutes > 120: 41 | group_by = "hour" 42 | elif seconds > 120: 43 | group_by = "minute" 44 | else: 45 | group_by = "second" 46 | 47 | combined_data = [] 48 | stats = self.stats_provider.get_command_stats(server, start, end, 49 | group_by) 50 | for data in stats: 51 | combined_data.append([data[1], data[0]]) 52 | 53 | for data in combined_data: 54 | return_data['data'].append([self.datetime_to_list(data[0]), data[1]]) 55 | 56 | self.write(return_data) 57 | -------------------------------------------------------------------------------- /src/api/controller/InfoController.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from BaseController import BaseController 3 | import tornado.ioloop 4 | import tornado.web 5 | import re 6 | 7 | 8 | class InfoController(BaseController): 9 | def get(self): 10 | """Serves a GET request. 11 | """ 12 | server = self.get_argument("server") 13 | redis_info = self.stats_provider.get_info(server) 14 | databases=[] 15 | 16 | for key in sorted(redis_info.keys()): 17 | if key.startswith("db"): 18 | database = redis_info[key] 19 | database['name']=key 20 | databases.append(database) 21 | 22 | total_keys=0 23 | for database in databases: 24 | total_keys+=database.get("keys") 25 | 26 | if(total_keys==0): 27 | databases=[{"name" : "db0", "keys" : "0", "expires" : "0"}] 28 | 29 | redis_info['databases'] = databases 30 | redis_info['total_keys']= self.shorten_number(total_keys) 31 | 32 | uptime_seconds = redis_info['uptime_in_seconds'] 33 | redis_info['uptime'] = self.shorten_time(uptime_seconds) 34 | 35 | commands_processed = redis_info['total_commands_processed'] 36 | commands_processed = self.shorten_number(commands_processed) 37 | redis_info['total_commands_processed_human'] = commands_processed 38 | 39 | self.write(redis_info) 40 | 41 | def shorten_time(self, seconds): 42 | """Takes an integer number of seconds and rounds it to a human readable 43 | format. 44 | 45 | Args: 46 | seconds (int): Number of seconds to convert. 47 | """ 48 | if seconds < 60: 49 | # less than 1 minute 50 | val = str(seconds) + " sec" 51 | elif seconds < 3600: 52 | # if the seconds is less than 1hr 53 | num = self.rounded_number(seconds, 60) 54 | if num == "60": 55 | val = '1h' 56 | else: 57 | val = num + "m" 58 | elif (seconds < 60*60*24): 59 | # if the number is less than 1 day 60 | num = self.rounded_number(seconds, 60 * 60) 61 | if num == "24": 62 | val = "1d" 63 | else: 64 | val = num + "h" 65 | else: 66 | num = self.rounded_number(seconds, 60*60*24) 67 | val = num + "d" 68 | 69 | return val 70 | 71 | def shorten_number(self, number): 72 | """Shortens a number to a human readable format. 73 | 74 | Args: 75 | number (int): Number to convert. 76 | """ 77 | if number < 1000: 78 | return number 79 | elif number >= 1000 and number < 1000000: 80 | num = self.rounded_number(number, 1000) 81 | val = "1M" if num == "1000" else num + "K" 82 | return val 83 | elif number >= 1000000 and number < 1000000000: 84 | num = self.rounded_number(number, 1000000) 85 | val = "1B" if num=="1000" else num + "M" 86 | return val 87 | elif number >= 1000000000 and number < 1000000000000: 88 | num = self.rounded_number(number, 1000000000) 89 | val = "1T" if num=="1000" else num + "B" 90 | return val 91 | else: 92 | num = self.rounded_number(number, 1000000000000) 93 | return num + "T" 94 | 95 | def rounded_number(self, number, denominator): 96 | """Rounds a number. 97 | 98 | Args: 99 | number (int|float): The number to round. 100 | denominator (int): The denominator. 101 | """ 102 | rounded = str(round(Decimal(number)/Decimal(denominator), 1)) 103 | replace_trailing_zero = re.compile('0$') 104 | no_trailing_zeros = replace_trailing_zero.sub('', rounded) 105 | replace_trailing_period = re.compile('\.$') 106 | final_number = replace_trailing_period.sub('', no_trailing_zeros) 107 | return final_number 108 | -------------------------------------------------------------------------------- /src/api/controller/MemoryController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | import tornado.ioloop 3 | import tornado.web 4 | import dateutil.parser 5 | import datetime 6 | 7 | 8 | class MemoryController(BaseController): 9 | 10 | def get(self): 11 | server = self.get_argument("server") 12 | from_date = self.get_argument("from", None) 13 | to_date = self.get_argument("to", None) 14 | 15 | return_data = dict(data=[], 16 | timestamp=datetime.datetime.now().isoformat()) 17 | 18 | if not from_date or not to_date: 19 | end = datetime.datetime.now() 20 | delta = datetime.timedelta(seconds=60) 21 | start = end - delta 22 | else: 23 | start = dateutil.parser.parse(from_date) 24 | end = dateutil.parser.parse(to_date) 25 | 26 | combined_data = [] 27 | # TODO: These variables aren't currently used; should they be removed? 28 | prev_max=0 29 | prev_current=0 30 | counter=0 31 | 32 | for data in self.stats_provider.get_memory_info(server, start, end): 33 | combined_data.append([data[0], data[1], data[2]]) 34 | 35 | for data in combined_data: 36 | d = [self.datetime_to_list(data[0]), data[1], data[2]] 37 | return_data['data'].append(d) 38 | 39 | self.write(return_data) 40 | 41 | -------------------------------------------------------------------------------- /src/api/controller/ServerListController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | from api.util import settings 3 | 4 | class ServerListController(BaseController): 5 | 6 | def get(self): 7 | servers = {"servers": self.read_server_config()} 8 | self.write(servers) 9 | 10 | def read_server_config(self): 11 | """Returns a list of servers with the 'id' field added. 12 | """ 13 | # TODO: Move this into the settings module so everything benefits. 14 | server_list = [] 15 | redis_servers = settings.get_redis_servers() 16 | 17 | for server in redis_servers: 18 | if 'password' not in server: 19 | server['password'] = None 20 | 21 | server_id = "%(server)s:%(port)s" % server 22 | s = dict(server=server['server'], port=server['port'], password=server['password'], id=server_id) 23 | server_list.append(s) 24 | 25 | return server_list 26 | -------------------------------------------------------------------------------- /src/api/controller/TopCommandsController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | import tornado.ioloop 3 | import tornado.web 4 | import dateutil.parser 5 | import datetime 6 | 7 | 8 | class TopCommandsController(BaseController): 9 | 10 | def get(self): 11 | return_data = dict(data=[], 12 | timestamp=datetime.datetime.now().isoformat()) 13 | 14 | server = self.get_argument("server") 15 | from_date = self.get_argument("from", None) 16 | to_date = self.get_argument("to", None) 17 | 18 | if not from_date or not to_date: 19 | end = datetime.datetime.now() 20 | delta = datetime.timedelta(seconds=120) 21 | start = end - delta 22 | else: 23 | start = dateutil.parser.parse(from_date) 24 | end = dateutil.parser.parse(to_date) 25 | 26 | for data in self.stats_provider.get_top_commands_stats(server, start, 27 | end): 28 | return_data['data'].append([data[0], data[1]]) 29 | 30 | self.write(return_data) 31 | -------------------------------------------------------------------------------- /src/api/controller/TopKeysController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | import tornado.ioloop 3 | import tornado.web 4 | import dateutil.parser 5 | import datetime 6 | 7 | 8 | class TopKeysController(BaseController): 9 | 10 | def get(self): 11 | return_data = dict(data=[], timestamp=datetime.datetime.now().isoformat()) 12 | 13 | server = self.get_argument("server") 14 | from_date = self.get_argument("from", None) 15 | to_date = self.get_argument("to", None) 16 | 17 | if not from_date or not to_date: 18 | end = datetime.datetime.now() 19 | delta = datetime.timedelta(seconds=120) 20 | start = end - delta 21 | else: 22 | start = dateutil.parser.parse(from_date) 23 | end = dateutil.parser.parse(to_date) 24 | 25 | for data in self.stats_provider.get_top_keys_stats(server, start, end): 26 | return_data['data'].append([data[0], data[1]]) 27 | 28 | self.write(return_data) 29 | -------------------------------------------------------------------------------- /src/api/controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/api/controller/__init__.py -------------------------------------------------------------------------------- /src/api/util/RDP.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Ramer-Douglas-Peucker algorithm roughly ported from the pseudo-code provided 3 | by http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm 4 | """ 5 | 6 | from math import sqrt 7 | 8 | def distance(a, b): 9 | return sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) 10 | 11 | def point_line_distance(point, start, end): 12 | if (start == end): 13 | return distance(point, start) 14 | else: 15 | n = abs( 16 | (end[0] - start[0]) * (start[1] - point[1]) - (start[0] - point[0]) * (end[1] - start[1]) 17 | ) 18 | d = sqrt( 19 | (end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2 20 | ) 21 | return n / d 22 | 23 | def rdp(points, epsilon): 24 | """ 25 | Reduces a series of points to a simplified version that loses detail, but 26 | maintains the general shape of the series. 27 | """ 28 | dmax = 0.0 29 | index = 0 30 | for i in range(1, len(points) - 1): 31 | d = point_line_distance(points[i], points[0], points[-1]) 32 | if d > dmax: 33 | index = i 34 | dmax = d 35 | if dmax >= epsilon: 36 | results = rdp(points[:index+1], epsilon)[:-1] + rdp(points[index:], epsilon) 37 | else: 38 | results = [points[0], points[-1]] 39 | return results -------------------------------------------------------------------------------- /src/api/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/api/util/__init__.py -------------------------------------------------------------------------------- /src/api/util/settings.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import os 3 | import json 4 | 5 | 6 | def get_settings(): 7 | """Parses the settings from redis-live.conf. 8 | """ 9 | # TODO: Consider YAML. Human writable, machine readable. 10 | with open(os.getenv("REDISLIVE_CONFIG", "redis-live.conf")) as config: 11 | return json.load(config) 12 | 13 | 14 | def get_redis_servers(): 15 | config = get_settings() 16 | return config["RedisServers"] 17 | 18 | 19 | def get_redis_stats_server(): 20 | config = get_settings() 21 | return config["RedisStatsServer"] 22 | 23 | 24 | def get_data_store_type(): 25 | config = get_settings() 26 | return config["DataStoreType"] 27 | 28 | 29 | def get_sqlite_stats_store(): 30 | config = get_settings() 31 | return config["SqliteStatsStore"] 32 | -------------------------------------------------------------------------------- /src/api/util/timeutils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | # Original fix for Py2.6: https://github.com/mozilla/mozdownload/issues/73 3 | def total_seconds(td): 4 | # Keep backward compatibility with Python 2.6 which doesn't have 5 | # this method 6 | if hasattr(td, 'total_seconds'): 7 | return td.total_seconds() 8 | else: 9 | return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 10 | 11 | def convert_to_epoch(timestamp): 12 | if (type(timestamp) is datetime.date): 13 | timestamp = datetime.datetime.fromordinal(timestamp.toordinal()) 14 | timestamp = timestamp.replace(tzinfo=None) 15 | diff = (timestamp - datetime.datetime(1970, 1, 1)) 16 | seconds = int(total_seconds(diff)) 17 | return seconds 18 | -------------------------------------------------------------------------------- /src/dataprovider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/dataprovider/__init__.py -------------------------------------------------------------------------------- /src/dataprovider/dataprovider.py: -------------------------------------------------------------------------------- 1 | from api.util import settings 2 | import sqliteprovider 3 | import redisprovider 4 | 5 | 6 | # TODO: Confirm there's not some implementation detail I've missed, then 7 | # ditch the classes here. 8 | class RedisLiveDataProvider(object): 9 | 10 | @staticmethod 11 | def get_provider(): 12 | """Returns a data provider based on the settings file. 13 | 14 | Valid providers are currently Redis and SQLite. 15 | """ 16 | data_store_type = settings.get_data_store_type() 17 | 18 | # FIXME: Should use a global variable for "redis" here. 19 | if data_store_type == "redis": 20 | return redisprovider.RedisStatsProvider() 21 | else: 22 | return sqliteprovider.RedisStatsProvider() 23 | -------------------------------------------------------------------------------- /src/dataprovider/sqliteprovider.py: -------------------------------------------------------------------------------- 1 | from api.util import settings 2 | import contextlib 3 | import sqlite3 4 | import json 5 | 6 | class RedisStatsProvider(object): 7 | """A Sqlite based persistance to store and fetch stats 8 | """ 9 | 10 | def __init__(self): 11 | stats = settings.get_sqlite_stats_store() 12 | self.location = stats.get('path', 'db/redislive.sqlite') 13 | self.conn = sqlite3.connect(self.location) 14 | self.retries = 10 15 | 16 | def save_memory_info(self, server, timestamp, used, peak): 17 | """Saves used and peak memory stats, 18 | 19 | Args: 20 | server (str): The server ID 21 | timestamp (datetime): The time of the info. 22 | used (int): Used memory value. 23 | peak (int): Peak memory value. 24 | """ 25 | query = "INSERT INTO memory VALUES (?, ?, ?, ?);" 26 | values = (timestamp.strftime('%Y-%m-%d %H:%M:%S'), used, peak, server) 27 | self._retry_query(query, values) 28 | 29 | def save_info_command(self, server, timestamp, info): 30 | """Save Redis info command dump 31 | 32 | Args: 33 | server (str): id of server 34 | timestamp (datetime): Timestamp. 35 | info (dict): The result of a Redis INFO command. 36 | """ 37 | query = "INSERT INTO info VALUES (?, ?, ?);" 38 | values = (timestamp.strftime('%Y-%m-%d %H:%M:%S'), json.dumps(info), 39 | server) 40 | self._retry_query(query, values) 41 | 42 | def save_monitor_command(self, server, timestamp, command, keyname, 43 | argument): 44 | """save information about every command 45 | 46 | Args: 47 | server (str): Server ID 48 | timestamp (datetime): Timestamp. 49 | command (str): The Redis command used. 50 | keyname (str): The key the command acted on. 51 | argument (str): The args sent to the command. 52 | """ 53 | # FIXME: why clear the argument here? 54 | argument = "" 55 | 56 | query = "INSERT INTO monitor " 57 | query += "(datetime, command, keyname, arguments, server) " 58 | query += "VALUES " 59 | query += "(?, ?, ?, ?, ?);" 60 | 61 | values = (timestamp.strftime('%Y-%m-%d %H:%M:%S'), command, keyname, 62 | argument, server) 63 | 64 | self._retry_query(query, values) 65 | 66 | def get_info(self, server): 67 | """Get info about the server 68 | 69 | Args: 70 | server (str): The server ID 71 | """ 72 | with contextlib.closing(self.conn.cursor()) as c: 73 | query = "SELECT info FROM info WHERE server=?" 74 | query += "ORDER BY datetime DESC LIMIT 1;" 75 | for r in c.execute(query, (server,)): 76 | return(json.loads(r[0])) 77 | 78 | def get_memory_info(self, server, from_date, to_date): 79 | """Get stats for Memory Consumption between a range of dates 80 | 81 | Args: 82 | server (str): The server ID 83 | from_date (datetime): Get memory info from this date onwards. 84 | to_date (datetime): Get memory info up to this date. 85 | """ 86 | time_fmt = '%Y-%m-%d %H:%M:%S' 87 | query = """SELECT strftime('%Y-%m-%d %H:%M:%S', datetime), max, current 88 | FROM memory 89 | WHERE datetime >= ? 90 | AND datetime <= ? 91 | AND server = ?;""" 92 | 93 | values = (from_date.strftime(time_fmt), to_date.strftime(time_fmt), 94 | server) 95 | 96 | with contextlib.closing(self.conn.cursor()) as c: 97 | return [[r[0], r[1], r[2]] for r in c.execute(query, values)] 98 | 99 | def get_command_stats(self, server, from_date, to_date, group_by): 100 | """Get total commands processed in the given time period 101 | 102 | Args: 103 | server (str): The server ID 104 | from_date (datetime): Get data from this date. 105 | to_date (datetime): Get data to this date. 106 | group_by (str): How to group the stats. 107 | """ 108 | time_fmt = '%Y-%m-%d %H:%M:%S' 109 | 110 | sql = """SELECT COUNT(*) AS total, strftime('%s', datetime) 111 | FROM monitor 112 | WHERE datetime >= ? 113 | AND datetime <= ? 114 | AND server = ? 115 | GROUP BY strftime('%s', datetime) 116 | ORDER BY datetime DESC;""" 117 | 118 | values = (from_date.strftime(time_fmt), to_date.strftime(time_fmt), 119 | server) 120 | 121 | if group_by == "day": 122 | query_time_fmt = '%Y-%m-%d' 123 | elif group_by == "hour": 124 | query_time_fmt = '%Y-%m-%d %H' 125 | elif group_by=="minute": 126 | query_time_fmt = '%Y-%m-%d %H:%M' 127 | else: 128 | query_time_fmt = '%Y-%m-%d %H:%M:%S' 129 | 130 | query = sql % (query_time_fmt, query_time_fmt) 131 | 132 | with contextlib.closing(self.conn.cursor()) as c: 133 | mem_data = [[r[0], r[1]] for r in c.execute(query, values)] 134 | return reversed(mem_data) 135 | 136 | def get_top_commands_stats(self, server, from_date, to_date): 137 | """Get top commands processed in the given time period 138 | 139 | Args: 140 | server (str): Server ID 141 | from_date (datetime): Get stats from this date. 142 | to_date (datetime): Get stats to this date. 143 | """ 144 | time_fmt = '%Y-%m-%d %H:%M:%S' 145 | query = """SELECT command, COUNT(*) AS total 146 | FROM monitor 147 | WHERE datetime >= ? 148 | AND datetime <= ? 149 | AND server = ? 150 | GROUP BY command 151 | ORDER BY total;""" 152 | values = (from_date.strftime(time_fmt), to_date.strftime(time_fmt), 153 | server) 154 | 155 | with contextlib.closing(self.conn.cursor()) as c: 156 | return [[r[0], r[1]] for r in c.execute(query, values)] 157 | 158 | def get_top_keys_stats(self, server, from_date, to_date): 159 | """Gets top comm processed 160 | 161 | Args: 162 | server (str): Server ID 163 | from_date (datetime): Get stats from this date. 164 | to_date (datetime): Get stats to this date. 165 | """ 166 | time_fmt = '%Y-%m-%d %H:%M:%S' 167 | query = """SELECT keyname, COUNT(*) AS total 168 | FROM monitor 169 | WHERE datetime >= ? 170 | AND datetime <= ? 171 | AND server = ? 172 | GROUP BY keyname ORDER BY total DESC 173 | LIMIT 10;""" 174 | values = (from_date.strftime(time_fmt), to_date.strftime(time_fmt), 175 | server) 176 | 177 | with contextlib.closing(self.conn.cursor()) as c: 178 | return [[r[0], r[1]] for r in c.execute(query, values)] 179 | 180 | def _retry_query(self, query, values=None): 181 | """Run a SQLite query until it sticks or until we reach the max number 182 | of retries. Single-threaded writes :( 183 | 184 | Args: 185 | query (str): The query to execute. 186 | 187 | Kwargs: 188 | values (tuple|dict): Used when the query is parameterized. 189 | """ 190 | with contextlib.closing(self.conn.cursor()) as cursor: 191 | completed = False 192 | counter = 0 193 | while counter < self.retries and not completed: 194 | counter += 1 195 | try: 196 | cursor.execute(query, values) 197 | self.conn.commit() 198 | completed = True 199 | except Exception: 200 | # FIXME: Catch specific exceptions here otherwise it's likely to 201 | # mask bugs/issues later. 202 | pass 203 | -------------------------------------------------------------------------------- /src/db/redislive.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/db/redislive.sqlite -------------------------------------------------------------------------------- /src/db/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE info(datetime text,info text, server text); 2 | 3 | CREATE TABLE keys(datetime text, expire number, persist number, server text); 4 | 5 | CREATE TABLE memory(datetime text,current real,max real, server text); 6 | 7 | CREATE TABLE "monitor" ("datetime" datetime,"command" text,"arguments" text,"server" text, keyname text); 8 | 9 | CREATE INDEX "monitor_dateTime_Index" ON "monitor" ("datetime" DESC); 10 | 11 | CREATE INDEX serverIndex on monitor(server); -------------------------------------------------------------------------------- /src/redis-live.conf.example: -------------------------------------------------------------------------------- 1 | { 2 | "RedisServers": 3 | [ 4 | { 5 | "server": "154.17.59.99", 6 | "port" : 6379 7 | }, 8 | 9 | { 10 | "server": "localhost", 11 | "port" : 6380, 12 | "password" : "some-password" 13 | } 14 | ], 15 | 16 | "DataStoreType" : "redis", 17 | 18 | "RedisStatsServer": 19 | { 20 | "server" : "ec2-184-72-166-144.compute-1.amazonaws.com", 21 | "port" : 6385 22 | }, 23 | 24 | "SqliteStatsStore" : 25 | { 26 | "path": "to your sql lite file" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/redis-live.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import tornado.ioloop 4 | import tornado.options 5 | from tornado.options import define, options 6 | import tornado.web 7 | 8 | from api.controller.BaseStaticFileHandler import BaseStaticFileHandler 9 | 10 | from api.controller.ServerListController import ServerListController 11 | from api.controller.InfoController import InfoController 12 | from api.controller.MemoryController import MemoryController 13 | from api.controller.CommandsController import CommandsController 14 | from api.controller.TopCommandsController import TopCommandsController 15 | from api.controller.TopKeysController import TopKeysController 16 | 17 | if __name__ == "__main__": 18 | define("port", default=8888, help="run on the given port", type=int) 19 | define("debug", default=0, help="debug mode", type=int) 20 | tornado.options.parse_command_line() 21 | 22 | # Bootup 23 | handlers = [ 24 | (r"/api/servers", ServerListController), 25 | (r"/api/info", InfoController), 26 | (r"/api/memory", MemoryController), 27 | (r"/api/commands", CommandsController), 28 | (r"/api/topcommands", TopCommandsController), 29 | (r"/api/topkeys", TopKeysController), 30 | (r"/(.*)", BaseStaticFileHandler, {"path": "www", "default_filename": "index.html"}) 31 | ] 32 | 33 | server_settings = {'debug': options.debug} 34 | application = tornado.web.Application(handlers, **server_settings) 35 | application.listen(options.port) 36 | tornado.ioloop.IOLoop.instance().start() 37 | -------------------------------------------------------------------------------- /src/util/redis-fill-data.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import redis 4 | from time import strftime 5 | import time 6 | import random 7 | 8 | def monitor(): 9 | redisHost = "127.0.0.1" 10 | redisPort = 6379 11 | redisClient = redis.StrictRedis(host=redisHost, port=redisPort, db=0) 12 | 13 | while True: 14 | 15 | x = random.randint(1, 100) 16 | y = random.randint(1, 20) 17 | 18 | if y==1: 19 | for z in xrange(1,x): 20 | redisClient.set("Key:" + `x`, x) 21 | elif y==2: 22 | for z in xrange(1,x): 23 | redisClient.get("Key:" + `x`) 24 | elif y==4: 25 | for z in xrange(1,x): 26 | redisClient.hset("HashKey:" + `x`, x, x) 27 | elif y==5: 28 | for z in xrange(1,(x/2)+2): 29 | redisClient.setex("Key:" + `x`, 1000, x) 30 | elif y==6: 31 | for z in xrange(1,x): 32 | redisClient.hexists("HashKey:" + `x`, y) 33 | elif y==7: 34 | for z in xrange(1,x): 35 | redisClient.setbit("BitSet:" + `x`, 1, 1) 36 | elif y==8: 37 | for z in xrange(1,x): 38 | redisClient.getbit("BitSet:" + `x`, 1) 39 | elif y==9: 40 | for z in xrange(1,x): 41 | redisClient.expire("Key:"+ `x`, 2000) 42 | elif y==11: 43 | redisClient.flushall() 44 | 45 | 46 | 47 | if __name__ == '__main__': 48 | monitor() -------------------------------------------------------------------------------- /src/util/redis-fill-data2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import redis 4 | from time import strftime 5 | import time 6 | import random 7 | 8 | def monitor(): 9 | redisHost = "127.0.0.1" 10 | redisPort = 6381 11 | redisClient = redis.StrictRedis(host=redisHost, port=redisPort, db=0) 12 | 13 | while True: 14 | x=1 15 | redisClient.set("Key:" + `x`, x) 16 | redisClient.set("KeyYU:" + `x`, x) 17 | redisClient.set("Key:" + `x`, x) 18 | redisClient.set("KeyYU:" + `x`, x) 19 | 20 | if __name__ == '__main__': 21 | monitor() -------------------------------------------------------------------------------- /src/www/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/www/images/logo.png -------------------------------------------------------------------------------- /src/www/js/app.js: -------------------------------------------------------------------------------- 1 | /* Main App 2 | * ====================== */ 3 | 4 | var App = { 5 | 6 | init: function() { 7 | 8 | this.RegisterPartials() 9 | this.RegisterHelpers() 10 | 11 | var ServerDropDown = new ServerList({ 12 | el : $("#server-list") 13 | , model : new ServerListModel() 14 | }) 15 | 16 | var infoWidget = new InfoWidget({ 17 | el : $("#info-widget-placeholder") 18 | , model : new InfoWidgetModel() 19 | }) 20 | 21 | var memoryWidget = new MemoryWidget({ 22 | el : $("#memory-widget-placeholder") 23 | , model : new MemoryWidgetModel() 24 | }) 25 | 26 | var commandsWidget = new CommandsWidget({ 27 | el : $("#commands-widget-placeholder") 28 | , model : new CommandsWidgetModel() 29 | }) 30 | 31 | var topCommandsWidget = new TopCommandsWidget({ 32 | el : $("#top-commands-widget-placeholder") 33 | , model : new TopCommandsWidgetModel() 34 | }) 35 | 36 | var topKeysWidget = new TopKeysWidget({ 37 | el : $("#top-keys-widget-placeholder") 38 | , model : new TopKeysWidgetModel() 39 | }) 40 | } 41 | 42 | , RegisterPartials : function(){ 43 | 44 | Handlebars.registerPartial("date-dropdown", $("#date-dropdown-template").html()); 45 | 46 | } 47 | 48 | , RegisterHelpers : function(){ 49 | 50 | Handlebars.registerHelper('hash', function ( context, options ) { 51 | 52 | var ret = "" 53 | , counter = 0 54 | 55 | $.each(context, function ( key, value ) { 56 | 57 | if (typeof value != "object") { 58 | obj = { "key" : key, "value" : value , "index" : counter++ } 59 | ret = ret + options.fn(obj) 60 | } 61 | 62 | }) 63 | 64 | return ret 65 | }) 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/www/js/google/jsapi.css: -------------------------------------------------------------------------------- 1 | 2 | .google-visualization-toolbar{font-size:100%}.google-visualization-toolbar .google-visualization-toolbar-export-igoogle,.google-visualization-toolbar .google-visualization-toolbar-export-data,.google-visualization-toolbar .google-visualization-toolbar-html-code{margin-right:.1em}.google-visualization-toolbar-html-code-explanation{font-weight:bold}.google-visualization-toolbar-ok-button{padding:2px}.google-visualization-toolbar-triangle{position:absolute;right:0;top:0}.google-visualization-toolbar-caption-table{width:100%;padding:0;margin:0;border:0;border-collapse:collapse}.google-visualization-toolbar-small-dialog{width:500px}.google-visualization-toolbar-big-dialog{width:800px}.google-visualization-toolbar-small-dialog,.google-visualization-toolbar-big-dialog{position:absolute;background-color:#c1d9ff;border:1px solid #3a5774;padding:8px}.google-visualization-toolbar-small-dialog-bg,.google-visualization-toolbar-big-dialog-bg{background-color:#ddd;position:absolute;top:0;left:0}.google-visualization-toolbar-small-dialog-title,.google-visualization-toolbar-big-dialog-title{background-color:#e0edfe;color:#000;cursor:pointer;padding:8px;position:relative;font-size:12pt;font-weight:bold;vertical-align:middle}.google-visualization-toolbar-small-dialog-content,.google-visualization-toolbar-big-dialog-content{background-color:#fff;padding:4px;font-weight:normal;overflow:auto}.google-visualization-toolbar-small-dialog-title-close,.google-visualization-toolbar-big-dialog-title-close{background:transparent url(close_box.gif) no-repeat scroll center;height:15px;position:absolute;right:10px;top:8px;width:15px}.google-visualization-toolbar-small-dialog-content iframe,.google-visualization-toolbar-big-dialog-content iframe{width:500px;height:700px;border:1px solid black}.charts-inline-block{position:relative;display:-moz-inline-box;display:inline-block}* html .charts-inline-block,*:first-child+html .charts-inline-block{display:inline}.charts-menu{background:#fff;border-color:#ccc #666 #666 #ccc;border-style:solid;border-width:1px;cursor:default;font:normal 13px Arial,sans-serif;margin:0;outline:none;padding:4px 0;position:absolute;z-index:20000}.charts-menu-button{background:#ddd url(//ssl.gstatic.com/editor/button-bg.png) repeat-x top left;border:0;color:#000;cursor:pointer;list-style:none;margin:2px;outline:none;padding:0;text-decoration:none;vertical-align:middle}.charts-menu-button-outer-box,.charts-menu-button-inner-box{border-style:solid;border-color:#aaa;vertical-align:top}.charts-menu-button-outer-box{margin:0;border-width:1px 0;padding:0}.charts-menu-button-inner-box{margin:0 -1px;border-width:0 1px;padding:3px 4px}* html .charts-menu-button-inner-box{left:-1px}* html .charts-menu-button-rtl .charts-menu-button-outer-box{left:-1px;right:auto}* html .charts-menu-button-rtl .charts-menu-button-inner-box{right:auto}*:first-child+html .charts-menu-button-inner-box{left:-1px}*:first-child+html .charts-menu-button-rtl .charts-menu-button-inner-box{left:1px;right:auto}::root .charts-menu-button{line-height:0}::root .charts-menu-button-outer-box{line-height:0}::root .charts-menu-button-inner-box{line-height:0}::root .charts-menu-button-caption{line-height:normal}::root .charts-menu-button-dropdown{line-height:normal}.charts-menu-button-disabled{background-image:none!important;opacity:.3;-moz-opacity:.3;filter:alpha(opacity=30)}.charts-menu-button-disabled .charts-menu-button-outer-box,.charts-menu-button-disabled .charts-menu-button-inner-box,.charts-menu-button-disabled .charts-menu-button-caption,.charts-menu-button-disabled .charts-menu-button-dropdown{color:#333!important;border-color:#999!important}* html .charts-menu-button-disabled,*:first-child+html .charts-menu-button-disabled{margin:2px 1px!important;padding:0 1px!important}.charts-menu-button-hover .charts-menu-button-outer-box,.charts-menu-button-hover .charts-menu-button-inner-box{border-color:#9cf #69e #69e #7af!important}.charts-menu-button-active,.charts-menu-button-open{background-color:#bbb;background-position:bottom left}.charts-menu-button-focused .charts-menu-button-outer-box,.charts-menu-button-focused .charts-menu-button-inner-box{border-color:orange}.charts-menu-button-caption{padding:0 4px 0 0;vertical-align:top}.charts-menu-button-dropdown{height:15px;width:7px;background:url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -388px 0;vertical-align:top}.charts-menu-button-collapse-right,.charts-menu-button-collapse-right .charts-menu-button-outer-box,.charts-menu-button-collapse-right .charts-menu-button-inner-box{margin-right:0}.charts-menu-button-collapse-left,.charts-menu-button-collapse-left .charts-menu-button-outer-box{margin-left:0}.charts-menu-button-collapse-left .charts-menu-button-inner-box{margin-left:0;border-left:1px solid #fff}.charts-menu-button-collapse-left.charts-menu-button-checked .charts-menu-button-inner-box{border-left:1px solid #ddd}.charts-menuitem{color:#000;font:normal 13px Arial,sans-serif;list-style:none;margin:0;padding:4px 7em 4px 28px;white-space:nowrap}.charts-menuitem.charts-menuitem-rtl{padding-left:7em;padding-right:28px}.charts-menu-nocheckbox .charts-menuitem,.charts-menu-noicon .charts-menuitem{padding-left:12px}.charts-menu-noaccel .charts-menuitem{padding-right:20px}.charts-menuitem-content{color:#000;font:normal 13px Arial,sans-serif}.charts-menuitem-disabled .charts-menuitem-accel,.charts-menuitem-disabled .charts-menuitem-content{color:#ccc!important}.charts-menuitem-disabled .charts-menuitem-icon{opacity:.3;-moz-opacity:.3;filter:alpha(opacity=30)}.charts-menuitem-highlight,.charts-menuitem-hover{background-color:#d6e9f8;border-color:#d6e9f8;border-style:dotted;border-width:1px 0;padding-bottom:3px;padding-top:3px}.charts-menuitem-checkbox,.charts-menuitem-icon{background-repeat:no-repeat;height:16px;left:6px;position:absolute;right:auto;vertical-align:middle;width:16px}.charts-menuitem-rtl .charts-menuitem-checkbox,.charts-menuitem-rtl .charts-menuitem-icon{left:auto;right:6px}.charts-option-selected .charts-menuitem-checkbox,.charts-option-selected .charts-menuitem-icon{background:url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0}.charts-menuitem-accel{color:#999;direction:ltr;left:auto;padding:0 6px;position:absolute;right:0;text-align:right}.charts-menuitem-rtl .charts-menuitem-accel{left:0;right:auto;text-align:left}.charts-menuitem-mnemonic-hint{text-decoration:underline}.charts-menuitem-mnemonic-separator{color:#999;font-size:12px;padding-left:4px} 3 | 4 | -------------------------------------------------------------------------------- /src/www/js/google/visualization.js: -------------------------------------------------------------------------------- 1 | if (window['google'] != undefined && window['google']['loader'] != undefined) { 2 | if (!window['google']['visualization']) { 3 | window['google']['visualization'] = {}; 4 | google.visualization.Version = '1.0'; 5 | google.visualization.JSHash = 'abbdd6ab2d343a51d49841bf93d022fb'; 6 | google.visualization.LoadArgs = 'file\75visualization\46v\0751\46packages\75corechart'; 7 | } 8 | // google.loader.writeLoadTag("css", google.loader.ServiceBase + "/api/visualization/1.0/abbdd6ab2d343a51d49841bf93d022fb/ui+en.css", false); 9 | // google.loader.writeLoadTag("script", google.loader.ServiceBase + "/api/visualization/1.0/abbdd6ab2d343a51d49841bf93d022fb/format+en,default+en,ui+en,corechart+en.I.js", false); 10 | } 11 | -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/www/js/libs/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkrode/RedisLive/46e1c755880358a3ddcd8cdc352c1beeeece789f/src/www/js/libs/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/README.md: -------------------------------------------------------------------------------- 1 | ## 2.0 BOOTSTRAP JS PHILOSOPHY 2 | These are the high-level design rules which guide the development of Bootstrap's plugin apis. 3 | 4 | --- 5 | 6 | ### DATA-ATTRIBUTE API 7 | 8 | We believe you should be able to use all plugins provided by Bootstrap purely through the markup API without writing a single line of javascript. 9 | 10 | We acknowledge that this isn't always the most performant and sometimes it may be desirable to turn this functionality off altogether. Therefore, as of 2.0 we provide the ability to disable the data attribute API by unbinding all events on the body namespaced with `'data-api'`. This looks like this: 11 | 12 | $('body').off('.data-api') 13 | 14 | To target a specific plugin, just include the plugins name as a namespace along with the data-api namespace like this: 15 | 16 | $('body').off('.alert.data-api') 17 | 18 | --- 19 | 20 | ### PROGRAMATIC API 21 | 22 | We also believe you should be able to use all plugins provided by Bootstrap purely through the JS API. 23 | 24 | All public APIs should be single, chainable methods, and return the collection acted upon. 25 | 26 | $(".btn.danger").button("toggle").addClass("fat") 27 | 28 | All methods should accept an optional options object, a string which targets a particular method, or null which initiates the default behavior: 29 | 30 | $("#myModal").modal() // initialized with defaults 31 | $("#myModal").modal({ keyboard: false }) // initialized with no keyboard 32 | $("#myModal").modal('show') // initializes and invokes show immediately afterqwe2 33 | 34 | --- 35 | 36 | ### OPTIONS 37 | 38 | Options should be sparse and add universal value. We should pick the right defaults. 39 | 40 | All plugins should have a default object which can be modified to affect all instances' default options. The defaults object should be available via `$.fn.plugin.defaults`. 41 | 42 | $.fn.modal.defaults = { … } 43 | 44 | An options definition should take the following form: 45 | 46 | *noun*: *adjective* - describes or modifies a quality of an instance 47 | 48 | examples: 49 | 50 | backdrop: true 51 | keyboard: false 52 | placement: 'top' 53 | 54 | --- 55 | 56 | ### EVENTS 57 | 58 | All events should have an infinitive and past participle form. The infinitive is fired just before an action takes place, the past participle on completion of the action. 59 | 60 | show | shown 61 | hide | hidden 62 | 63 | --- 64 | 65 | ### CONSTRUCTORS 66 | 67 | Each plugin should expose its raw constructor on a `Constructor` property -- accessed in the following way: 68 | 69 | 70 | $.fn.popover.Constructor 71 | 72 | --- 73 | 74 | ### DATA ACCESSOR 75 | 76 | Each plugin stores a copy of the invoked class on an object. This class instance can be accessed directly through jQuery's data API like this: 77 | 78 | $('[rel=popover]').data('popover') instanceof $.fn.popover.Constructor 79 | 80 | --- 81 | 82 | ### DATA ATTRIBUTES 83 | 84 | Data attributes should take the following form: 85 | 86 | - data-{{verb}}={{plugin}} - defines main interaction 87 | - data-target || href^=# - defined on "control" element (if element controls an element other than self) 88 | - data-{{noun}} - defines class instance options 89 | 90 | examples: 91 | 92 | // control other targets 93 | data-toggle="modal" data-target="#foo" 94 | data-toggle="collapse" data-target="#foo" data-parent="#bar" 95 | 96 | // defined on element they control 97 | data-spy="scroll" 98 | 99 | data-dismiss="modal" 100 | data-dismiss="alert" 101 | 102 | data-toggle="dropdown" 103 | 104 | data-toggle="button" 105 | data-toggle="buttons-checkbox" 106 | data-toggle="buttons-radio" -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alert.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* ALERT CLASS DEFINITION 26 | * ====================== */ 27 | 28 | var dismiss = '[data-dismiss="alert"]' 29 | , Alert = function ( el ) { 30 | $(el).on('click', dismiss, this.close) 31 | } 32 | 33 | Alert.prototype = { 34 | 35 | constructor: Alert 36 | 37 | , close: function ( e ) { 38 | var $this = $(this) 39 | , selector = $this.attr('data-target') 40 | , $parent 41 | 42 | if (!selector) { 43 | selector = $this.attr('href') 44 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 45 | } 46 | 47 | $parent = $(selector) 48 | $parent.trigger('close') 49 | 50 | e && e.preventDefault() 51 | 52 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 53 | 54 | $parent 55 | .trigger('close') 56 | .removeClass('in') 57 | 58 | function removeElement() { 59 | $parent 60 | .trigger('closed') 61 | .remove() 62 | } 63 | 64 | $.support.transition && $parent.hasClass('fade') ? 65 | $parent.on($.support.transition.end, removeElement) : 66 | removeElement() 67 | } 68 | 69 | } 70 | 71 | 72 | /* ALERT PLUGIN DEFINITION 73 | * ======================= */ 74 | 75 | $.fn.alert = function ( option ) { 76 | return this.each(function () { 77 | var $this = $(this) 78 | , data = $this.data('alert') 79 | if (!data) $this.data('alert', (data = new Alert(this))) 80 | if (typeof option == 'string') data[option].call($this) 81 | }) 82 | } 83 | 84 | $.fn.alert.Constructor = Alert 85 | 86 | 87 | /* ALERT DATA-API 88 | * ============== */ 89 | 90 | $(function () { 91 | $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) 92 | }) 93 | 94 | }( window.jQuery ); -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-button.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-button.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#buttons 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | !function( $ ){ 21 | 22 | "use strict" 23 | 24 | /* BUTTON PUBLIC CLASS DEFINITION 25 | * ============================== */ 26 | 27 | var Button = function ( element, options ) { 28 | this.$element = $(element) 29 | this.options = $.extend({}, $.fn.button.defaults, options) 30 | } 31 | 32 | Button.prototype = { 33 | 34 | constructor: Button 35 | 36 | , setState: function ( state ) { 37 | var d = 'disabled' 38 | , $el = this.$element 39 | , data = $el.data() 40 | , val = $el.is('input') ? 'val' : 'html' 41 | 42 | state = state + 'Text' 43 | data.resetText || $el.data('resetText', $el[val]()) 44 | 45 | $el[val](data[state] || this.options[state]) 46 | 47 | // push to event loop to allow forms to submit 48 | setTimeout(function () { 49 | state == 'loadingText' ? 50 | $el.addClass(d).attr(d, d) : 51 | $el.removeClass(d).removeAttr(d) 52 | }, 0) 53 | } 54 | 55 | , toggle: function () { 56 | var $parent = this.$element.parent('[data-toggle="buttons-radio"]') 57 | 58 | $parent && $parent 59 | .find('.active') 60 | .removeClass('active') 61 | 62 | this.$element.toggleClass('active') 63 | } 64 | 65 | } 66 | 67 | 68 | /* BUTTON PLUGIN DEFINITION 69 | * ======================== */ 70 | 71 | $.fn.button = function ( option ) { 72 | return this.each(function () { 73 | var $this = $(this) 74 | , data = $this.data('button') 75 | , options = typeof option == 'object' && option 76 | if (!data) $this.data('button', (data = new Button(this, options))) 77 | if (option == 'toggle') data.toggle() 78 | else if (option) data.setState(option) 79 | }) 80 | } 81 | 82 | $.fn.button.defaults = { 83 | loadingText: 'loading...' 84 | } 85 | 86 | $.fn.button.Constructor = Button 87 | 88 | 89 | /* BUTTON DATA-API 90 | * =============== */ 91 | 92 | $(function () { 93 | $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { 94 | var $btn = $(e.target) 95 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 96 | $btn.button('toggle') 97 | }) 98 | }) 99 | 100 | }( window.jQuery ); -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-carousel.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-carousel.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#carousel 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* CAROUSEL CLASS DEFINITION 26 | * ========================= */ 27 | 28 | var Carousel = function (element, options) { 29 | this.$element = $(element) 30 | this.options = $.extend({}, $.fn.carousel.defaults, options) 31 | this.options.slide && this.slide(this.options.slide) 32 | this.options.pause == 'hover' && this.$element 33 | .on('mouseenter', $.proxy(this.pause, this)) 34 | .on('mouseleave', $.proxy(this.cycle, this)) 35 | } 36 | 37 | Carousel.prototype = { 38 | 39 | cycle: function () { 40 | this.interval = setInterval($.proxy(this.next, this), this.options.interval) 41 | return this 42 | } 43 | 44 | , to: function (pos) { 45 | var $active = this.$element.find('.active') 46 | , children = $active.parent().children() 47 | , activePos = children.index($active) 48 | , that = this 49 | 50 | if (pos > (children.length - 1) || pos < 0) return 51 | 52 | if (this.sliding) { 53 | return this.$element.one('slid', function () { 54 | that.to(pos) 55 | }) 56 | } 57 | 58 | if (activePos == pos) { 59 | return this.pause().cycle() 60 | } 61 | 62 | return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) 63 | } 64 | 65 | , pause: function () { 66 | clearInterval(this.interval) 67 | this.interval = null 68 | return this 69 | } 70 | 71 | , next: function () { 72 | if (this.sliding) return 73 | return this.slide('next') 74 | } 75 | 76 | , prev: function () { 77 | if (this.sliding) return 78 | return this.slide('prev') 79 | } 80 | 81 | , slide: function (type, next) { 82 | var $active = this.$element.find('.active') 83 | , $next = next || $active[type]() 84 | , isCycling = this.interval 85 | , direction = type == 'next' ? 'left' : 'right' 86 | , fallback = type == 'next' ? 'first' : 'last' 87 | , that = this 88 | 89 | this.sliding = true 90 | 91 | isCycling && this.pause() 92 | 93 | $next = $next.length ? $next : this.$element.find('.item')[fallback]() 94 | 95 | if ($next.hasClass('active')) return 96 | 97 | if (!$.support.transition && this.$element.hasClass('slide')) { 98 | this.$element.trigger('slide') 99 | $active.removeClass('active') 100 | $next.addClass('active') 101 | this.sliding = false 102 | this.$element.trigger('slid') 103 | } else { 104 | $next.addClass(type) 105 | $next[0].offsetWidth // force reflow 106 | $active.addClass(direction) 107 | $next.addClass(direction) 108 | this.$element.trigger('slide') 109 | this.$element.one($.support.transition.end, function () { 110 | $next.removeClass([type, direction].join(' ')).addClass('active') 111 | $active.removeClass(['active', direction].join(' ')) 112 | that.sliding = false 113 | setTimeout(function () { that.$element.trigger('slid') }, 0) 114 | }) 115 | } 116 | 117 | isCycling && this.cycle() 118 | 119 | return this 120 | } 121 | 122 | } 123 | 124 | 125 | /* CAROUSEL PLUGIN DEFINITION 126 | * ========================== */ 127 | 128 | $.fn.carousel = function ( option ) { 129 | return this.each(function () { 130 | var $this = $(this) 131 | , data = $this.data('carousel') 132 | , options = typeof option == 'object' && option 133 | if (!data) $this.data('carousel', (data = new Carousel(this, options))) 134 | if (typeof option == 'number') data.to(option) 135 | else if (typeof option == 'string' || (option = options.slide)) data[option]() 136 | else data.cycle() 137 | }) 138 | } 139 | 140 | $.fn.carousel.defaults = { 141 | interval: 5000 142 | , pause: 'hover' 143 | } 144 | 145 | $.fn.carousel.Constructor = Carousel 146 | 147 | 148 | /* CAROUSEL DATA-API 149 | * ================= */ 150 | 151 | $(function () { 152 | $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { 153 | var $this = $(this), href 154 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 155 | , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) 156 | $target.carousel(options) 157 | e.preventDefault() 158 | }) 159 | }) 160 | 161 | }( window.jQuery ); -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-collapse.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-collapse.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#collapse 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | !function( $ ){ 21 | 22 | "use strict" 23 | 24 | var Collapse = function ( element, options ) { 25 | this.$element = $(element) 26 | this.options = $.extend({}, $.fn.collapse.defaults, options) 27 | 28 | if (this.options["parent"]) { 29 | this.$parent = $(this.options["parent"]) 30 | } 31 | 32 | this.options.toggle && this.toggle() 33 | } 34 | 35 | Collapse.prototype = { 36 | 37 | constructor: Collapse 38 | 39 | , dimension: function () { 40 | var hasWidth = this.$element.hasClass('width') 41 | return hasWidth ? 'width' : 'height' 42 | } 43 | 44 | , show: function () { 45 | var dimension = this.dimension() 46 | , scroll = $.camelCase(['scroll', dimension].join('-')) 47 | , actives = this.$parent && this.$parent.find('.in') 48 | , hasData 49 | 50 | if (actives && actives.length) { 51 | hasData = actives.data('collapse') 52 | actives.collapse('hide') 53 | hasData || actives.data('collapse', null) 54 | } 55 | 56 | this.$element[dimension](0) 57 | this.transition('addClass', 'show', 'shown') 58 | this.$element[dimension](this.$element[0][scroll]) 59 | 60 | } 61 | 62 | , hide: function () { 63 | var dimension = this.dimension() 64 | this.reset(this.$element[dimension]()) 65 | this.transition('removeClass', 'hide', 'hidden') 66 | this.$element[dimension](0) 67 | } 68 | 69 | , reset: function ( size ) { 70 | var dimension = this.dimension() 71 | 72 | this.$element 73 | .removeClass('collapse') 74 | [dimension](size || 'auto') 75 | [0].offsetWidth 76 | 77 | this.$element[size ? 'addClass' : 'removeClass']('collapse') 78 | 79 | return this 80 | } 81 | 82 | , transition: function ( method, startEvent, completeEvent ) { 83 | var that = this 84 | , complete = function () { 85 | if (startEvent == 'show') that.reset() 86 | that.$element.trigger(completeEvent) 87 | } 88 | 89 | this.$element 90 | .trigger(startEvent) 91 | [method]('in') 92 | 93 | $.support.transition && this.$element.hasClass('collapse') ? 94 | this.$element.one($.support.transition.end, complete) : 95 | complete() 96 | } 97 | 98 | , toggle: function () { 99 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 100 | } 101 | 102 | } 103 | 104 | /* COLLAPSIBLE PLUGIN DEFINITION 105 | * ============================== */ 106 | 107 | $.fn.collapse = function ( option ) { 108 | return this.each(function () { 109 | var $this = $(this) 110 | , data = $this.data('collapse') 111 | , options = typeof option == 'object' && option 112 | if (!data) $this.data('collapse', (data = new Collapse(this, options))) 113 | if (typeof option == 'string') data[option]() 114 | }) 115 | } 116 | 117 | $.fn.collapse.defaults = { 118 | toggle: true 119 | } 120 | 121 | $.fn.collapse.Constructor = Collapse 122 | 123 | 124 | /* COLLAPSIBLE DATA-API 125 | * ==================== */ 126 | 127 | $(function () { 128 | $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { 129 | var $this = $(this), href 130 | , target = $this.attr('data-target') 131 | || e.preventDefault() 132 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 133 | , option = $(target).data('collapse') ? 'toggle' : $this.data() 134 | $(target).collapse(option) 135 | }) 136 | }) 137 | 138 | }( window.jQuery ); -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* DROPDOWN CLASS DEFINITION 26 | * ========================= */ 27 | 28 | var toggle = '[data-toggle="dropdown"]' 29 | , Dropdown = function ( element ) { 30 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 31 | $('html').on('click.dropdown.data-api', function () { 32 | $el.parent().removeClass('open') 33 | }) 34 | } 35 | 36 | Dropdown.prototype = { 37 | 38 | constructor: Dropdown 39 | 40 | , toggle: function ( e ) { 41 | var $this = $(this) 42 | , selector = $this.attr('data-target') 43 | , $parent 44 | , isActive 45 | 46 | if (!selector) { 47 | selector = $this.attr('href') 48 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 49 | } 50 | 51 | $parent = $(selector) 52 | $parent.length || ($parent = $this.parent()) 53 | 54 | isActive = $parent.hasClass('open') 55 | 56 | clearMenus() 57 | !isActive && $parent.toggleClass('open') 58 | 59 | return false 60 | } 61 | 62 | } 63 | 64 | function clearMenus() { 65 | $(toggle).parent().removeClass('open') 66 | } 67 | 68 | 69 | /* DROPDOWN PLUGIN DEFINITION 70 | * ========================== */ 71 | 72 | $.fn.dropdown = function ( option ) { 73 | return this.each(function () { 74 | var $this = $(this) 75 | , data = $this.data('dropdown') 76 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 77 | if (typeof option == 'string') data[option].call($this) 78 | }) 79 | } 80 | 81 | $.fn.dropdown.Constructor = Dropdown 82 | 83 | 84 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 85 | * =================================== */ 86 | 87 | $(function () { 88 | $('html').on('click.dropdown.data-api', clearMenus) 89 | $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 90 | }) 91 | 92 | }( window.jQuery ); -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#modals 4 | * ========================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================= */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* MODAL CLASS DEFINITION 26 | * ====================== */ 27 | 28 | var Modal = function ( content, options ) { 29 | this.options = options 30 | this.$element = $(content) 31 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 32 | } 33 | 34 | Modal.prototype = { 35 | 36 | constructor: Modal 37 | 38 | , toggle: function () { 39 | return this[!this.isShown ? 'show' : 'hide']() 40 | } 41 | 42 | , show: function () { 43 | var that = this 44 | 45 | if (this.isShown) return 46 | 47 | $('body').addClass('modal-open') 48 | 49 | this.isShown = true 50 | this.$element.trigger('show') 51 | 52 | escape.call(this) 53 | backdrop.call(this, function () { 54 | var transition = $.support.transition && that.$element.hasClass('fade') 55 | 56 | !that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position 57 | 58 | that.$element 59 | .show() 60 | 61 | if (transition) { 62 | that.$element[0].offsetWidth // force reflow 63 | } 64 | 65 | that.$element.addClass('in') 66 | 67 | transition ? 68 | that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : 69 | that.$element.trigger('shown') 70 | 71 | }) 72 | } 73 | 74 | , hide: function ( e ) { 75 | e && e.preventDefault() 76 | 77 | if (!this.isShown) return 78 | 79 | var that = this 80 | this.isShown = false 81 | 82 | $('body').removeClass('modal-open') 83 | 84 | escape.call(this) 85 | 86 | this.$element 87 | .trigger('hide') 88 | .removeClass('in') 89 | 90 | $.support.transition && this.$element.hasClass('fade') ? 91 | hideWithTransition.call(this) : 92 | hideModal.call(this) 93 | } 94 | 95 | } 96 | 97 | 98 | /* MODAL PRIVATE METHODS 99 | * ===================== */ 100 | 101 | function hideWithTransition() { 102 | var that = this 103 | , timeout = setTimeout(function () { 104 | that.$element.off($.support.transition.end) 105 | hideModal.call(that) 106 | }, 500) 107 | 108 | this.$element.one($.support.transition.end, function () { 109 | clearTimeout(timeout) 110 | hideModal.call(that) 111 | }) 112 | } 113 | 114 | function hideModal( that ) { 115 | this.$element 116 | .hide() 117 | .trigger('hidden') 118 | 119 | backdrop.call(this) 120 | } 121 | 122 | function backdrop( callback ) { 123 | var that = this 124 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 125 | 126 | if (this.isShown && this.options.backdrop) { 127 | var doAnimate = $.support.transition && animate 128 | 129 | this.$backdrop = $('
') 130 | .appendTo(document.body) 131 | 132 | if (this.options.backdrop != 'static') { 133 | this.$backdrop.click($.proxy(this.hide, this)) 134 | } 135 | 136 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 137 | 138 | this.$backdrop.addClass('in') 139 | 140 | doAnimate ? 141 | this.$backdrop.one($.support.transition.end, callback) : 142 | callback() 143 | 144 | } else if (!this.isShown && this.$backdrop) { 145 | this.$backdrop.removeClass('in') 146 | 147 | $.support.transition && this.$element.hasClass('fade')? 148 | this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) : 149 | removeBackdrop.call(this) 150 | 151 | } else if (callback) { 152 | callback() 153 | } 154 | } 155 | 156 | function removeBackdrop() { 157 | this.$backdrop.remove() 158 | this.$backdrop = null 159 | } 160 | 161 | function escape() { 162 | var that = this 163 | if (this.isShown && this.options.keyboard) { 164 | $(document).on('keyup.dismiss.modal', function ( e ) { 165 | e.which == 27 && that.hide() 166 | }) 167 | } else if (!this.isShown) { 168 | $(document).off('keyup.dismiss.modal') 169 | } 170 | } 171 | 172 | 173 | /* MODAL PLUGIN DEFINITION 174 | * ======================= */ 175 | 176 | $.fn.modal = function ( option ) { 177 | return this.each(function () { 178 | var $this = $(this) 179 | , data = $this.data('modal') 180 | , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) 181 | if (!data) $this.data('modal', (data = new Modal(this, options))) 182 | if (typeof option == 'string') data[option]() 183 | else if (options.show) data.show() 184 | }) 185 | } 186 | 187 | $.fn.modal.defaults = { 188 | backdrop: true 189 | , keyboard: true 190 | , show: true 191 | } 192 | 193 | $.fn.modal.Constructor = Modal 194 | 195 | 196 | /* MODAL DATA-API 197 | * ============== */ 198 | 199 | $(function () { 200 | $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { 201 | var $this = $(this), href 202 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 203 | , option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data()) 204 | 205 | e.preventDefault() 206 | $target.modal(option) 207 | }) 208 | }) 209 | 210 | }( window.jQuery ); -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/js/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#popovers 4 | * =========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * =========================================================== */ 19 | 20 | 21 | !function( $ ) { 22 | 23 | "use strict" 24 | 25 | var Popover = function ( element, options ) { 26 | this.init('popover', element, options) 27 | } 28 | 29 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js 30 | ========================================== */ 31 | 32 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { 33 | 34 | constructor: Popover 35 | 36 | , setContent: function () { 37 | var $tip = this.tip() 38 | , title = this.getTitle() 39 | , content = this.getContent() 40 | 41 | $tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title) 42 | $tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content) 43 | 44 | $tip.removeClass('fade top bottom left right in') 45 | } 46 | 47 | , hasContent: function () { 48 | return this.getTitle() || this.getContent() 49 | } 50 | 51 | , getContent: function () { 52 | var content 53 | , $e = this.$element 54 | , o = this.options 55 | 56 | content = $e.attr('data-content') 57 | || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) 58 | 59 | content = content.toString().replace(/(^\s*|\s*$)/, "") 60 | 61 | return content 62 | } 63 | 64 | , tip: function() { 65 | if (!this.$tip) { 66 | this.$tip = $(this.options.template) 67 | } 68 | return this.$tip 69 | } 70 | 71 | }) 72 | 73 | 74 | /* POPOVER PLUGIN DEFINITION 75 | * ======================= */ 76 | 77 | $.fn.popover = function ( option ) { 78 | return this.each(function () { 79 | var $this = $(this) 80 | , data = $this.data('popover') 81 | , options = typeof option == 'object' && option 82 | if (!data) $this.data('popover', (data = new Popover(this, options))) 83 | if (typeof option == 'string') data[option]() 84 | }) 85 | } 86 | 87 | $.fn.popover.Constructor = Popover 88 | 89 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { 90 | placement: 'right' 91 | , content: '' 92 | , template: ' and elements
3 | // --------------------------------------------------------
4 |
5 | // Inline and block code styles
6 | code,
7 | pre {
8 | padding: 0 3px 2px;
9 | #font > #family > .monospace;
10 | font-size: @baseFontSize - 1;
11 | color: @grayDark;
12 | .border-radius(3px);
13 | }
14 |
15 | // Inline code
16 | code {
17 | padding: 2px 4px;
18 | color: #d14;
19 | background-color: #f7f7f9;
20 | border: 1px solid #e1e1e8;
21 | }
22 |
23 | // Blocks of code
24 | pre {
25 | display: block;
26 | padding: (@baseLineHeight - 1) / 2;
27 | margin: 0 0 @baseLineHeight / 2;
28 | font-size: @baseFontSize * .925; // 13px to 12px
29 | line-height: @baseLineHeight;
30 | background-color: #f5f5f5;
31 | border: 1px solid #ccc; // fallback for IE7-8
32 | border: 1px solid rgba(0,0,0,.15);
33 | .border-radius(4px);
34 | white-space: pre;
35 | white-space: pre-wrap;
36 | word-break: break-all;
37 | word-wrap: break-word;
38 |
39 | // Make prettyprint styles more spaced out for readability
40 | &.prettyprint {
41 | margin-bottom: @baseLineHeight;
42 | }
43 |
44 | // Account for some code outputs that place code tags in pre tags
45 | code {
46 | padding: 0;
47 | color: inherit;
48 | background-color: transparent;
49 | border: 0;
50 | }
51 | }
52 |
53 | // Enable scrollable blocks of code
54 | .pre-scrollable {
55 | max-height: 340px;
56 | overflow-y: scroll;
57 | }
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/component-animations.less:
--------------------------------------------------------------------------------
1 | // COMPONENT ANIMATIONS
2 | // --------------------
3 |
4 | .fade {
5 | .transition(opacity .15s linear);
6 | opacity: 0;
7 | &.in {
8 | opacity: 1;
9 | }
10 | }
11 |
12 | .collapse {
13 | .transition(height .35s ease);
14 | position:relative;
15 | overflow:hidden;
16 | height: 0;
17 | &.in {
18 | height: auto;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/dropdowns.less:
--------------------------------------------------------------------------------
1 | // DROPDOWN MENUS
2 | // --------------
3 |
4 | // Use the .menu class on any
element within the topbar or ul.tabs and you'll get some superfancy dropdowns
5 | .dropdown {
6 | position: relative;
7 | }
8 | .dropdown-toggle {
9 | // The caret makes the toggle a bit too tall in IE7
10 | *margin-bottom: -3px;
11 | }
12 | .dropdown-toggle:active,
13 | .open .dropdown-toggle {
14 | outline: 0;
15 | }
16 |
17 | // Dropdown arrow/caret
18 | // --------------------
19 | .caret {
20 | display: inline-block;
21 | width: 0;
22 | height: 0;
23 | vertical-align: top;
24 | border-left: 4px solid transparent;
25 | border-right: 4px solid transparent;
26 | border-top: 4px solid @black;
27 | .opacity(30);
28 | content: "";
29 | }
30 |
31 | // Place the caret
32 | .dropdown .caret {
33 | margin-top: 8px;
34 | margin-left: 2px;
35 | }
36 | .dropdown:hover .caret,
37 | .open.dropdown .caret {
38 | .opacity(100);
39 | }
40 |
41 | // The dropdown menu (ul)
42 | // ----------------------
43 | .dropdown-menu {
44 | position: absolute;
45 | top: 100%;
46 | left: 0;
47 | z-index: @zindexDropdown;
48 | float: left;
49 | display: none; // none by default, but block on "open" of the menu
50 | min-width: 160px;
51 | padding: 4px 0;
52 | margin: 0; // override default ul
53 | list-style: none;
54 | background-color: @dropdownBackground;
55 | border-color: #ccc;
56 | border-color: rgba(0,0,0,.2);
57 | border-style: solid;
58 | border-width: 1px;
59 | .border-radius(0 0 5px 5px);
60 | .box-shadow(0 5px 10px rgba(0,0,0,.2));
61 | -webkit-background-clip: padding-box;
62 | -moz-background-clip: padding;
63 | background-clip: padding-box;
64 | *border-right-width: 2px;
65 | *border-bottom-width: 2px;
66 |
67 | // Aligns the dropdown menu to right
68 | &.pull-right {
69 | right: 0;
70 | left: auto;
71 | }
72 |
73 | // Dividers (basically an hr) within the dropdown
74 | .divider {
75 | .nav-divider();
76 | }
77 |
78 | // Links within the dropdown menu
79 | a {
80 | display: block;
81 | padding: 3px 15px;
82 | clear: both;
83 | font-weight: normal;
84 | line-height: @baseLineHeight;
85 | color: @dropdownLinkColor;
86 | white-space: nowrap;
87 | }
88 | }
89 |
90 | // Hover state
91 | // -----------
92 | .dropdown-menu li > a:hover,
93 | .dropdown-menu .active > a,
94 | .dropdown-menu .active > a:hover {
95 | color: @dropdownLinkColorHover;
96 | text-decoration: none;
97 | background-color: @dropdownLinkBackgroundHover;
98 | }
99 |
100 | // Open state for the dropdown
101 | // ---------------------------
102 | .dropdown.open {
103 | // IE7's z-index only goes to the nearest positioned ancestor, which would
104 | // make the menu appear below buttons that appeared later on the page
105 | *z-index: @zindexDropdown;
106 |
107 | .dropdown-toggle {
108 | color: @white;
109 | background: #ccc;
110 | background: rgba(0,0,0,.3);
111 | }
112 | .dropdown-menu {
113 | display: block;
114 | }
115 | }
116 |
117 | // Right aligned dropdowns
118 | .pull-right .dropdown-menu {
119 | left: auto;
120 | right: 0;
121 | }
122 |
123 | // Allow for dropdowns to go bottom up (aka, dropup-menu)
124 | // ------------------------------------------------------
125 | // Just add .dropup after the standard .dropdown class and you're set, bro.
126 | // TODO: abstract this so that the navbar fixed styles are not placed here?
127 | .dropup,
128 | .navbar-fixed-bottom .dropdown {
129 | // Reverse the caret
130 | .caret {
131 | border-top: 0;
132 | border-bottom: 4px solid @black;
133 | content: "\2191";
134 | }
135 | // Different positioning for bottom up menu
136 | .dropdown-menu {
137 | top: auto;
138 | bottom: 100%;
139 | margin-bottom: 1px;
140 | }
141 | }
142 |
143 | // Typeahead
144 | // ---------
145 | .typeahead {
146 | margin-top: 2px; // give it some space to breathe
147 | .border-radius(4px);
148 | }
149 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/grid.less:
--------------------------------------------------------------------------------
1 | // Fixed (940px)
2 | #grid > .core(@gridColumnWidth, @gridGutterWidth);
3 |
4 | // Fluid (940px)
5 | #grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth);
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/hero-unit.less:
--------------------------------------------------------------------------------
1 | // HERO UNIT
2 | // ---------
3 |
4 | .hero-unit {
5 | padding: 60px;
6 | margin-bottom: 30px;
7 | background-color: @heroUnitBackground;
8 | .border-radius(6px);
9 | h1 {
10 | margin-bottom: 0;
11 | font-size: 60px;
12 | line-height: 1;
13 | color: @heroUnitHeadingColor;
14 | letter-spacing: -1px;
15 | }
16 | p {
17 | font-size: 18px;
18 | font-weight: 200;
19 | line-height: @baseLineHeight * 1.5;
20 | color: @heroUnitLeadColor;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/labels.less:
--------------------------------------------------------------------------------
1 | // LABELS
2 | // ------
3 |
4 | // Base
5 | .label {
6 | padding: 1px 4px 2px;
7 | font-size: @baseFontSize * .846;
8 | font-weight: bold;
9 | line-height: 13px; // ensure proper line-height if floated
10 | color: @white;
11 | vertical-align: middle;
12 | white-space: nowrap;
13 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
14 | background-color: @grayLight;
15 | .border-radius(3px);
16 | }
17 |
18 | // Hover state
19 | .label:hover {
20 | color: @white;
21 | text-decoration: none;
22 | }
23 |
24 | // Colors
25 | .label-important { background-color: @errorText; }
26 | .label-important:hover { background-color: darken(@errorText, 10%); }
27 |
28 | .label-warning { background-color: @orange; }
29 | .label-warning:hover { background-color: darken(@orange, 10%); }
30 |
31 | .label-success { background-color: @successText; }
32 | .label-success:hover { background-color: darken(@successText, 10%); }
33 |
34 | .label-info { background-color: @infoText; }
35 | .label-info:hover { background-color: darken(@infoText, 10%); }
36 |
37 | .label-inverse { background-color: @grayDark; }
38 | .label-inverse:hover { background-color: darken(@grayDark, 10%); }
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/layouts.less:
--------------------------------------------------------------------------------
1 | //
2 | // Layouts
3 | // Fixed-width and fluid (with sidebar) layouts
4 | // --------------------------------------------
5 |
6 |
7 | // Container (centered, fixed-width layouts)
8 | .container {
9 | .container-fixed();
10 | }
11 |
12 | // Fluid layouts (left aligned, with sidebar, min- & max-width content)
13 | .container-fluid {
14 | padding-left: @gridGutterWidth;
15 | padding-right: @gridGutterWidth;
16 | .clearfix();
17 | }
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/modals.less:
--------------------------------------------------------------------------------
1 | // MODALS
2 | // ------
3 |
4 | // Recalculate z-index where appropriate
5 | .modal-open {
6 | .dropdown-menu { z-index: @zindexDropdown + @zindexModal; }
7 | .dropdown.open { *z-index: @zindexDropdown + @zindexModal; }
8 | .popover { z-index: @zindexPopover + @zindexModal; }
9 | .tooltip { z-index: @zindexTooltip + @zindexModal; }
10 | }
11 |
12 | // Background
13 | .modal-backdrop {
14 | position: fixed;
15 | top: 0;
16 | right: 0;
17 | bottom: 0;
18 | left: 0;
19 | z-index: @zindexModalBackdrop;
20 | background-color: @black;
21 | // Fade for backdrop
22 | &.fade { opacity: 0; }
23 | }
24 |
25 | .modal-backdrop,
26 | .modal-backdrop.fade.in {
27 | .opacity(80);
28 | }
29 |
30 | // Base modal
31 | .modal {
32 | position: fixed;
33 | top: 50%;
34 | left: 50%;
35 | z-index: @zindexModal;
36 | overflow: auto;
37 | width: 560px;
38 | margin: -250px 0 0 -280px;
39 | background-color: @white;
40 | border: 1px solid #999;
41 | border: 1px solid rgba(0,0,0,.3);
42 | *border: 1px solid #999; /* IE6-7 */
43 | .border-radius(6px);
44 | .box-shadow(0 3px 7px rgba(0,0,0,0.3));
45 | .background-clip(padding-box);
46 | &.fade {
47 | .transition(e('opacity .3s linear, top .3s ease-out'));
48 | top: -25%;
49 | }
50 | &.fade.in { top: 50%; }
51 | }
52 | .modal-header {
53 | padding: 9px 15px;
54 | border-bottom: 1px solid #eee;
55 | // Close icon
56 | .close { margin-top: 2px; }
57 | }
58 |
59 | // Body (where all modal content resises)
60 | .modal-body {
61 | overflow-y: auto;
62 | max-height: 400px;
63 | padding: 15px;
64 | }
65 | // Remove bottom margin if need be
66 | .modal-form {
67 | margin-bottom: 0;
68 | }
69 |
70 | // Footer (for actions)
71 | .modal-footer {
72 | padding: 14px 15px 15px;
73 | margin-bottom: 0;
74 | text-align: right; // right align buttons
75 | background-color: #f5f5f5;
76 | border-top: 1px solid #ddd;
77 | .border-radius(0 0 6px 6px);
78 | .box-shadow(inset 0 1px 0 @white);
79 | .clearfix(); // clear it in case folks use .pull-* classes on buttons
80 |
81 | // Properly space out buttons
82 | .btn + .btn {
83 | margin-left: 5px;
84 | margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
85 | }
86 | // but override that for button groups
87 | .btn-group .btn + .btn {
88 | margin-left: -1px;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/pager.less:
--------------------------------------------------------------------------------
1 | // PAGER
2 | // -----
3 |
4 | .pager {
5 | margin-left: 0;
6 | margin-bottom: @baseLineHeight;
7 | list-style: none;
8 | text-align: center;
9 | .clearfix();
10 | }
11 | .pager li {
12 | display: inline;
13 | }
14 | .pager a {
15 | display: inline-block;
16 | padding: 5px 14px;
17 | background-color: #fff;
18 | border: 1px solid #ddd;
19 | .border-radius(15px);
20 | }
21 | .pager a:hover {
22 | text-decoration: none;
23 | background-color: #f5f5f5;
24 | }
25 | .pager .next a {
26 | float: right;
27 | }
28 | .pager .previous a {
29 | float: left;
30 | }
31 | .pager .disabled a,
32 | .pager .disabled a:hover {
33 | color: @grayLight;
34 | background-color: #fff;
35 | cursor: default;
36 | }
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/pagination.less:
--------------------------------------------------------------------------------
1 | // PAGINATION
2 | // ----------
3 |
4 | .pagination {
5 | height: @baseLineHeight * 2;
6 | margin: @baseLineHeight 0;
7 | }
8 | .pagination ul {
9 | display: inline-block;
10 | .ie7-inline-block();
11 | margin-left: 0;
12 | margin-bottom: 0;
13 | .border-radius(3px);
14 | .box-shadow(0 1px 2px rgba(0,0,0,.05));
15 | }
16 | .pagination li {
17 | display: inline;
18 | }
19 | .pagination a {
20 | float: left;
21 | padding: 0 14px;
22 | line-height: (@baseLineHeight * 2) - 2;
23 | text-decoration: none;
24 | border: 1px solid #ddd;
25 | border-left-width: 0;
26 | }
27 | .pagination a:hover,
28 | .pagination .active a {
29 | background-color: #f5f5f5;
30 | }
31 | .pagination .active a {
32 | color: @grayLight;
33 | cursor: default;
34 | }
35 | .pagination .disabled span,
36 | .pagination .disabled a,
37 | .pagination .disabled a:hover {
38 | color: @grayLight;
39 | background-color: transparent;
40 | cursor: default;
41 | }
42 | .pagination li:first-child a {
43 | border-left-width: 1px;
44 | .border-radius(3px 0 0 3px);
45 | }
46 | .pagination li:last-child a {
47 | .border-radius(0 3px 3px 0);
48 | }
49 |
50 | // Centered
51 | .pagination-centered {
52 | text-align: center;
53 | }
54 | .pagination-right {
55 | text-align: right;
56 | }
57 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/popovers.less:
--------------------------------------------------------------------------------
1 | // POPOVERS
2 | // --------
3 |
4 | .popover {
5 | position: absolute;
6 | top: 0;
7 | left: 0;
8 | z-index: @zindexPopover;
9 | display: none;
10 | padding: 5px;
11 | &.top { margin-top: -5px; }
12 | &.right { margin-left: 5px; }
13 | &.bottom { margin-top: 5px; }
14 | &.left { margin-left: -5px; }
15 | &.top .arrow { #popoverArrow > .top(); }
16 | &.right .arrow { #popoverArrow > .right(); }
17 | &.bottom .arrow { #popoverArrow > .bottom(); }
18 | &.left .arrow { #popoverArrow > .left(); }
19 | .arrow {
20 | position: absolute;
21 | width: 0;
22 | height: 0;
23 | }
24 | }
25 | .popover-inner {
26 | padding: 3px;
27 | width: 280px;
28 | overflow: hidden;
29 | background: @black; // has to be full background declaration for IE fallback
30 | background: rgba(0,0,0,.8);
31 | .border-radius(6px);
32 | .box-shadow(0 3px 7px rgba(0,0,0,0.3));
33 | }
34 | .popover-title {
35 | padding: 9px 15px;
36 | line-height: 1;
37 | background-color: #f5f5f5;
38 | border-bottom:1px solid #eee;
39 | .border-radius(3px 3px 0 0);
40 | }
41 | .popover-content {
42 | padding: 14px;
43 | background-color: @white;
44 | .border-radius(0 0 3px 3px);
45 | .background-clip(padding-box);
46 | p, ul, ol {
47 | margin-bottom: 0;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/progress-bars.less:
--------------------------------------------------------------------------------
1 | // PROGRESS BARS
2 | // -------------
3 |
4 |
5 | // ANIMATIONS
6 | // ----------
7 |
8 | // Webkit
9 | @-webkit-keyframes progress-bar-stripes {
10 | from { background-position: 0 0; }
11 | to { background-position: 40px 0; }
12 | }
13 |
14 | // Firefox
15 | @-moz-keyframes progress-bar-stripes {
16 | from { background-position: 0 0; }
17 | to { background-position: 40px 0; }
18 | }
19 |
20 | // IE9
21 | @-ms-keyframes progress-bar-stripes {
22 | from { background-position: 0 0; }
23 | to { background-position: 40px 0; }
24 | }
25 |
26 | // Spec
27 | @keyframes progress-bar-stripes {
28 | from { background-position: 0 0; }
29 | to { background-position: 40px 0; }
30 | }
31 |
32 |
33 |
34 | // THE BARS
35 | // --------
36 |
37 | // Outer container
38 | .progress {
39 | overflow: hidden;
40 | height: 18px;
41 | margin-bottom: 18px;
42 | #gradient > .vertical(#f5f5f5, #f9f9f9);
43 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
44 | .border-radius(4px);
45 | }
46 |
47 | // Bar of progress
48 | .progress .bar {
49 | width: 0%;
50 | height: 18px;
51 | color: @white;
52 | font-size: 12px;
53 | text-align: center;
54 | text-shadow: 0 -1px 0 rgba(0,0,0,.25);
55 | #gradient > .vertical(#149bdf, #0480be);
56 | .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
57 | .box-sizing(border-box);
58 | .transition(width .6s ease);
59 | }
60 |
61 | // Striped bars
62 | .progress-striped .bar {
63 | #gradient > .striped(#149bdf);
64 | .background-size(40px 40px);
65 | }
66 |
67 | // Call animation for the active one
68 | .progress.active .bar {
69 | -webkit-animation: progress-bar-stripes 2s linear infinite;
70 | -moz-animation: progress-bar-stripes 2s linear infinite;
71 | animation: progress-bar-stripes 2s linear infinite;
72 | }
73 |
74 |
75 |
76 | // COLORS
77 | // ------
78 |
79 | // Danger (red)
80 | .progress-danger .bar {
81 | #gradient > .vertical(#ee5f5b, #c43c35);
82 | }
83 | .progress-danger.progress-striped .bar {
84 | #gradient > .striped(#ee5f5b);
85 | }
86 |
87 | // Success (green)
88 | .progress-success .bar {
89 | #gradient > .vertical(#62c462, #57a957);
90 | }
91 | .progress-success.progress-striped .bar {
92 | #gradient > .striped(#62c462);
93 | }
94 |
95 | // Info (teal)
96 | .progress-info .bar {
97 | #gradient > .vertical(#5bc0de, #339bb9);
98 | }
99 | .progress-info.progress-striped .bar {
100 | #gradient > .striped(#5bc0de);
101 | }
102 |
103 | // Warning (orange)
104 | .progress-warning .bar {
105 | #gradient > .vertical(lighten(@orange, 15%), @orange);
106 | }
107 | .progress-warning.progress-striped .bar {
108 | #gradient > .striped(lighten(@orange, 15%));
109 | }
110 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/reset.less:
--------------------------------------------------------------------------------
1 | // Reset.less
2 | // Adapted from Normalize.css http://github.com/necolas/normalize.css
3 | // ------------------------------------------------------------------------
4 |
5 | // Display in IE6-9 and FF3
6 | // -------------------------
7 |
8 | article,
9 | aside,
10 | details,
11 | figcaption,
12 | figure,
13 | footer,
14 | header,
15 | hgroup,
16 | nav,
17 | section {
18 | display: block;
19 | }
20 |
21 | // Display block in IE6-9 and FF3
22 | // -------------------------
23 |
24 | audio,
25 | canvas,
26 | video {
27 | display: inline-block;
28 | *display: inline;
29 | *zoom: 1;
30 | }
31 |
32 | // Prevents modern browsers from displaying 'audio' without controls
33 | // -------------------------
34 |
35 | audio:not([controls]) {
36 | display: none;
37 | }
38 |
39 | // Base settings
40 | // -------------------------
41 |
42 | html {
43 | font-size: 100%;
44 | -webkit-text-size-adjust: 100%;
45 | -ms-text-size-adjust: 100%;
46 | }
47 | // Focus states
48 | a:focus {
49 | .tab-focus();
50 | }
51 | // Hover & Active
52 | a:hover,
53 | a:active {
54 | outline: 0;
55 | }
56 |
57 | // Prevents sub and sup affecting line-height in all browsers
58 | // -------------------------
59 |
60 | sub,
61 | sup {
62 | position: relative;
63 | font-size: 75%;
64 | line-height: 0;
65 | vertical-align: baseline;
66 | }
67 | sup {
68 | top: -0.5em;
69 | }
70 | sub {
71 | bottom: -0.25em;
72 | }
73 |
74 | // Img border in a's and image quality
75 | // -------------------------
76 |
77 | img {
78 | height: auto;
79 | border: 0;
80 | -ms-interpolation-mode: bicubic;
81 | vertical-align: middle;
82 | }
83 |
84 | // Forms
85 | // -------------------------
86 |
87 | // Font size in all browsers, margin changes, misc consistency
88 | button,
89 | input,
90 | select,
91 | textarea {
92 | margin: 0;
93 | font-size: 100%;
94 | vertical-align: middle;
95 | }
96 | button,
97 | input {
98 | *overflow: visible; // Inner spacing ie IE6/7
99 | line-height: normal; // FF3/4 have !important on line-height in UA stylesheet
100 | }
101 | button::-moz-focus-inner,
102 | input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
103 | padding: 0;
104 | border: 0;
105 | }
106 | button,
107 | input[type="button"],
108 | input[type="reset"],
109 | input[type="submit"] {
110 | cursor: pointer; // Cursors on all buttons applied consistently
111 | -webkit-appearance: button; // Style clickable inputs in iOS
112 | }
113 | input[type="search"] { // Appearance in Safari/Chrome
114 | -webkit-appearance: textfield;
115 | -webkit-box-sizing: content-box;
116 | -moz-box-sizing: content-box;
117 | box-sizing: content-box;
118 | }
119 | input[type="search"]::-webkit-search-decoration,
120 | input[type="search"]::-webkit-search-cancel-button {
121 | -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
122 | }
123 | textarea {
124 | overflow: auto; // Remove vertical scrollbar in IE6-9
125 | vertical-align: top; // Readability and alignment cross-browser
126 | }
127 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/scaffolding.less:
--------------------------------------------------------------------------------
1 | // Scaffolding
2 | // Basic and global styles for generating a grid system, structural layout, and page templates
3 | // -------------------------------------------------------------------------------------------
4 |
5 |
6 | // Body reset
7 | // ----------
8 |
9 | body {
10 | margin: 0;
11 | padding-top: 70px;
12 | font-family: @baseFontFamily;
13 | font-size: @baseFontSize;
14 | line-height: @baseLineHeight;
15 | color: @textColor;
16 | background-color: @bodyBackground;
17 | }
18 |
19 |
20 | // Links
21 | // -----
22 |
23 | a {
24 | color: @linkColor;
25 | text-decoration: none;
26 | }
27 | a:hover {
28 | color: @linkColorHover;
29 | text-decoration: underline;
30 | }
31 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/tables.less:
--------------------------------------------------------------------------------
1 | //
2 | // Tables.less
3 | // Tables for, you guessed it, tabular data
4 | // ----------------------------------------
5 |
6 |
7 | // BASE TABLES
8 | // -----------------
9 |
10 | table {
11 | max-width: 100%;
12 | border-collapse: collapse;
13 | border-spacing: 0;
14 | background-color: @tableBackground;
15 | }
16 |
17 | // BASELINE STYLES
18 | // ---------------
19 |
20 | .table {
21 | width: 100%;
22 | //margin-bottom: @baseLineHeight;
23 | // Cells
24 | th,
25 | td {
26 | padding: 5px;
27 | line-height: @baseLineHeight;
28 | text-align: left;
29 | vertical-align: top;
30 | //border-top: 1px solid @tableBorder;
31 | }
32 | th {
33 | font-weight: bold;
34 | }
35 | // Bottom align for column headings
36 | thead th {
37 | vertical-align: bottom;
38 | }
39 | // Remove top border from thead by default
40 | colgroup + thead tr:first-child th,
41 | colgroup + thead tr:first-child td,
42 | thead:first-child tr:first-child th,
43 | thead:first-child tr:first-child td {
44 | border-top: 0;
45 | }
46 | // Account for multiple tbody instances
47 | tbody + tbody {
48 | border-top: 2px solid @tableBorder;
49 | }
50 | }
51 |
52 |
53 |
54 | // CONDENSED TABLE W/ HALF PADDING
55 | // -------------------------------
56 |
57 | .table-condensed {
58 | th,
59 | td {
60 | padding: 4px 5px;
61 | }
62 | }
63 |
64 |
65 | // BORDERED VERSION
66 | // ----------------
67 |
68 | .table-bordered {
69 | border: 1px solid @tableBorder;
70 | border-left: 0;
71 | border-collapse: separate; // Done so we can round those corners!
72 | *border-collapse: collapsed; // IE7 can't round corners anyway
73 | .border-radius(4px);
74 | th,
75 | td {
76 | border-left: 1px solid @tableBorder;
77 | }
78 | // Prevent a double border
79 | thead:first-child tr:first-child th,
80 | tbody:first-child tr:first-child th,
81 | tbody:first-child tr:first-child td {
82 | border-top: 0;
83 | }
84 | // For first th or td in the first row in the first thead or tbody
85 | thead:first-child tr:first-child th:first-child,
86 | tbody:first-child tr:first-child td:first-child {
87 | .border-radius(4px 0 0 0);
88 | }
89 | thead:first-child tr:first-child th:last-child,
90 | tbody:first-child tr:first-child td:last-child {
91 | .border-radius(0 4px 0 0);
92 | }
93 | // For first th or td in the first row in the first thead or tbody
94 | thead:last-child tr:last-child th:first-child,
95 | tbody:last-child tr:last-child td:first-child {
96 | .border-radius(0 0 0 4px);
97 | }
98 | thead:last-child tr:last-child th:last-child,
99 | tbody:last-child tr:last-child td:last-child {
100 | .border-radius(0 0 4px 0);
101 | }
102 | }
103 |
104 |
105 | // ZEBRA-STRIPING
106 | // --------------
107 |
108 | // Default zebra-stripe styles (alternating gray and transparent backgrounds)
109 | .table-striped {
110 | tbody {
111 | tr:nth-child(odd) td,
112 | tr:nth-child(odd) th {
113 | background-color: @tableBackgroundAccent;
114 | }
115 | }
116 | }
117 |
118 |
119 | // HOVER EFFECT
120 | // ------------
121 | // Placed here since it has to come after the potential zebra striping
122 | .table {
123 | tbody tr:hover td,
124 | tbody tr:hover th {
125 | //background-color: @tableBackgroundHover;
126 | }
127 | }
128 |
129 |
130 | // TABLE CELL SIZING
131 | // -----------------
132 |
133 | // Change the columns
134 | table {
135 | .span1 { .tableColumns(1); }
136 | .span2 { .tableColumns(2); }
137 | .span3 { .tableColumns(3); }
138 | .span4 { .tableColumns(4); }
139 | .span5 { .tableColumns(5); }
140 | .span6 { .tableColumns(6); }
141 | .span7 { .tableColumns(7); }
142 | .span8 { .tableColumns(8); }
143 | .span9 { .tableColumns(9); }
144 | .span10 { .tableColumns(10); }
145 | .span11 { .tableColumns(11); }
146 | .span12 { .tableColumns(12); }
147 | .span13 { .tableColumns(13); }
148 | .span14 { .tableColumns(14); }
149 | .span15 { .tableColumns(15); }
150 | .span16 { .tableColumns(16); }
151 | .span17 { .tableColumns(17); }
152 | .span18 { .tableColumns(18); }
153 | .span19 { .tableColumns(19); }
154 | .span20 { .tableColumns(20); }
155 | .span21 { .tableColumns(21); }
156 | .span22 { .tableColumns(22); }
157 | .span23 { .tableColumns(23); }
158 | .span24 { .tableColumns(24); }
159 | }
160 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/thumbnails.less:
--------------------------------------------------------------------------------
1 | // THUMBNAILS
2 | // ----------
3 |
4 | .thumbnails {
5 | margin-left: -@gridGutterWidth;
6 | list-style: none;
7 | .clearfix();
8 | }
9 | .thumbnails > li {
10 | float: left;
11 | margin: 0 0 @baseLineHeight @gridGutterWidth;
12 | }
13 | .thumbnail {
14 | display: block;
15 | padding: 4px;
16 | line-height: 1;
17 | border: 1px solid #ddd;
18 | .border-radius(4px);
19 | .box-shadow(0 1px 1px rgba(0,0,0,.075));
20 | }
21 | // Add a hover state for linked versions only
22 | a.thumbnail:hover {
23 | border-color: @linkColor;
24 | .box-shadow(0 1px 4px rgba(0,105,214,.25));
25 | }
26 | // Images and captions
27 | .thumbnail > img {
28 | display: block;
29 | max-width: 100%;
30 | margin-left: auto;
31 | margin-right: auto;
32 | }
33 | .thumbnail .caption {
34 | padding: 9px;
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/tooltip.less:
--------------------------------------------------------------------------------
1 | // TOOLTIP
2 | // ------=
3 |
4 | .tooltip {
5 | position: absolute;
6 | z-index: @zindexTooltip;
7 | display: block;
8 | visibility: visible;
9 | padding: 5px;
10 | font-size: 11px;
11 | .opacity(0);
12 | &.in { .opacity(80); }
13 | &.top { margin-top: -2px; }
14 | &.right { margin-left: 2px; }
15 | &.bottom { margin-top: 2px; }
16 | &.left { margin-left: -2px; }
17 | &.top .tooltip-arrow { #popoverArrow > .top(); }
18 | &.left .tooltip-arrow { #popoverArrow > .left(); }
19 | &.bottom .tooltip-arrow { #popoverArrow > .bottom(); }
20 | &.right .tooltip-arrow { #popoverArrow > .right(); }
21 | }
22 | .tooltip-inner {
23 | max-width: 200px;
24 | padding: 3px 8px;
25 | color: @white;
26 | text-align: center;
27 | text-decoration: none;
28 | background-color: @black;
29 | .border-radius(4px);
30 | }
31 | .tooltip-arrow {
32 | position: absolute;
33 | width: 0;
34 | height: 0;
35 | }
36 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/type.less:
--------------------------------------------------------------------------------
1 | // Typography.less
2 | // Headings, body text, lists, code, and more for a versatile and durable typography system
3 | // ----------------------------------------------------------------------------------------
4 |
5 |
6 | // BODY TEXT
7 | // ---------
8 |
9 | p {
10 | margin: 0 0 @baseLineHeight / 2;
11 | font-family: @baseFontFamily;
12 | font-size: @baseFontSize;
13 | line-height: @baseLineHeight;
14 | small {
15 | font-size: @baseFontSize - 2;
16 | color: @grayLight;
17 | }
18 | }
19 | .lead {
20 | margin-bottom: @baseLineHeight;
21 | font-size: 20px;
22 | font-weight: 200;
23 | line-height: @baseLineHeight * 1.5;
24 | }
25 |
26 | // HEADINGS
27 | // --------
28 |
29 | h1, h2, h3, h4, h5, h6 {
30 | margin: 0;
31 | font-family: @headingsFontFamily;
32 | font-weight: @headingsFontWeight;
33 | color: @headingsColor;
34 | text-rendering: optimizelegibility; // Fix the character spacing for headings
35 | small {
36 | font-weight: normal;
37 | color: @grayLight;
38 | }
39 | }
40 | h1 {
41 | font-size: 30px;
42 | line-height: @baseLineHeight * 2;
43 | small {
44 | font-size: 18px;
45 | }
46 | }
47 | h2 {
48 | font-size: 24px;
49 | line-height: @baseLineHeight * 2;
50 | small {
51 | font-size: 18px;
52 | }
53 | }
54 | h3 {
55 | line-height: @baseLineHeight * 1.5;
56 | font-size: 18px;
57 | small {
58 | font-size: 14px;
59 | }
60 | }
61 | h4, h5, h6 {
62 | line-height: @baseLineHeight;
63 | }
64 | h4 {
65 | font-size: 14px;
66 | small {
67 | font-size: 12px;
68 | }
69 | }
70 | h5 {
71 | font-size: 12px;
72 | }
73 | h6 {
74 | font-size: 11px;
75 | color: @grayLight;
76 | text-transform: uppercase;
77 | }
78 |
79 | // Page header
80 | .page-header {
81 | padding-bottom: @baseLineHeight - 1;
82 | margin: @baseLineHeight 0;
83 | border-bottom: 1px solid @grayLighter;
84 | }
85 | .page-header h1 {
86 | line-height: 1;
87 | }
88 |
89 |
90 |
91 | // LISTS
92 | // -----
93 |
94 | // Unordered and Ordered lists
95 | ul, ol {
96 | padding: 0;
97 | margin: 0 0 @baseLineHeight / 2 25px;
98 | }
99 | ul ul,
100 | ul ol,
101 | ol ol,
102 | ol ul {
103 | margin-bottom: 0;
104 | }
105 | ul {
106 | list-style: disc;
107 | }
108 | ol {
109 | list-style: decimal;
110 | }
111 | li {
112 | line-height: @baseLineHeight;
113 | }
114 | ul.unstyled,
115 | ol.unstyled {
116 | margin-left: 0;
117 | list-style: none;
118 | }
119 |
120 | // Description Lists
121 | dl {
122 | margin-bottom: @baseLineHeight;
123 | }
124 | dt,
125 | dd {
126 | line-height: @baseLineHeight;
127 | }
128 | dt {
129 | font-weight: bold;
130 | line-height: @baseLineHeight - 1; // fix jank Helvetica Neue font bug
131 | }
132 | dd {
133 | margin-left: @baseLineHeight / 2;
134 | }
135 | // Horizontal layout (like forms)
136 | .dl-horizontal {
137 | dt {
138 | float: left;
139 | clear: left;
140 | width: 120px;
141 | text-align: right;
142 | }
143 | dd {
144 | margin-left: 130px;
145 | }
146 | }
147 |
148 | // MISC
149 | // ----
150 |
151 | // Horizontal rules
152 | hr {
153 | margin: 2 0;
154 | border: 0;
155 | border-top: @hrWidth solid @hrBorder;
156 | border-bottom: 1px solid @white;
157 | }
158 |
159 | // Emphasis
160 | strong {
161 | font-weight: bold;
162 | }
163 | em {
164 | font-style: italic;
165 | }
166 | .muted {
167 | color: @grayLight;
168 | }
169 |
170 | // Abbreviations and acronyms
171 | abbr[title] {
172 | border-bottom: 1px dotted #ddd;
173 | cursor: help;
174 | }
175 | abbr.initialism {
176 | font-size: 90%;
177 | text-transform: uppercase;
178 | }
179 |
180 | // Blockquotes
181 | blockquote {
182 | padding: 0 0 0 15px;
183 | margin: 0 0 @baseLineHeight;
184 | border-left: 5px solid @grayLighter;
185 | p {
186 | margin-bottom: 0;
187 | #font > .shorthand(16px,300,@baseLineHeight * 1.25);
188 | }
189 | small {
190 | display: block;
191 | line-height: @baseLineHeight;
192 | color: @grayLight;
193 | &:before {
194 | content: '\2014 \00A0';
195 | }
196 | }
197 |
198 | // Float right with text-align: right
199 | &.pull-right {
200 | float: right;
201 | padding-left: 0;
202 | padding-right: 15px;
203 | border-left: 0;
204 | border-right: 5px solid @grayLighter;
205 | p,
206 | small {
207 | text-align: right;
208 | }
209 | }
210 | }
211 |
212 | // Quotes
213 | q:before,
214 | q:after,
215 | blockquote:before,
216 | blockquote:after {
217 | content: "";
218 | }
219 |
220 | // Addresses
221 | address {
222 | display: block;
223 | margin-bottom: @baseLineHeight;
224 | line-height: @baseLineHeight;
225 | font-style: normal;
226 | }
227 |
228 | // Misc
229 | small {
230 | font-size: 100%;
231 | }
232 | cite {
233 | font-style: normal;
234 | }
235 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/utilities.less:
--------------------------------------------------------------------------------
1 | // UTILITY CLASSES
2 | // ---------------
3 |
4 | // Quick floats
5 | .pull-right {
6 | float: right;
7 | }
8 | .pull-left {
9 | float: left;
10 | }
11 |
12 | // Toggling content
13 | .hide {
14 | display: none;
15 | }
16 | .show {
17 | display: block;
18 | }
19 |
20 | // Visibility
21 | .invisible {
22 | visibility: hidden;
23 | }
24 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/variables.less:
--------------------------------------------------------------------------------
1 | // Variables.less
2 | // Variables to customize the look and feel of Bootstrap
3 | // -----------------------------------------------------
4 |
5 |
6 |
7 | // GLOBAL VALUES
8 | // --------------------------------------------------
9 |
10 |
11 | // Grays
12 | // -------------------------
13 | @black: #000;
14 | @grayDarker: #222;
15 | @grayDark: #333;
16 | @gray: #555;
17 | @grayLight: #999;
18 | @grayLighter: #eee;
19 | @white: #fff;
20 |
21 |
22 | // Accent colors
23 | // -------------------------
24 | @blue: #049cdb;
25 | @blueDark: #0064cd;
26 | @green: #46a546;
27 | @red: #9d261d;
28 | @yellow: #ffc40d;
29 | @orange: #f89406;
30 | @pink: #c3325f;
31 | @purple: #7a43b6;
32 |
33 |
34 | // Scaffolding
35 | // -------------------------
36 | @bodyBackground: @white;
37 | @textColor: @grayDark;
38 |
39 |
40 | // Links
41 | // -------------------------
42 | @linkColor: #08c;
43 | @linkColorHover: darken(@linkColor, 15%);
44 |
45 |
46 | // Typography
47 | // -------------------------
48 | @baseFontSize: 13px;
49 | @baseFontFamily: "Myriad Pro";
50 | @baseLineHeight: 18px;
51 | @altFontFamily: Georgia, "Times New Roman", Times, serif;
52 |
53 | @headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
54 | @headingsFontWeight: bold; // instead of browser default, bold
55 | @headingsColor: inherit; // empty to use BS default, @textColor
56 |
57 |
58 | // Tables
59 | // -------------------------
60 | @tableBackground: transparent; // overall background-color
61 | @tableBackgroundAccent: #f9f9f9; // for striping
62 | @tableBackgroundHover: #f5f5f5; // for hover
63 | @tableBorder: #ddd; // table and cell border
64 |
65 |
66 | // Buttons
67 | // -------------------------
68 | @btnBackground: @white;
69 | @btnBackgroundHighlight: darken(@white, 10%);
70 | @btnBorder: darken(@white, 20%);
71 |
72 | @btnPrimaryBackground: @linkColor;
73 | @btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 15%);
74 |
75 | @btnInfoBackground: #5bc0de;
76 | @btnInfoBackgroundHighlight: #2f96b4;
77 |
78 | @btnSuccessBackground: #62c462;
79 | @btnSuccessBackgroundHighlight: #51a351;
80 |
81 | @btnWarningBackground: lighten(@orange, 15%);
82 | @btnWarningBackgroundHighlight: @orange;
83 |
84 | @btnDangerBackground: #ee5f5b;
85 | @btnDangerBackgroundHighlight: #bd362f;
86 |
87 | @btnInverseBackground: @gray;
88 | @btnInverseBackgroundHighlight: @grayDarker;
89 |
90 |
91 | // Forms
92 | // -------------------------
93 | @inputBackground: @white;
94 | @inputBorder: #ccc;
95 | @inputDisabledBackground: @grayLighter;
96 |
97 |
98 | // Dropdowns
99 | // -------------------------
100 | @dropdownBackground: @white;
101 | @dropdownBorder: rgba(0,0,0,.2);
102 | @dropdownLinkColor: @grayDark;
103 | @dropdownLinkColorHover: @white;
104 | @dropdownLinkBackgroundHover: @linkColor;
105 |
106 |
107 |
108 |
109 | // COMPONENT VARIABLES
110 | // --------------------------------------------------
111 |
112 | // Z-index master list
113 | // -------------------------
114 | // Used for a bird's eye view of components dependent on the z-axis
115 | // Try to avoid customizing these :)
116 | @zindexDropdown: 1000;
117 | @zindexPopover: 1010;
118 | @zindexTooltip: 1020;
119 | @zindexFixedNavbar: 1030;
120 | @zindexModalBackdrop: 1040;
121 | @zindexModal: 1050;
122 |
123 |
124 | // Sprite icons path
125 | // -------------------------
126 | @iconSpritePath: "../img/glyphicons-halflings.png";
127 | @iconWhiteSpritePath: "../img/glyphicons-halflings-white.png";
128 |
129 |
130 | // Input placeholder text color
131 | // -------------------------
132 | @placeholderText: @grayLight;
133 |
134 |
135 | // Hr border color
136 | // -------------------------
137 | @hrBorder: @grayLighter;
138 | //@hrBorder: #000;
139 | @hrWidth: 2px;
140 |
141 | // Navbar
142 | // -------------------------
143 | @navbarHeight: 40px;
144 | @navbarBackground: @grayDarker;
145 | @navbarBackgroundHighlight: @grayDark;
146 |
147 | @navbarText: @grayLight;
148 | @navbarLinkColor: @grayLight;
149 | @navbarLinkColorHover: @white;
150 | @navbarLinkColorActive: @navbarLinkColorHover;
151 | @navbarLinkBackgroundHover: transparent;
152 | @navbarLinkBackgroundActive: @navbarBackground;
153 |
154 | @navbarSearchBackground: lighten(@navbarBackground, 25%);
155 | @navbarSearchBackgroundFocus: @white;
156 | @navbarSearchBorder: darken(@navbarSearchBackground, 30%);
157 | @navbarSearchPlaceholderColor: #ccc;
158 |
159 |
160 | // Hero unit
161 | // -------------------------
162 | @heroUnitBackground: @grayLighter;
163 | @heroUnitHeadingColor: inherit;
164 | @heroUnitLeadColor: inherit;
165 |
166 |
167 | // Form states and alerts
168 | // -------------------------
169 | @warningText: #c09853;
170 | @warningBackground: #fcf8e3;
171 | @warningBorder: darken(spin(@warningBackground, -10), 3%);
172 |
173 | @errorText: #b94a48;
174 | @errorBackground: #f2dede;
175 | @errorBorder: darken(spin(@errorBackground, -10), 3%);
176 |
177 | @successText: #468847;
178 | @successBackground: #dff0d8;
179 | @successBorder: darken(spin(@successBackground, -10), 5%);
180 |
181 | @infoText: #3a87ad;
182 | @infoBackground: #d9edf7;
183 | @infoBorder: darken(spin(@infoBackground, -10), 7%);
184 |
185 |
186 |
187 |
188 | // GRID
189 | // --------------------------------------------------
190 |
191 | // Default 940px grid
192 | // -------------------------
193 | @gridColumns: 12;
194 | @gridColumnWidth: 60px;
195 | @gridGutterWidth: 20px;
196 | @gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));
197 |
198 |
199 | // Fluid grid
200 | // -------------------------
201 | @fluidGridColumnWidth: 6.382978723%;
202 | @fluidGridGutterWidth: 2.127659574%;
203 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/wells.less:
--------------------------------------------------------------------------------
1 | // WELLS
2 | // -----
3 |
4 | .well {
5 | min-height: 20px;
6 | padding: 0px;
7 | margin-bottom: 0px;
8 | background-color: #DAEAF1;
9 | border: 1px solid #DAEAF1;
10 | //border: 1px solid rgba(0,0,0,.05);
11 | .border-radius(4px);
12 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
13 | blockquote {
14 | border-color: #ddd;
15 | border-color: rgba(0,0,0,.15);
16 | }
17 | height:65px;
18 | }
19 |
20 | // Sizes
21 | .well-large {
22 | padding: 24px;
23 | .border-radius(6px);
24 | }
25 | .well-small {
26 | padding: 10px;
27 | .border-radius(0px);
28 | }
29 |
30 | .alert
31 | {
32 | background-color: #E70B20 !important;
33 | color:white !important;
34 | }
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/www/js/libs/bootstrap/less/widget.less:
--------------------------------------------------------------------------------
1 | // WIDGETS
2 | // ----------
3 |
4 | .widget {
5 | //display: block;
6 | //padding: 10px;
7 | //margin:10px;
8 | //line-height: 1;
9 | //border: 1px solid #ddd;
10 | //.border-radius(4px);
11 | //.box-shadow(0 1px 1px rgba(0,0,0,.075));
12 | }
13 |
14 | .info-box{
15 | min-height: 20px;
16 | min-width: 250px;
17 | padding: 5px;
18 | margin-bottom: 0px;
19 | background-color: #DAEAF1;
20 | border: 1px solid #DAEAF1;
21 | //border: 1px solid rgba(0,0,0,.05);
22 | .border-radius(0px);
23 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
24 | blockquote {
25 | border-color: #ddd;
26 | border-color: rgba(0,0,0,.15);
27 | }
28 | height:65px;
29 | }
30 |
31 | .date-control{
32 | display: inline-block;
33 | position: relative;
34 | top:-10px;
35 | }
36 |
37 | .well-value {
38 | font-size: 60px;
39 | padding: 10px;
40 | weight:bold;
41 | color: #1581AA;
42 | //color: #000;
43 | }
44 |
45 |
46 | .server-list{
47 | margin-top: 7px !important;
48 | }
--------------------------------------------------------------------------------
/src/www/js/models/commands-widget-model.js:
--------------------------------------------------------------------------------
1 | var CommandsWidgetModel = Backbone.Model.extend({
2 |
3 | url : "api/commands",
4 |
5 | initialize : function(){
6 |
7 | }
8 |
9 | })
10 |
--------------------------------------------------------------------------------
/src/www/js/models/info-widget-model.js:
--------------------------------------------------------------------------------
1 | var InfoWidgetModel = Backbone.Model.extend({
2 |
3 | url: "api/info",
4 |
5 | initialize: function() {
6 | },
7 |
8 | defaults: {
9 | "databases" : {}
10 | }
11 |
12 | })
13 |
--------------------------------------------------------------------------------
/src/www/js/models/memory-widget-model.js:
--------------------------------------------------------------------------------
1 | var MemoryWidgetModel = Backbone.Model.extend({
2 |
3 | url : "api/memory",
4 |
5 | initialize : function(){
6 |
7 | }
8 |
9 | })
10 |
--------------------------------------------------------------------------------
/src/www/js/models/serverlist-model.js:
--------------------------------------------------------------------------------
1 | var ServerListModel = Backbone.Model.extend({
2 |
3 | url : "api/servers",
4 |
5 | initialize : function(){
6 |
7 | }
8 |
9 | })
10 |
--------------------------------------------------------------------------------
/src/www/js/models/top-commands-widget-model.js:
--------------------------------------------------------------------------------
1 | var TopCommandsWidgetModel = Backbone.Model.extend({
2 |
3 | url : "api/topcommands",
4 |
5 | initialize : function(){
6 |
7 | }
8 |
9 | })
10 |
--------------------------------------------------------------------------------
/src/www/js/models/top-keys-widget-model.js:
--------------------------------------------------------------------------------
1 | var TopKeysWidgetModel = Backbone.Model.extend({
2 |
3 | url : "api/topkeys",
4 |
5 | initialize : function(){
6 |
7 | }
8 |
9 | })
10 |
--------------------------------------------------------------------------------
/src/www/js/views/base-widget-view.js:
--------------------------------------------------------------------------------
1 | var BaseWidget = Backbone.View.extend({
2 |
3 | enableLogging : false
4 |
5 | , updateFrequency : 5000
6 |
7 | , Name : "BaseWidget"
8 |
9 | , server : ""
10 |
11 | , events : {
12 | "click .time-period" : "ChangeTimeFrame"
13 | , "click .go" : "Go"
14 | }
15 |
16 | , init : function () {
17 |
18 | var self = this
19 |
20 | $(document).on("ServerChange", function(e, server){
21 | self.server = server
22 | })
23 |
24 | this.timer = setInterval( function () { self.UpdateModel(true) }, this.updateFrequency )
25 |
26 | // set event listners
27 | this.model
28 | .on("error", this.error, this)
29 | .on("change", this.ModelChanged, this)
30 |
31 | }
32 |
33 | , UpdateModel : function ( enableTimer ) {
34 |
35 | clearInterval(this.timer)
36 |
37 | this.startTime = new Date()
38 |
39 | this.model.fetch({
40 | data : {
41 | // if no from/to are found, provide reasonable defaults of a week ago and now, respectively
42 | from : this.$el.find('[name=from]').val() || new Date(new Date() - 7*24*60*60000).toUTCString()
43 | , to : this.$el.find('[name=to]').val() || new Date().toUTCString()
44 | , server : this.server
45 | }
46 | })
47 |
48 | this.enableTimer = enableTimer
49 |
50 | }
51 |
52 | , ModelChanged : function(){
53 |
54 | this.endTime = new Date()
55 | var timeElapsed = (this.endTime - this.startTime);
56 |
57 | if (this.enableLogging)
58 | console.log(this.Name + ": Time Elapsed = " + timeElapsed + " ms")
59 |
60 | this.render()
61 |
62 | if(this.enableTimer)
63 | {
64 | var self = this
65 | this.timer = setInterval( function () { self.UpdateModel(true) }, this.updateFrequency )
66 | }
67 | }
68 |
69 | , Go : function( el ) {
70 | this.UpdateModel(false)
71 | }
72 |
73 | , ChangeTimeFrame : function ( el ) {
74 |
75 | var selectionType = $(el.target).data("type")
76 | , timeFrame = parseInt( $(el.target).data("time") )
77 |
78 | // update the dropdown's label
79 | $(el.target)
80 | .closest(".btn-group")
81 | .children()
82 | .first()
83 | .text($(el.target).text())
84 |
85 | // Custom time frame selected
86 | if ( selectionType == "custom" ) {
87 | $(el.target)
88 | .closest(".btn-group")
89 | .siblings(".date-control")
90 | .css("display","inline")
91 | }
92 | // real time
93 | else if ( selectionType == "realtime" ) {
94 | $(el.target)
95 | .closest(".btn-group")
96 | .siblings(".date-control")
97 | .css("display","none")
98 |
99 | var self = this
100 | this.$el.find('[name=from]').val("")
101 | this.$el.find('[name=to]').val("")
102 | this.timer = setInterval( function () { self.UpdateModel(true) }, this.updateFrequency )
103 | }
104 | // one of the template time frame selected
105 | // example: last 15mins, last 1 day etc
106 | else {
107 |
108 | $(el.target)
109 | .closest(".btn-group")
110 | .siblings(".date-control")
111 | .css("display","none")
112 |
113 | var endDate = new Date()
114 | , startDate = endDate
115 |
116 | switch(selectionType) {
117 |
118 | case 'minute' :
119 | startDate = new Date(endDate - timeFrame * 60000)
120 | break
121 |
122 | case 'hour' :
123 | startDate = new Date(endDate - timeFrame * 60*60000)
124 | break
125 |
126 | case 'day' :
127 | startDate = new Date(endDate - timeFrame * 24*60*60000)
128 | break
129 |
130 | case 'week' :
131 | startDate = new Date(endDate - timeFrame * 7*24*60*60000)
132 | break
133 |
134 | case 'month' :
135 | startDate = new Date(endDate - timeFrame * 30*24*60*60000)
136 | break
137 | }
138 |
139 | this.$el.find('[name=from]').val(this.ISODateString(startDate))
140 | this.$el.find('[name=to]').val(this.ISODateString(endDate))
141 | this.UpdateModel(false)
142 |
143 | }
144 | }
145 |
146 | , ISODateString : function ( d ) {
147 |
148 | function pad ( n ) {
149 | return n < 10 ? '0'+n : n
150 | }
151 |
152 | return d.getFullYear()+'-'
153 | + pad(d.getMonth()+1)+'-'
154 | + pad(d.getDate())+' '
155 | + pad(d.getHours())+':'
156 | + pad(d.getMinutes())+':'
157 | + pad(d.getSeconds())
158 | }
159 |
160 | , error: function ( model, error ) {
161 | if (this.enableLogging)
162 | console.log(this.Name + ": Error Occured \n" + error + "\n" + model )
163 |
164 | }
165 |
166 | })
167 |
--------------------------------------------------------------------------------
/src/www/js/views/commands-widget-view.js:
--------------------------------------------------------------------------------
1 | var CommandsWidget = BaseWidget.extend({
2 |
3 | initialize : function() {
4 |
5 | this.Name = "Commands Widget"
6 |
7 | this.init()
8 |
9 | // templates
10 | var templateSelector = "#commands-widget-template"
11 | , templateSource = $(templateSelector).html()
12 |
13 | this.template = Handlebars.compile(templateSource)
14 | this.$el.empty().html(this.template())
15 |
16 | // chart
17 | this.chart = new google.visualization.AreaChart($("#commands-widget-chart").empty().get(0))
18 | this.dataTable = new google.visualization.DataTable()
19 | this.dataTable.addColumn('datetime', 'datetime')
20 | this.dataTable.addColumn('number', 'Commands Processed')
21 | }
22 |
23 | , render : function() {
24 |
25 | var model = this.model.toJSON()
26 | , markUp = this.template(model)
27 | , self = this
28 |
29 | self.dataTable.removeRows(0,self.dataTable.getNumberOfRows())
30 |
31 | $.each(model.data, function(index, obj){
32 |
33 | // first item of the object contains datetime info
34 | // [ YYYY, MM, DD, HH, MM, SS ]
35 | var recordDate = new Date(obj[0][0], obj[0][1]-1, obj[0][2], obj[0][3], obj[0][4], obj[0][5])
36 |
37 | if(self.dataTable)
38 | self.dataTable.addRow( [recordDate, obj[1]] )
39 | })
40 |
41 | var pointSize = model.data.length > 120 ? 1 : 5
42 | , options = {
43 | title : ''
44 | , colors: [ '#17BECF', '#9EDAE5' ]
45 | , areaOpacity : .9
46 | , pointSize: pointSize
47 | , chartArea: { 'top' : 10, 'width' : '85%' }
48 | , width : "100%"
49 | , height : 200
50 | , animation : { duration : 500, easing: 'out' }
51 | , vAxis: { minValue : 0 }
52 | }
53 |
54 | this.chart.draw(this.dataTable, options)
55 | }
56 | })
--------------------------------------------------------------------------------
/src/www/js/views/info-widget-view.js:
--------------------------------------------------------------------------------
1 | /* Info Widget
2 | * ====================== */
3 |
4 | var InfoWidget = BaseWidget.extend({
5 |
6 | initialize : function() {
7 |
8 | this.Name = "Info Widget"
9 |
10 | this.init()
11 | this.updateFrequency = 5000 // every 5 seconds
12 |
13 | // templates
14 | var templateSource = $("#info-widget-template").html()
15 | , popOverTemplateSource = $("#popover-template").html()
16 | , infoTemplateSource = $("#info-template").html()
17 |
18 | this.template = Handlebars.compile(templateSource)
19 | this.popOverTemplate = Handlebars.compile(popOverTemplateSource)
20 | this.infoTemplate = Handlebars.compile(infoTemplateSource)
21 |
22 | }
23 |
24 | , render: function() {
25 |
26 | var model = this.model.toJSON()
27 | , markUp = this.template(model)
28 | , popoverMarkup = this.popOverTemplate(model.databases)
29 | , infoMarkup = this.infoTemplate(model)
30 |
31 | $(this.el).html(markUp)
32 |
33 | $('#total-keys').popover({
34 | "title" : "databases"
35 | , "content" : popoverMarkup
36 | })
37 |
38 | $('#misc-info').popover({
39 | "title" : "info"
40 | , "content" : infoMarkup
41 | , "placement" : "bottom"
42 | })
43 | }
44 |
45 | })
--------------------------------------------------------------------------------
/src/www/js/views/memory-widget-view.js:
--------------------------------------------------------------------------------
1 | var MemoryWidget = BaseWidget.extend({
2 |
3 | initialize : function() {
4 |
5 | this.Name = "Memory Widget"
6 |
7 | this.init()
8 |
9 | // templates
10 | var templateSelector = "#memory-widget-template"
11 | , templateSource = $(templateSelector).html()
12 |
13 | this.template = Handlebars.compile(templateSource)
14 | this.$el.empty().html(this.template())
15 |
16 | // chart
17 | this.chart = new google.visualization.LineChart($("#memory-widget-chart").empty().get(0))
18 | this.dataTable = new google.visualization.DataTable()
19 | this.dataTable.addColumn('datetime', 'datetime')
20 | this.dataTable.addColumn('number', 'Max')
21 | this.dataTable.addColumn('number', 'Current')
22 | }
23 |
24 | , render : function() {
25 |
26 | var model = this.model.toJSON()
27 | , markUp = this.template(model)
28 | , self = this
29 |
30 | self.dataTable.removeRows(0,self.dataTable.getNumberOfRows())
31 |
32 | $.each(model.data, function(index, obj){
33 |
34 | // first item of the object contains datetime info
35 | // [ YYYY, MM, DD, HH, MM, SS ]
36 | var recordDate = new Date(obj[0][0], obj[0][1]-1, obj[0][2], obj[0][3], obj[0][4], obj[0][5])
37 |
38 | if(self.dataTable)
39 | self.dataTable.addRow( [recordDate, obj[1], obj[2]] )
40 | })
41 |
42 | var pointSize = model.data.length > 120 ? 1 : 5
43 | , options = {
44 | title : ''
45 | , colors: [ '#1581AA', '#77BA44' ]
46 | , pointSize: pointSize
47 | , chartArea: { 'top' : 10, 'width' : '85%' }
48 | , width : "100%"
49 | , height : 200
50 | , animation : { duration : 500, easing : 'out' }
51 | }
52 |
53 | this.chart.draw(this.dataTable, options)
54 | }
55 | })
--------------------------------------------------------------------------------
/src/www/js/views/serverlist-view.js:
--------------------------------------------------------------------------------
1 | var ServerList = Backbone.View.extend({
2 |
3 | initialize : function() {
4 | this.$el.empty()
5 | this.model.on("change", this.render, this)
6 | this.$el.on("change", this.ServerChanged)
7 | this.model.fetch()
8 | }
9 |
10 | , ServerChanged : function(){
11 | $(document).trigger("ServerChange", $(this).val())
12 | }
13 |
14 | , render : function() {
15 | var model = this.model.toJSON()
16 | , self = this
17 |
18 | $.each(model.servers,function(index, obj){
19 | self.$el.append("")
20 | })
21 |
22 | self.$el.trigger("change")
23 | }
24 |
25 | })
--------------------------------------------------------------------------------
/src/www/js/views/top-commands-widget-view.js:
--------------------------------------------------------------------------------
1 | var TopCommandsWidget = BaseWidget.extend({
2 |
3 | initialize : function() {
4 |
5 | this.Name = "Top Commands Widget"
6 |
7 | this.init()
8 |
9 | // templates
10 | var templateSelector = "#top-commands-widget-template"
11 | , templateSource = $(templateSelector).html()
12 |
13 | this.template = Handlebars.compile(templateSource)
14 | this.$el.empty().html(this.template())
15 |
16 | // chart
17 | this.chart = new google.visualization.ColumnChart($("#top-commands-widget-chart").empty().get(0))
18 | this.dataTable = new google.visualization.DataTable()
19 | this.dataTable.addColumn('string', 'command')
20 | this.dataTable.addColumn('number', 'count')
21 |
22 | }
23 |
24 | , render : function() {
25 |
26 | var model = this.model.toJSON()
27 | , markUp = this.template(model)
28 | , self = this
29 |
30 | this.dataTable.removeRows(0, this.dataTable.getNumberOfRows())
31 |
32 | this.dataTable.addRows(model.data)
33 |
34 | //https://developers.google.com/chart/interactive/docs/gallery/columnchart#Configuration_Options
35 | var options = {
36 | title : ''
37 | , colors : ['#006B9F', '#008FD5', '#454545', '#E70B20' ]
38 | , chartArea: { 'left' : 100, 'top' : 10, 'width': '90%', 'height': '200' }
39 | , height: 250
40 | , animation: { duration : 500, easing : 'linear' }
41 | , legend: { position: 'none' }
42 | }
43 |
44 | this.chart.draw(this.dataTable, options)
45 | }
46 | })
--------------------------------------------------------------------------------
/src/www/js/views/top-keys-widget-view.js:
--------------------------------------------------------------------------------
1 | var TopKeysWidget = BaseWidget.extend({
2 |
3 | initialize : function() {
4 |
5 | this.Name = "Top Keys Widget"
6 |
7 | this.init()
8 |
9 | // templates
10 | var templateSelector = "#top-keys-widget-template"
11 | , templateSource = $(templateSelector).html()
12 |
13 | this.template = Handlebars.compile(templateSource)
14 | this.$el.empty().html(this.template())
15 |
16 | // chart
17 | this.chart = new google.visualization.ColumnChart($("#top-keys-widget-chart").empty().get(0))
18 | this.dataTable = new google.visualization.DataTable()
19 |
20 | }
21 |
22 | , render : function() {
23 |
24 | var model = this.model.toJSON()
25 | , markUp = this.template(model)
26 | , self = this
27 |
28 | this.dataTable.removeRows( 0, this.dataTable.getNumberOfRows() )
29 | this.dataTable.removeColumns(0, this.dataTable.getNumberOfColumns())
30 |
31 | this.dataTable.addColumn('string', 'key')
32 | this.dataTable.addColumn('number', 'count')
33 | this.dataTable.addRows(model.data)
34 |
35 | //https://developers.google.com/chart/interactive/docs/gallery/columnchart#Configuration_Options
36 | var options = {
37 | title : ''
38 | , colors : [ '#008FD5', '#006B9F', '#454545', '#E70B20' ]
39 | , chartArea: { 'left' : 100, 'top' : 10, 'width': '90%', 'height': '200' }
40 | , height : 250
41 | , animation: { duration : 500, easing : 'linear' }
42 | , legend: { position: 'none' }
43 | }
44 |
45 | this.chart.draw(this.dataTable, options)
46 | }
47 | })
--------------------------------------------------------------------------------