├── .gitignore ├── MIT-LICENSE.txt ├── README.md ├── design ├── overview.png └── redis-live.png ├── install └── centos │ ├── install-redis-monitor.sh │ ├── redis-monitor │ └── redis7009.conf ├── requirements.txt ├── specification_ch.txt └── src ├── __init__.py ├── api ├── __init__.py ├── controller │ ├── BaseController.py │ ├── BaseStaticFileHandler.py │ ├── CommandsController.py │ ├── InfoController.py │ ├── InfoListController.py │ ├── MemoryController.py │ ├── ServerListController.py │ ├── SettingsController.py │ ├── SlowlogController.py │ ├── StatusController.py │ ├── TopCommandsController.py │ ├── TopKeysController.py │ └── __init__.py └── util │ ├── RDP.py │ ├── __init__.py │ └── settings.py ├── daemonized.py ├── dataprovider ├── __init__.py ├── dataprovider.py └── redisprovider.py ├── redis_live.conf ├── redis_live.py ├── redis_live_daemon.py ├── redis_monitor.py ├── redis_monitor_daemon.py └── www ├── images └── logo.png ├── index.html ├── js ├── app.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 │ ├── corechart.js │ ├── google.js │ ├── handlebars │ │ └── handlebars-1.0.0.beta.6.js │ ├── jquery │ │ └── jquery-1.7.2.min.js │ ├── jsapi.js │ ├── less │ │ └── less-1.3.0.min.js │ ├── tooltip.css │ └── underscore │ │ └── underscore-min.js ├── models │ ├── commands-widget-model.js │ ├── info-widget-model.js │ ├── memory-widget-model.js │ ├── serverlist-model.js │ ├── status-widget-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 │ ├── status-widget-view.js │ ├── top-commands-widget-view.js │ └── top-keys-widget-view.js ├── overview.html ├── settings.html └── static ├── css ├── reset.css └── style.css └── js └── jquery.min.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 -------------------------------------------------------------------------------- /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 | NOTICE: This repository is no longer maintained, maybe some compatibility issue exists. 2 | 3 | redis-monitor 4 | --------- 5 | 6 | Base [RedisLive](https://github.com/nkrode/RedisLive) 7 | 8 | ## Features: 9 | * cluster: support thousands of redis instances 10 | * light: redis info base 11 | * metrics: memory, comands, Key HitRate, keyspace, master-slave change, expire keys 12 | * notification API: crash, master-slave stats changed notify 13 | 14 | ## Configuration 15 | vim src/redis_live.conf 16 | 17 | config: 18 | 19 | - RedisStatsServer: stats storage backend(redis) 20 | - others: config on dashboard settings tab 21 | 22 | samples: 23 | ``` 24 | {"master_slave_sms": "1,1", 25 | "RedisStatsServer": {"port": 6379, "server": "127.0.0.1"}, 26 | "sms_alert": "192.168.110.207:9999", 27 | "DataStoreType": "redis", 28 | "RedisServers": [ 29 | {"instance": "Master1", "group": "Test1", "port": 6379, "server": "127.0.0.1"}, 30 | {"instance": "Slave1", "group": "Test1", "port": 6380, "server": "127.0.0.1"} 31 | ]} 32 | 33 | ``` 34 | 35 | ## Install Deps 36 | pip install -r requirements.txt 37 | 38 | ## Run 39 | # 1. start redis instance for stat stroage 40 | redis-server --port 6379 41 | 42 | # 2. start web portal 43 | cd src/ 44 | python redis_live.py 45 | 46 | # 3. start stats collector daemon process 47 | cd src/ 48 | python redis_monitor.py 49 | 50 | # 4. dashboard: http://127.0.0.1:8888/index.html 51 | 52 | ## overview 53 |  54 |  55 | 56 | -------------------------------------------------------------------------------- /design/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/design/overview.png -------------------------------------------------------------------------------- /design/redis-live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/design/redis-live.png -------------------------------------------------------------------------------- /install/centos/install-redis-monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p /usr/local/redis-monitor 4 | \cp -rf ../../* /usr/local/redis-monitor 5 | \cp -rf redis7009.conf /etc/redis/ 6 | \cp -rf redis-monitor /etc/init.d/ 7 | chmod 777 /etc/init.d/redis-monitor 8 | 9 | chkconfig --add redis-monitor 10 | service redis-monitor start 11 | 12 | -------------------------------------------------------------------------------- /install/centos/redis-monitor: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #chkconfig: 35 90 10 3 | #description: redis-monitor 4 | 5 | monitor_start() { 6 | monitor_stopmonitor 7 | /usr/local/bin/redis-server /etc/redis/redis7009.conf 8 | python /usr/local/redis-monitor/redis_monitor_daemon.py 9 | python /usr/local/redis-monitor/redis_live_daemon.py 10 | } 11 | 12 | monitor_stop() { 13 | monitor_stopmonitor 14 | monitor_stoplive 15 | /usr/local/bin/redis-cli -p 7009 SHUTDOWN 16 | } 17 | monitor_stopmonitor(){ 18 | MPID=`ps axu|grep "redis_monitor_daemon.py"|grep -v grep|awk '{ print $2}'` 19 | if [ "$MPID" != "" ]; 20 | then 21 | echo "kill monitor ( pid =" $MPID ")" 22 | kill -s KILL $MPID 23 | fi 24 | } 25 | monitor_stoplive(){ 26 | MPID=`ps axu|grep "redis_live_daemon.py"|grep -v grep|awk '{ print $2}'` 27 | if [ "$MPID" != "" ]; 28 | then 29 | echo "kill live ( pid =" $MPID ")" 30 | kill -s KILL $MPID 31 | fi 32 | } 33 | monitor_remon(){ 34 | monitor_stopmonitor 35 | python /usr/local/redis-monitor/redis_monitor_daemon.py 36 | } 37 | monitor_relive(){ 38 | monitor_stoplive 39 | python /usr/local/redis-monitor/redis_live_daemon.py 40 | } 41 | monitor_restart(){ 42 | monitor_stop 43 | monitor_start 44 | } 45 | monitor_usage() { 46 | echo -e "Usage: $0 {start,stop,restart,relive,remon}" 47 | exit 1 48 | } 49 | 50 | case "$1" in 51 | start) monitor_start ;; 52 | stop) monitor_stop ;; 53 | restart) monitor_restart ;; 54 | relive) monitor_relive ;; 55 | remon) monitor_remon ;; 56 | *) monitor_usage ;; 57 | esac 58 | -------------------------------------------------------------------------------- /install/centos/redis7009.conf: -------------------------------------------------------------------------------- 1 | pidfile /var/run/redis7009.pid 2 | port 7009 3 | dir ./ 4 | dbfilename dump7009.rdb 5 | appendfilename appendonly7009.aof 6 | logfile /var/log/redis7009.log 7 | 8 | timeout 300 9 | loglevel notice 10 | databases 16 11 | save "" 12 | daemonize yes 13 | stop-writes-on-bgsave-error yes 14 | rdbcompression no 15 | rdbchecksum yes 16 | maxclients 1000 17 | # 2G 18 | maxmemory 2147483648 19 | maxmemory-policy allkeys-lru 20 | maxmemory-samples 5 21 | appendonly yes 22 | appendfsync everysec 23 | no-appendfsync-on-rewrite no 24 | auto-aof-rewrite-percentage 0 25 | auto-aof-rewrite-min-size 64mb 26 | lua-time-limit 5000 27 | #10 ms 28 | slowlog-log-slower-than 10000 29 | slowlog-max-len 128 30 | hash-max-ziplist-entries 512 31 | hash-max-ziplist-value 64 32 | list-max-ziplist-entries 512 33 | list-max-ziplist-value 64 34 | set-max-intset-entries 512 35 | zset-max-ziplist-entries 128 36 | zset-max-ziplist-value 64 37 | activerehashing yes 38 | client-output-buffer-limit normal 0 0 0 39 | client-output-buffer-limit slave 1024mb 1024mb 60 40 | client-output-buffer-limit pubsub 32mb 8mb 60 41 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-dateutil 2 | redis 3 | tornado 4 | argparse 5 | -------------------------------------------------------------------------------- /specification_ch.txt: -------------------------------------------------------------------------------- 1 | 2 | 一、Monitor 3 | 1. 为每个redis实例开启一个定时线程,用于收集Info数据,并存储如下: 4 | - Info: 但获取失败是保存为 NULL值,并提取如下数据单独存储,便于显示 5 | - Memory :peak,current 6 | - command: 两次差值计算结果 7 | - Status : down、Master(detail)、slave(detail),并只有在两次变更时记录 8 | - Hit rate 9 | - keys、expires 10 | - 过期数据 expired、evicted 11 | 12 | 2.数据量评估 13 | - 如每2s取一次,那么每小时数据:1200,一次Info数据1.5K;那么一个实例1小时1.8M,那么20个实例一天产生数据:860M。 14 | - Info全量数据还是不准备全量保存一份,无论redis内存占用,还是使用sqllite,压力都大。 15 | - 那么不缓存Info,其他数据一次约50字节,那么一天下来:30M;设计最长保存7天;由Monitor定时检查。 16 | - redis采用ZSET保存,使用UNIX 时间戳为score,数据内容使用二进制保存。 17 | 18 | 二、Web界面: 19 | Overview + Live 20 | 21 | 三、使用方式 22 | - redis-live.conf 为json格式配置, 收集到的数据存储目前只能使用redis 23 | 24 | - 启动: 25 | 两个进程: redis-monitor.py 为定时收集进程; redis-live.py 是站点,端口为:8888 26 | 27 | 安装依赖: 28 | python2.7 29 | tornado 30 | redis-py 31 | python-dateutil-2.1 32 | jinja2 33 | werkzeug 34 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/src/__init__.py -------------------------------------------------------------------------------- /src/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/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 | import redis 6 | 7 | class BaseController(tornado.web.RequestHandler): 8 | 9 | stats_provider = RedisLiveDataProvider.get_provider() 10 | 11 | def getStatsPerServer(self, server, password=None): 12 | try: 13 | connection = redis.Redis(host=server[0], port=(int)(server[1]), db=0, password=password, socket_timeout=0.1) 14 | info = connection.info() 15 | # when instances down ,this maybe slowly... 16 | info.update({ 17 | "server_name" : server, 18 | "status" : info.get("role"), 19 | "last_save_humanized": info.get("last_save_time") 20 | }) 21 | 22 | #master status 23 | role = info["role"] 24 | slaves="" 25 | 26 | if(role == "master"): 27 | connected_slaves = (int)(info["connected_slaves"]) 28 | slaves = "" 29 | for i in range(0, connected_slaves): 30 | slaves += str(info["slave" + (str)(i)]) 31 | else: 32 | master_host = info["master_host"] 33 | master_port = (str)(info["master_port"]) 34 | master_link_status = info["master_link_status"] 35 | master_sync_in_progress = info["master_sync_in_progress"] 36 | if(master_host!=""): 37 | slaves=master_host+":"+(str)(master_port)+","+master_link_status 38 | if(master_sync_in_progress==1): 39 | slaves+=",syncing" 40 | info['master_slaves']=slaves 41 | 42 | 43 | except redis.exceptions.ConnectionError: 44 | info = { 45 | "role" :"down", 46 | "uptime_in_seconds" :0, 47 | "total_commands_processed":0, 48 | "used_memory_human" :"", 49 | "connected_clients" :"", 50 | "status" : "down", 51 | "server_name" : server, 52 | "connected_clients" : 0, 53 | "used_memory_human" : '?', 54 | } 55 | 56 | return info 57 | 58 | def datetime_to_list(self, datetime): 59 | """Converts a datetime to a list. 60 | 61 | Args: 62 | datetime (datetime): The datetime to convert. 63 | """ 64 | parsed_date = dateutil.parser.parse(datetime) 65 | # don't return the last two fields, we don't want them. 66 | return tuple(parsed_date.timetuple())[:-2] 67 | 68 | # todo : fix this 69 | def average_data(self, data): 70 | """Averages data. 71 | 72 | TODO: More docstring here, once functionality is understood. 73 | """ 74 | average = [] 75 | 76 | deviation = 1024 * 1024 77 | 78 | start = dateutil.parser.parse(data[0][0]) 79 | end = dateutil.parser.parse(data[-1][0]) 80 | difference = end - start 81 | weeks, days = divmod(difference.days, 7) 82 | minutes, seconds = divmod(difference.seconds, 60) 83 | hours, minutes = divmod(minutes, 60) 84 | 85 | # TODO: These if/elif/else branches chould probably be broken out into 86 | # individual functions to make it easier to follow what's going on. 87 | if difference.days > 0: 88 | current_max = 0 89 | current_current = 0 90 | current_d = 0 91 | 92 | for dt, max_memory, current_memory in data: 93 | d = dateutil.parser.parse(dt) 94 | if d.day != current_d: 95 | current_d = d.day 96 | average.append([dt, max_memory, current_memory]) 97 | current_max = max_memory 98 | current_current = current_memory 99 | else: 100 | if max_memory > current_max or \ 101 | current_memory > current_current: 102 | average.pop() 103 | average.append([dt, max_memory, current_memory]) 104 | current_max = max_memory 105 | current_current = current_memory 106 | elif hours > 0: 107 | current_max = 0 108 | current_current = 0 109 | current = -1 110 | keep_flag = False 111 | 112 | for dt, max_memory, current_memory in data: 113 | d = dateutil.parser.parse(dt) 114 | if d.hour != current: 115 | current = d.hour 116 | average.append([dt, max_memory, current_memory]) 117 | current_max = max_memory 118 | current_current = current_memory 119 | keep_flag = False 120 | elif abs(max_memory - current_max) > deviation or \ 121 | abs(current_memory - current_current) > deviation: 122 | # average.pop() 123 | average.append([dt, max_memory, current_memory]) 124 | current_max = max_memory 125 | current_current = current_memory 126 | keep_flag = True 127 | elif max_memory > current_max or \ 128 | current_memory > current_current: 129 | if keep_flag != True: 130 | average.pop() 131 | average.append([dt, max_memory, current_memory]) 132 | current_max = max_memory 133 | current_current = current_memory 134 | keep_flag = False 135 | else: 136 | current_max = 0 137 | current_current = 0 138 | current_m = -1 139 | keep_flag = False 140 | for dt, max_memory, current_memory in data: 141 | d = dateutil.parser.parse(dt) 142 | if d.minute != current_m: 143 | current_m = d.minute 144 | average.append([dt, max_memory, current_memory]) 145 | current_max = max_memory 146 | current_current = current_memory 147 | keep_flag = False 148 | elif abs(max_memory - current_max) > deviation or \ 149 | abs(current_memory - current_current) > deviation: 150 | # average.pop() 151 | average.append([dt, max_memory, current_memory]) 152 | current_max = max_memory 153 | current_current = current_memory 154 | keep_flag = True 155 | elif max_memory > current_max or \ 156 | current_memory > current_current: 157 | if keep_flag != True: 158 | average.pop() 159 | average.append([dt, max_memory, current_memory]) 160 | current_max = max_memory 161 | current_current = current_memory 162 | keep_flag = False 163 | 164 | return average 165 | -------------------------------------------------------------------------------- /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 | import datetime 6 | 7 | 8 | class CommandsController(BaseController): 9 | 10 | def get(self): 11 | """Serves a GET request. 12 | """ 13 | return_data = dict(data=[], 14 | timestamp=datetime.datetime.now().isoformat()) 15 | 16 | server = self.get_argument("server") 17 | from_date = self.get_argument("from", None) 18 | to_date = self.get_argument("to", None) 19 | 20 | if from_date==None or to_date==None or len(from_date)==0: 21 | end = datetime.datetime.now() 22 | delta = datetime.timedelta(seconds=900) 23 | start = end - delta 24 | else: 25 | start = dateutil.parser.parse(from_date) 26 | end = dateutil.parser.parse(to_date) 27 | 28 | data = self.stats_provider.get_keys_info(server, start, end) 29 | 30 | return_data['data']=data 31 | 32 | self.write(return_data) 33 | -------------------------------------------------------------------------------- /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 | import redis 7 | from api.util import settings 8 | 9 | class InfoController(BaseController): 10 | def get(self): 11 | """Serves a GET request. 12 | """ 13 | 14 | server = self.get_argument("server").split(':') 15 | 16 | password = None 17 | 18 | for redis_server in settings.get_redis_servers(): 19 | if (redis_server["server"] == server[0]) and (int(redis_server["port"]) == int(server[1])): 20 | password = redis_server["password"] 21 | break 22 | 23 | redis_info = self.getStatsPerServer(server, password) 24 | databases=[] 25 | 26 | for key in sorted(redis_info.keys()): 27 | if key.startswith("db"): 28 | database = redis_info[key] 29 | database['name']=key 30 | databases.append(database) 31 | 32 | total_keys=0 33 | for database in databases: 34 | total_keys+=database.get("keys") 35 | 36 | if(total_keys==0): 37 | databases=[{"name" : "db0", "keys" : "0", "expires" : "0"}] 38 | 39 | redis_info['databases'] = databases 40 | redis_info['total_keys']= self.shorten_number(total_keys) 41 | 42 | uptime_seconds = redis_info['uptime_in_seconds'] 43 | redis_info['uptime'] = self.shorten_time(uptime_seconds) 44 | 45 | commands_processed = redis_info['total_commands_processed'] 46 | commands_processed = self.shorten_number(commands_processed) 47 | redis_info['total_commands_processed_human'] = commands_processed 48 | 49 | self.write(redis_info) 50 | 51 | def shorten_time(self, seconds): 52 | """Takes an integer number of seconds and rounds it to a human readable 53 | format. 54 | 55 | Args: 56 | seconds (int): Number of seconds to convert. 57 | """ 58 | if seconds < 60: 59 | # less than 1 minute 60 | val = str(seconds) + " sec" 61 | elif seconds < 3600: 62 | # if the seconds is less than 1hr 63 | num = self.rounded_number(seconds, 60) 64 | if num == "60": 65 | val = '1h' 66 | else: 67 | val = num + "m" 68 | elif (seconds < 60*60*24): 69 | # if the number is less than 1 day 70 | num = self.rounded_number(seconds, 60 * 60) 71 | if num == "24": 72 | val = "1d" 73 | else: 74 | val = num + "h" 75 | else: 76 | num = self.rounded_number(seconds, 60*60*24) 77 | val = num + "d" 78 | 79 | return val 80 | 81 | def shorten_number(self, number): 82 | """Shortens a number to a human readable format. 83 | 84 | Args: 85 | number (int): Number to convert. 86 | """ 87 | if number < 1000: 88 | return number 89 | elif number >= 1000 and number < 1000000: 90 | num = self.rounded_number(number, 1000) 91 | val = "1M" if num == "1000" else num + "K" 92 | return val 93 | elif number >= 1000000 and number < 1000000000: 94 | num = self.rounded_number(number, 1000000) 95 | val = "1B" if num=="1000" else num + "M" 96 | return val 97 | elif number >= 1000000000 and number < 1000000000000: 98 | num = self.rounded_number(number, 1000000000) 99 | val = "1T" if num=="1000" else num + "B" 100 | return val 101 | else: 102 | num = self.rounded_number(number, 1000000000000) 103 | return num + "T" 104 | 105 | def rounded_number(self, number, denominator): 106 | """Rounds a number. 107 | 108 | Args: 109 | number (int|float): The number to round. 110 | denominator (int): The denominator. 111 | """ 112 | rounded = str(round(Decimal(number)/Decimal(denominator), 1)) 113 | replace_trailing_zero = re.compile('0$') 114 | no_trailing_zeros = replace_trailing_zero.sub('', rounded) 115 | replace_trailing_period = re.compile('\.$') 116 | final_number = replace_trailing_period.sub('', no_trailing_zeros) 117 | return final_number 118 | -------------------------------------------------------------------------------- /src/api/controller/InfoListController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | from api.util import settings 3 | import redis 4 | 5 | class InfoListController(BaseController): 6 | 7 | def get(self): 8 | group = self.get_argument("group", None) 9 | 10 | response = {} 11 | response['data']=[] 12 | for server in settings.get_redis_servers(): 13 | if(group !=None and group!='all' and server['group'] != group): 14 | continue; 15 | 16 | info=self.getStatsPerServer((server['server'], server['port']), server['password']) 17 | 18 | info.update({"addr" : info.get("server_name")[0].replace(".", "_") + str(info.get("server_name")[1]), 19 | }) 20 | info['show_name']=server['group']+'('+server['instance']+')' 21 | info['group']= server['group'] 22 | screen_strategy = 'normal' 23 | if info.get("status") == 'down': 24 | screen_strategy = 'hidden' 25 | 26 | info.update({ "screen_strategy": screen_strategy,}) 27 | 28 | response["data"].append(info) 29 | 30 | self.write(response) 31 | -------------------------------------------------------------------------------- /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 from_date==None or to_date==None: 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 | server_list = [] 12 | redis_servers = settings.get_redis_servers() 13 | 14 | for server in redis_servers: 15 | server['id']= "%(server)s:%(port)s" % server 16 | server_list.append(server) 17 | 18 | return server_list 19 | -------------------------------------------------------------------------------- /src/api/controller/SettingsController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | from api.util import settings 3 | import os 4 | 5 | class SettingsController(BaseController): 6 | 7 | def get(self): 8 | server_list="" 9 | for server in settings.get_redis_servers(): 10 | server_list+= "%(server)s:%(port)s %(group)s %(instance)s\r\n" % server 11 | 12 | sms_repl=0; 13 | sms_stats=0; 14 | try: 15 | sms=settings.get_master_slave_sms_type() 16 | sms=sms.split(',') 17 | sms_repl=(int)(sms[0]) 18 | sms_stats=(int)(sms[1]) 19 | except: 20 | pass 21 | 22 | servers = {"servers": server_list,"sms1":sms_repl,"sms2":sms_stats} 23 | self.write(servers) 24 | 25 | def post(self): 26 | try: 27 | server_list=self.get_argument("servers") 28 | sms1=(int)(self.get_argument("sms1")) 29 | sms2=(int)(self.get_argument("sms2")) 30 | sms= "%s,%s" %(sms1,sms2) 31 | 32 | servers=[] 33 | for server in server_list.split('\n'): 34 | eps=server.split(':') 35 | if(len(eps)!=2): 36 | raise Exception('server Ip format error.'); 37 | 38 | ip=eps[0] 39 | eps2 = eps[1].split(' ') 40 | port=(int)(eps2[0]) 41 | group='' 42 | instance='' 43 | 44 | if(len(eps2)>1): 45 | group=eps2[1] 46 | if(len(eps2)>2): 47 | instance=eps2[2] 48 | 49 | servers.append({'server':ip,'port':port,'group':group,'instance':instance}) 50 | settings.save_settings(servers, sms) 51 | self.write({"status":200}) 52 | except Exception,ex: 53 | self.write({"status":500,"error":ex.message}) -------------------------------------------------------------------------------- /src/api/controller/SlowlogController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | import datetime 3 | import redis 4 | 5 | class SlowlogController(BaseController): 6 | 7 | def get(self): 8 | data={} 9 | data['data']=[] 10 | server = self.get_argument("server").split(':') 11 | connection = redis.Redis(host=server[0], port=(int)(server[1]), db=0,socket_timeout=1) 12 | logs = connection.execute_command('slowlog','get','128') 13 | for lid,timeticks,run_micro,commands in logs: 14 | timestamp = datetime.datetime.fromtimestamp(int(timeticks)) 15 | cmd=' '.join(commands) 16 | data['data'].append({'id':lid,'time':str(timestamp),'escapeMs':(float)(run_micro)/1000,'cmd':cmd}) 17 | self.write(data) -------------------------------------------------------------------------------- /src/api/controller/StatusController.py: -------------------------------------------------------------------------------- 1 | from BaseController import BaseController 2 | import tornado.ioloop 3 | import tornado.web 4 | import dateutil.parser 5 | import datetime 6 | 7 | class StatusController(BaseController): 8 | 9 | def get(self): 10 | return_data = {} 11 | return_data['data']=[] 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 from_date == None or to_date == None or len(from_date) == 0: 18 | end = datetime.datetime.now() 19 | delta = datetime.timedelta(seconds=300) 20 | start = end - delta 21 | else: 22 | start = dateutil.parser.parse(from_date) 23 | end = dateutil.parser.parse(to_date) 24 | 25 | data = self.stats_provider.get_status_info(server, start, end) 26 | 27 | for item in data: 28 | row=item[1] 29 | timestamp = datetime.datetime.fromtimestamp(int(row['timestamp'])) 30 | row['time']= timestamp.strftime('%Y-%m-%d %H:%M:%S') 31 | return_data['data'].append(row) 32 | 33 | self.write(return_data) 34 | -------------------------------------------------------------------------------- /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 from_date==None or to_date==None: 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 from_date==None or to_date==None: 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/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/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/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/src/api/util/__init__.py -------------------------------------------------------------------------------- /src/api/util/settings.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | def get_settings(): 5 | return json.load(open(os.path.abspath('.')+ "/redis_live.conf")) 6 | 7 | def get_redis_servers(): 8 | config = get_settings() 9 | servers= config["RedisServers"] 10 | data=[] 11 | for server in servers: 12 | server['ep']='%(server)s:%(port)d' % server 13 | if(server.get('group')==None or server.get('group')==''): 14 | server['group']='ungrouped' 15 | if(server.get('instance')==None or server.get('instance')==''): 16 | server['instance']=(str)(server['port']) 17 | data.append(server) 18 | return data 19 | 20 | def get_redis_alerturi(): 21 | config = get_settings() 22 | return config["sms_alert"] 23 | 24 | def get_redis_stats_server(): 25 | config = get_settings() 26 | return config["RedisStatsServer"] 27 | 28 | def get_data_store_type(): 29 | config = get_settings() 30 | return config["DataStoreType"] 31 | 32 | def get_master_slave_sms_type(): 33 | config = get_settings() 34 | return config['master_slave_sms'] 35 | 36 | def save_settings(redisServers,smsType): 37 | config = get_settings() 38 | config["RedisServers"]= redisServers; 39 | config['master_slave_sms']=smsType; 40 | 41 | data = json.dumps(config) 42 | data = data.replace('}', '}\r\n') 43 | output = open(os.path.abspath('.') + "/redis_live.conf", "w") 44 | output.truncate() 45 | output.write(data) 46 | output.close() 47 | -------------------------------------------------------------------------------- /src/daemonized.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | class daemonized(object): 5 | def __init__(self): 6 | pass 7 | 8 | def daemonize(self): 9 | pass 10 | 11 | try: 12 | pid = os.fork() 13 | if pid > 0: 14 | sys.exit(0) 15 | except OSError,e: 16 | sys.stderr.write("Fork 1 has failed --> %d--[%s]\n" \ 17 | % (e.errno,e.strerror)) 18 | sys.exit(1) 19 | 20 | #os.chdir('/') 21 | #detach from terminal 22 | os.setsid() 23 | #file to be created? 24 | os.umask(0) 25 | 26 | try: 27 | pid = os.fork() 28 | if pid > 0: 29 | print "Daemon process pid %d" % pid 30 | sys.exit(0) 31 | except OSError, e: 32 | sys.stderr.write("Fork 2 has failed --> %d--[%s]" \ 33 | % (e.errno, e.strerror)) 34 | sys.exit(1) 35 | 36 | sys.stdout.flush() 37 | sys.stderr.flush() 38 | if sys.platform != 'darwin': # This block breaks on OS X 39 | # Redirect standard file descriptors 40 | sys.stdout.flush() 41 | sys.stderr.flush() 42 | si = file( os.devnull, 'r') 43 | so = file( os.devnull, 'a+') 44 | se = file( os.devnull, 'a+', 0) 45 | 46 | os.dup2(si.fileno(), sys.stdin.fileno()) 47 | os.dup2(so.fileno(), sys.stdout.fileno()) 48 | os.dup2(se.fileno(), sys.stderr.fileno()) 49 | 50 | def start_daemon(self): 51 | self.daemonize() 52 | 53 | self.run_daemon() 54 | 55 | def start(self): 56 | self.run_daemon() 57 | 58 | def run_daemon(self): 59 | '''override''' 60 | pass 61 | 62 | -------------------------------------------------------------------------------- /src/dataprovider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/src/dataprovider/__init__.py -------------------------------------------------------------------------------- /src/dataprovider/dataprovider.py: -------------------------------------------------------------------------------- 1 | from api.util import settings 2 | import redisprovider 3 | 4 | 5 | # TODO: Confirm there's not some implementation detail I've missed, then 6 | # ditch the classes here. 7 | class RedisLiveDataProvider(object): 8 | 9 | @staticmethod 10 | def get_provider(): 11 | """Returns a data provider based on the settings file. 12 | 13 | Valid providers are currently Redis and SQLite. 14 | """ 15 | data_store_type = settings.get_data_store_type() 16 | 17 | # FIXME: Should use a global variable for "redis" here. 18 | if data_store_type == "redis": 19 | return redisprovider.RedisStatsProvider() -------------------------------------------------------------------------------- /src/dataprovider/redisprovider.py: -------------------------------------------------------------------------------- 1 | from api.util import settings 2 | from datetime import datetime, timedelta 3 | import redis 4 | import json 5 | import ast 6 | import time 7 | import struct 8 | 9 | def datetime2_unix_int(timestamp): 10 | return (int)(time.mktime(timestamp.timetuple())) 11 | 12 | class RedisStatsProvider(object): 13 | def __init__(self): 14 | stats_server = settings.get_redis_stats_server() 15 | self.server = stats_server["server"] 16 | self.port = stats_server["port"] 17 | self.password = stats_server["password"] 18 | self.conn = redis.StrictRedis(host=self.server, port=self.port, db=0, password=self.password) 19 | 20 | def save_keys_Info(self, server,rediskey,timestamp, expires, persists,expired,evicted 21 | ,hit_rate,commands,used,peak): 22 | score=datetime2_unix_int(timestamp) 23 | data=struct.pack('iiiiiiiqq', 24 | score, 25 | commands, 26 | expires, 27 | persists, 28 | expired, 29 | evicted, 30 | hit_rate, 31 | peak, 32 | used) 33 | self.conn.zadd(server +':'+ rediskey, score, data) 34 | 35 | def get_keys_info(self, server, from_date, to_date): 36 | data = [] 37 | start = datetime2_unix_int(from_date) 38 | end = datetime2_unix_int(to_date) 39 | key=server + ":info" 40 | if(end-start>=3600*2): 41 | key=key+"_hours" 42 | rows = self.conn.zrangebyscore(key, start, end) 43 | 44 | rate=1 45 | if(len(rows)> 400): 46 | rate=len(rows)/300 47 | 48 | index=0 49 | for row in rows: 50 | index+=1 51 | if(index%rate==0): 52 | row=struct.unpack('iiiiiiiqq',row) 53 | timestamp = datetime.fromtimestamp(int(row[0])) 54 | item=list(row) 55 | item[0]=tuple(timestamp.timetuple())[:-2] 56 | 57 | data.append(item) 58 | return data 59 | 60 | def save_status_info(self, server, timestamp, data): 61 | timestamp=datetime2_unix_int(timestamp) 62 | data['timestamp']=timestamp 63 | self.conn.zadd(server + ":status", timestamp, json.dumps(data)) 64 | 65 | def get_status_info(self, server, from_date, to_date): 66 | data = [] 67 | start = datetime2_unix_int(from_date) 68 | end = datetime2_unix_int(to_date) 69 | rows = self.conn.zrangebyscore(server + ":status", start, end) 70 | for row in rows: 71 | row = ast.literal_eval(row) 72 | timestamp = datetime.fromtimestamp(int(row['timestamp'])) 73 | data.append([tuple(timestamp.timetuple())[:-2], row]) 74 | return data 75 | 76 | def delete_history(self,server,timestamp): 77 | begin=0 78 | end = datetime2_unix_int(timestamp) 79 | self.conn.zremrangebyscore(server + ":info", begin, end) 80 | self.conn.zremrangebyscore(server + ":info_hours", begin, end-(3600*24*7)) 81 | # status for more then 3 month 82 | self.conn.zremrangebyscore(server + ":status", begin, end - (3600*24*90)) 83 | 84 | def collection_database(self): 85 | self.conn.bgrewriteaof() 86 | -------------------------------------------------------------------------------- /src/redis_live.conf: -------------------------------------------------------------------------------- 1 | { 2 | "master_slave_sms": "1,1", 3 | "RedisStatsServer": { 4 | "port": 6379, 5 | "server": "127.0.0.1", 6 | "password": "XXXXXXXXX" 7 | }, 8 | "sms_alert": "192.168.110.207:9999", 9 | "DataStoreType": "redis", 10 | "RedisServers": [{ 11 | "instance": "Master1", 12 | "group": "Test1", 13 | "port": 6379, 14 | "server": "127.0.0.1" 15 | }, { 16 | "instance": "Slave1", 17 | "group": "Test1", 18 | "port": 6380, 19 | "server": "127.0.0.1", 20 | "password": "XXXXXXXXX" 21 | }] 22 | } 23 | -------------------------------------------------------------------------------- /src/redis_live.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 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.CommandsController import CommandsController 13 | from api.controller.InfoListController import InfoListController 14 | from api.controller.StatusController import StatusController 15 | from api.controller.SettingsController import SettingsController 16 | from api.controller.SlowlogController import SlowlogController 17 | from daemonized import daemonized 18 | 19 | class redis_live(daemonized): 20 | def run_daemon(self): 21 | 22 | define("port", default=8888, help="run on the given port", type=int) 23 | define("debug", default=0, help="debug mode", type=int) 24 | tornado.options.parse_command_line() 25 | 26 | print os.path.abspath('.') 27 | # Bootup 28 | handlers = [ 29 | (r"/api/servers", ServerListController), 30 | (r"/api/info", InfoController), 31 | (r"/api/status", StatusController), 32 | (r"/api/infolist",InfoListController), 33 | (r"/api/commands", CommandsController), 34 | (r"/api/settings",SettingsController), 35 | (r"/api/slowlog",SlowlogController), 36 | (r"/(.*)", BaseStaticFileHandler, {"path": os.path.abspath('.')+'/www'}) 37 | ] 38 | 39 | server_settings = {'debug': options.debug} 40 | application = tornado.web.Application(handlers, **server_settings) 41 | application.listen(options.port) 42 | print("start at:0.0.0.0:%d http://127.0.0.1:8888/index.html" %(options.port)) 43 | tornado.ioloop.IOLoop.instance().start() 44 | 45 | if __name__ == "__main__": 46 | live= redis_live() 47 | live.start() 48 | -------------------------------------------------------------------------------- /src/redis_live_daemon.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | curpath = os.path.split(os.path.realpath(sys.argv[0]))[0] 7 | os.chdir(curpath) 8 | 9 | from redis_live import redis_live 10 | live = redis_live() 11 | live.start_daemon() 12 | -------------------------------------------------------------------------------- /src/redis_monitor_daemon.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | curpath = os.path.split(os.path.realpath(sys.argv[0]))[0] 7 | os.chdir(curpath) 8 | 9 | from redis_monitor import redis_monitor 10 | montor = redis_monitor() 11 | montor.start_daemon() 12 | -------------------------------------------------------------------------------- /src/www/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/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 commandsWidget = new CommandsWidget({ 22 | el : $("#commands-widget-placeholder") 23 | , model : new CommandsWidgetModel() 24 | }) 25 | var statusWidget = new StatusWidget({ 26 | el : $("#status-widget-placeholder") 27 | , model : new StatusWidgetModel() 28 | }) 29 | 30 | } 31 | 32 | , RegisterPartials : function(){ 33 | 34 | // Handlebars.registerPartial("date-dropdown", $("#date-dropdown-template").html()); 35 | 36 | } 37 | 38 | , RegisterHelpers : function(){ 39 | 40 | Handlebars.registerHelper('hash', function ( context, options ) { 41 | 42 | var ret = "" 43 | , counter = 0 44 | 45 | $.each(context, function ( key, value ) { 46 | 47 | if (typeof value != "object") { 48 | obj = { "key" : key, "value" : value , "index" : counter++ } 49 | ret = ret + options.fn(obj) 50 | } 51 | 52 | }) 53 | 54 | return ret 55 | }) 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/src/www/js/libs/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/www/js/libs/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittlePeng/redis-monitor/e61648c462e3f5534de612ad382bc70c74975180/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/libs/google.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 = '0fb98c40e9e7bc594107c5bf2cc32e1e';
6 | google.visualization.LoadArgs = 'file\75visualization\46v\0751\46packages\75corechart';
7 | }
8 | // google.loader.writeLoadTag("script", google.loader.ServiceBase + "/api/visualization/1.0/0fb98c40e9e7bc594107c5bf2cc32e1e/format+zh_CN,default,corechart.I.js", false);
9 | }
10 |
--------------------------------------------------------------------------------
/src/www/js/libs/tooltip.css:
--------------------------------------------------------------------------------
1 | /* Copyright 2012 Google Inc. All Rights Reserved. */
2 |
3 | .google-visualization-tooltip-action: hover {
4 | background-color: #eeeeee;
5 | }
6 | .google-visualization-tooltip {
7 | border:solid 1px #bdbdbd;
8 | border-radius: 2px;
9 | background-color: white;
10 | position: absolute;
11 | box-shadow: 0px 2px 2px 0px rgba(204, 204, 204, 0.6);
12 | font-size: 12px;
13 | padding: 0px;
14 | -moz-box-shadow: 0px 2px 2px 0px rgba(204, 204, 204, 0.6);
15 | -webkit-box-shadow: 0px 2px 2px 0px rgba(204, 204, 204, 0.6);
16 | }
17 | .google-visualization-tooltip-action-list {
18 | list-style-type: none;
19 | margin: 0;
20 | padding: 0.5em 0em 0.5em 0em;
21 | cursor: hand;
22 | }
23 | .google-visualization-tooltip-action {
24 | margin: 0;
25 | cursor: pointer;
26 | padding: 0.5em 2em 0.5em 1em;
27 | }
28 | .google-visualization-tooltip-action:hover {
29 | background-color: #eeeeee;
30 | }
31 | .google-visualization-tooltip-item-list {
32 | list-style-type: none;
33 | margin: 1em 0 1em 0;
34 | padding: 0em;
35 | }
36 | .google-visualization-tooltip-item {
37 | margin: 0.65em 0em 0.65em 0em;
38 | padding: 0em 2em 0em 1em;
39 | }
40 | .google-visualization-tooltip-item-list
41 | .google-visualization-tooltip-item:first-child {
42 | margin: 1em 0em 1em 0em;
43 | }
44 | .google-visualization-tooltip-separator {
45 | margin: 0;
46 | padding: 0;
47 | height: 1px;
48 | background-color: #dddddd;
49 | }
50 | .google-visualization-tooltip-square {
51 | display: inline-block;
52 | /* IE does not support inline-block fall back to float left */
53 | float: left\9;
54 | clear: none;
55 | width: 0.5em;
56 | height: 0.5em;
57 | margin: 0.16em 0.7em 0em 0em;
58 | border-bottom: solid 0.1em white;
59 | }
60 |
61 | table.dataintable {
62 | font-family:Arial, Helvetica, sans-serif;
63 | margin-top:10px;
64 | border-collapse:collapse;
65 | border:1px solid #aaa;
66 | width:100%;
67 | }
68 |
69 | table.dataintable th {
70 | vertical-align:baseline;
71 | padding:5px 15px 5px 5px;
72 | background-color:#d5d5d5;
73 | border:1px solid #aaa;
74 | text-align:left;
75 | }
76 |
77 | table.dataintable td {
78 | vertical-align:text-top;
79 | padding:5px 15px 5px 5px;
80 | background-color:#efefef;
81 | border:1px solid #aaa;
82 | }
83 |
84 | table.dataintable pre {
85 | width:auto;
86 | margin:0;
87 | padding:0;
88 | border:0;
89 | background-color:transparent;
90 | }
91 |
92 | table.dataintable p {margin:0 0 2px 0;}
93 |
94 | div#maincontent table.dataintable ul, div#maincontent table.dataintable li {
95 | list-style-type:none;
96 | margin:0;
97 | padding:0;
98 | }
99 |
100 | table.dataintable td em{
101 | color:#0000ff;
102 | font-weight:normal;
103 | }
104 |
105 | table.dataintable .table_value {color:#0F93D2;}
106 |
--------------------------------------------------------------------------------
/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/status-widget-model.js:
--------------------------------------------------------------------------------
1 | var StatusWidgetModel = Backbone.Model.extend({
2 |
3 | url: "api/status",
4 |
5 | initialize: function() {
6 | }
7 | })
8 |
--------------------------------------------------------------------------------
/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 : 3000
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 | if(self.server==""){
22 | self.server = server;
23 | //first triggle defalut
24 | $("#time-period-defalut").click();
25 | //self.UpdateModel(true);
26 | }
27 | else if(self.server !=server){
28 | self.server = server;
29 | self.UpdateModelInner(true,true);
30 | }
31 | })
32 |
33 | $(".time-period").click(function(el){
34 | self.ChangeTimeFrame(self,el);
35 | });
36 | $(".go").click(function(el){
37 | self.Go(el);
38 | });
39 | // set event listners
40 | this.model
41 | .on("error", this.error, this)
42 | .on("error",this.ModelChanged,this)
43 | .on("change", this.ModelChanged, this);
44 |
45 | this.timer=null;
46 | },UpdateModel:function(enableTimer) {
47 | this.UpdateModelInner(enableTimer, false)
48 | }
49 | , UpdateModelInner : function ( enableTimer ,onlyOnce) {
50 | if(!onlyOnce){
51 | this.enableTimer = enableTimer;
52 | if(this.timer!=null)
53 | {
54 | clearTimeout(this.timer)
55 | this.timer=null
56 | }
57 | }
58 |
59 | this.startTime = new Date()
60 | var vfrom= $(document).find('[name=from]').val();
61 | var vto =$(document).find('[name=to]').val();
62 |
63 | //stop
64 | if(vfrom.length!=0 || vto.length!=0)
65 | this.enableTimer =false;
66 |
67 | this.model.fetch({
68 | data : {
69 | from : vfrom
70 | , to : vto
71 | , server : this.server
72 | }
73 | })
74 | }
75 |
76 | , ModelChanged : function(){
77 |
78 | this.endTime = new Date()
79 | var timeElapsed = (this.endTime - this.startTime);
80 |
81 | if (this.enableLogging)
82 | console.log(this.Name + ": Time Elapsed = " + timeElapsed + " ms")
83 |
84 | this.render()
85 |
86 | if(this.enableTimer && this.timer==null)
87 | {
88 | var self = this
89 | this.timer = setTimeout( function () { self.UpdateModel(true) }, this.updateFrequency )
90 | }
91 | }
92 |
93 | , Go : function( el ) {
94 | this.UpdateModel(false)
95 | }
96 |
97 | , ChangeTimeFrame : function (self, el ) {
98 |
99 | var selectionType = $(el.target).data("type")
100 | , timeFrame = parseInt( $(el.target).data("time") )
101 |
102 | // update the dropdown's label
103 | $(el.target)
104 | .closest(".btn-group")
105 | .children()
106 | .first()
107 | .text($(el.target).text())
108 |
109 | // Custom time frame selected
110 | if ( selectionType == "custom" ) {
111 | $(el.target)
112 | .closest(".btn-group")
113 | .siblings(".date-control")
114 | .css("display","inline")
115 | }
116 | // real time
117 | else if ( selectionType == "realtime" ) {
118 | $(el.target)
119 | .closest(".btn-group")
120 | .siblings(".date-control")
121 | .css("display","none")
122 |
123 |
124 | $(document).find('[name=from]').val("")
125 | $(document).find('[name=to]').val("")
126 | self.timer = setTimeout( function () { self.UpdateModel(true) }, this.updateFrequency )
127 | }
128 | // one of the template time frame selected
129 | // example: last 15mins, last 1 day etc
130 | else {
131 |
132 | $(el.target)
133 | .closest(".btn-group")
134 | .siblings(".date-control")
135 | .css("display","none")
136 |
137 | var endDate = new Date()
138 | , startDate = endDate
139 |
140 | switch(selectionType) {
141 |
142 | case 'minute' :
143 | startDate = new Date(endDate - timeFrame * 60000)
144 | break
145 |
146 | case 'hour' :
147 | startDate = new Date(endDate - timeFrame * 60*60000)
148 | break
149 |
150 | case 'day' :
151 | startDate = new Date(endDate - timeFrame * 24*60*60000)
152 | break
153 |
154 | case 'week' :
155 | startDate = new Date(endDate - timeFrame * 7*24*60*60000)
156 | break
157 |
158 | case 'month' :
159 | startDate = new Date(endDate - timeFrame * 30*24*60*60000)
160 | break
161 | }
162 |
163 |
164 | $(document).find('[name=from]').val(self.ISODateString(startDate))
165 | $(document).find('[name=to]').val(self.ISODateString(endDate))
166 | self.UpdateModel(false)
167 | }
168 | }
169 |
170 | , ISODateString : function ( d ) {
171 |
172 | function pad ( n ) {
173 | return n < 10 ? '0'+n : n
174 | }
175 |
176 | return d.getFullYear()+'-'
177 | + pad(d.getMonth()+1)+'-'
178 | + pad(d.getDate())+' '
179 | + pad(d.getHours())+':'
180 | + pad(d.getMinutes())+':'
181 | + pad(d.getSeconds())
182 | }
183 |
184 | , error: function ( model, error ) {
185 | if (this.enableLogging)
186 | console.log(this.Name + ": Error Occured \n" + error + "\n" + model )
187 |
188 | }
189 |
190 | })
--------------------------------------------------------------------------------
/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 | // chart 0
16 | this.chart0 = new google.visualization.LineChart($("#memory-widget-chart").empty().get(0))
17 | this.dataTable0 = new google.visualization.DataTable()
18 | this.dataTable0.addColumn('datetime', 'datetime')
19 | this.dataTable0.addColumn('number', 'Peak')
20 | this.dataTable0.addColumn('number', 'Current')
21 |
22 | // chart 1
23 | this.chart = new google.visualization.AreaChart($("#commands-widget-chart").empty().get(0))
24 | this.dataTable = new google.visualization.DataTable()
25 | this.dataTable.addColumn('datetime', 'datetime')
26 | this.dataTable.addColumn('number', 'Commands Processed')
27 |
28 | // chart 2
29 | this.chart2 = new google.visualization.AreaChart($("#hitrate-widget-chart").empty().get(0))
30 | this.dataTable2 = new google.visualization.DataTable()
31 | this.dataTable2.addColumn('datetime', 'datetime')
32 | this.dataTable2.addColumn('number', 'Hit Rate')
33 | //chart 3
34 | this.chart3 = new google.visualization.LineChart($("#keyspace-widget-chart").empty().get(0))
35 | this.dataTable3 = new google.visualization.DataTable()
36 | this.dataTable3.addColumn('datetime', 'datetime')
37 | this.dataTable3.addColumn('number', 'persists')
38 | this.dataTable3.addColumn('number', 'expires')
39 | //chart 4
40 | this.chart4 = new google.visualization.LineChart($("#kick-widget-chart").empty().get(0))
41 | this.dataTable4 = new google.visualization.DataTable()
42 | this.dataTable4.addColumn('datetime', 'datetime')
43 | this.dataTable4.addColumn('number', 'expired')
44 | this.dataTable4.addColumn('number', 'evicted')
45 | }
46 |
47 | , render : function() {
48 |
49 | var model = this.model.toJSON()
50 | , markUp = this.template(model)
51 | , self = this
52 |
53 | self.dataTable0.removeRows(0,self.dataTable0.getNumberOfRows())
54 | self.dataTable.removeRows(0,self.dataTable.getNumberOfRows())
55 | self.dataTable2.removeRows(0,self.dataTable2.getNumberOfRows())
56 | self.dataTable3.removeRows(0,self.dataTable3.getNumberOfRows())
57 | self.dataTable4.removeRows(0,self.dataTable4.getNumberOfRows())
58 |
59 | $.each(model.data, function(index, obj){
60 |
61 | // first item of the object contains datetime info
62 | // [ YYYY, MM, DD, HH, MM, SS ]
63 | var recordDate = new Date(obj[0][0], obj[0][1]-1, obj[0][2], obj[0][3], obj[0][4], obj[0][5])
64 | if(self.dataTable0)
65 | self.dataTable0.addRow( [recordDate, obj[7],obj[8]] )
66 | if(self.dataTable)
67 | self.dataTable.addRow( [recordDate, obj[1]] )
68 | if(self.dataTable2)
69 | self.dataTable2.addRow( [recordDate, obj[6]] )
70 | if(self.dataTable3)
71 | self.dataTable3.addRow( [recordDate, obj[3],obj[2]] )
72 | if(self.dataTable4)
73 | self.dataTable4.addRow( [recordDate, obj[4],obj[5]] )
74 | })
75 |
76 | var pointSize = model.data.length > 120 ? 1 : 5
77 | , options = {
78 | title : ''
79 | , colors: [ '#17BECF', '#9EDAE5' ]
80 | , areaOpacity : .9
81 | , pointSize: pointSize
82 | , chartArea: { 'top' : 10, 'width' : '85%' }
83 | , width : "100%"
84 | , height : 200
85 | , animation : { duration : 500, easing: 'out' }
86 | , vAxis: { minValue : 0 }
87 | }
88 | ,options2 = {
89 | title : ''
90 | , colors: [ '#1581AA', '#77BA44' ]
91 | , pointSize: pointSize
92 | , chartArea: { 'top' : 10, 'width' : '85%' }
93 | , width : "100%"
94 | , height : 200
95 | , animation : { duration : 500, easing : 'out' }
96 | }
97 | if($(this.chart0.cd).parent().css("display") !="none")
98 | this.chart0.draw(this.dataTable0, options2)
99 |
100 | if($(this.chart.cd).parent().css("display") !="none")
101 | this.chart.draw(this.dataTable, options2)
102 |
103 | if($(this.chart2.cd).parent().css("display") !="none")
104 | this.chart2.draw(this.dataTable2, options2)
105 |
106 | if($(this.chart3.cd).parent().css("display") !="none")
107 | this.chart3.draw(this.dataTable3, options2)
108 |
109 | if($(this.chart4.cd).parent().css("display") !="none")
110 | this.chart4.draw(this.dataTable4, options2)
111 |
112 | if($('#cb_slowlog').attr("checked")=="checked"){
113 | $('#cb_slowlog').trigger('click');
114 | $('#cb_slowlog').attr('checked','checked');
115 | }
116 | }
117 | })
--------------------------------------------------------------------------------
/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 | , render: function() {
24 |
25 | var model = this.model.toJSON()
26 | , markUp = this.template(model)
27 | , popoverMarkup = this.popOverTemplate(model.databases)
28 | , infoMarkup = this.infoTemplate(model)
29 |
30 | $(this.el).html(markUp)
31 |
32 | $('#total-keys').popover({
33 | "title" : "databases"
34 | , "content" : popoverMarkup
35 | })
36 |
37 | $('#misc-info').popover({
38 | "title" : "info"
39 | , "content" : infoMarkup
40 | , "placement" : "bottom"
41 | })
42 | }
43 | })
--------------------------------------------------------------------------------
/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 | var to=self.getQueryString('uri');
22 | if(to!=null && to.toString().indexOf(':')>0)
23 | self.$el.find('option[value="'+to+'"]').attr('selected',true);
24 |
25 | self.$el.trigger("change")
26 | },
27 | getQueryString: function(name) {
28 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
29 | var r = window.location.search.substr(1).match(reg);
30 | if (r != null) return unescape(r[2]); return null;
31 | }
32 | })
--------------------------------------------------------------------------------
/src/www/js/views/status-widget-view.js:
--------------------------------------------------------------------------------
1 | /* Status Widget
2 | * ====================== */
3 |
4 | var StatusWidget = BaseWidget.extend({
5 |
6 | initialize : function() {
7 |
8 | this.Name = "Status Widget"
9 |
10 | this.init()
11 | this.updateFrequency = 5000 // every 5 seconds
12 |
13 | // templates
14 | var templateSource = $("#status-widget-template").html()
15 | this.template = Handlebars.compile(templateSource)
16 | }
17 |
18 | , render: function() {
19 |
20 | var model = this.model.toJSON()
21 | , markUp = this.template(model.data)
22 |
23 | $(this.el).html(markUp)
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 | })
--------------------------------------------------------------------------------
/src/www/overview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
31 | Loading...
32 |
33 |
34 |
35 |
205 |
--------------------------------------------------------------------------------
/src/www/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 | Redis instances list
25 |
26 |
27 |
28 | sms alert:
29 |
30 | master-slave change
31 |
32 | master-slave status
33 |
34 |
35 |
36 |
37 |
77 |
78 |
--------------------------------------------------------------------------------
/src/www/static/css/reset.css:
--------------------------------------------------------------------------------
1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}
2 |
3 |
--------------------------------------------------------------------------------
/src/www/static/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #F5F5F5;
3 | color: #333333;
4 | font-family: 'Droid Sans', "proxima-nova-1","proxima-nova-2","HelveticaNeue-Light","Helvetica Neue Light","Helvetica Neue",Arial,Helvetica,sans-serif;
5 | font-size: 15px;
6 | }
7 |
8 | a:link {
9 | color:#7299BD;
10 | text-decoration:none;
11 | }
12 |
13 | a:link, a:visited {
14 | color:#333333;
15 | text-decoration:none;
16 | }
17 |
18 | a:hover {
19 | color:#565656;
20 | }
21 |
22 |
23 | .container {
24 | width:360px;
25 | margin-right: auto;
26 | margin-left: auto;
27 | float: left;
28 | }
29 |
30 | h1 {
31 | font-size: 22px;
32 | font-weight: lighter;
33 | margin-bottom: 10px;
34 | margin-top: 5px;
35 | }
36 |
37 | h2 {
38 | font-size: 22px;
39 | font-weight: lighter;
40 | margin-bottom: 10px;
41 | margin-top: 10px;
42 | }
43 |
44 | .stat {
45 | background: white;
46 | border-radius: 4px 4px 4px 4px;
47 | box-shadow: 0 0 30px rgba(0, 0, 0, 0.1);
48 | margin-bottom: 20px;
49 | margin-top: 10px;
50 | padding: 20px;
51 | margin-right: 30px;
52 | margin-left: 30px;
53 | min-height:200px;
54 | }
55 |
56 | .up {
57 | background: lightgreen;
58 | border-radius: 3px;
59 | padding: 5px;
60 | text-align: center;
61 | margin-right: 5px;
62 | width: 90px !important;
63 | display:block;
64 | font-size: 18px;
65 | margin-bottom: 10px;
66 | }
67 |
68 | .down {
69 | background: #F62817;
70 | border-radius: 3px;
71 | padding: 5px;
72 | text-align: center;
73 | margin-right: 5px;
74 | width: 90px !important;
75 | display:block;
76 | font-size: 18px;
77 | margin-bottom: 10px;
78 | color:white;
79 | }
80 |
81 | .info {
82 | font-size: 15px;
83 | }
84 |
85 | .info_line {
86 | line-height: 40px;
87 | border-bottom: 1px solid #ebebeb;
88 | }
89 |
90 | .detail_line strong {
91 | float: right;
92 | }
93 |
94 | #footer {
95 | bottom: 0;
96 | height: 80px;
97 | margin-top: 20px;
98 | }
99 |
100 | #footer_text {
101 | border-top: 1px solid #CCCCCC;
102 | color: #999999;
103 | margin: auto;
104 | padding-bottom: 10px;
105 | padding-top: 5px;
106 | width: 480px;
107 | text-align:center;
108 | }
109 |
110 | .clear {
111 | clear: both;
112 | }
113 |
114 | .line_span {
115 | float: right;
116 | }
117 |
118 | .hidden {
119 | display: none;
120 | }
121 |
122 |
--------------------------------------------------------------------------------
28 |
29 |
30 |