├── media ├── none.html ├── images │ ├── add.png │ ├── close.gif │ ├── close.png │ ├── data.jpg │ ├── desc.png │ ├── edit.png │ ├── info.jpg │ ├── info.png │ ├── input.png │ ├── reset.png │ ├── ruler.gif │ ├── save.png │ ├── delete.png │ ├── export.png │ ├── favicon.png │ ├── import.png │ ├── loading.gif │ ├── userinfo.jpg │ ├── openclose.png │ ├── tree-node.gif │ ├── tree-vline.gif │ ├── close_hover.png │ ├── folder-open.png │ ├── tree-lastnode.gif │ ├── userinfobig.jpg │ ├── grid-18px-masked.png │ ├── tree-folder-expanded.gif │ ├── tree-folder-collapsed.gif │ ├── tree-lastnode-collapsed.gif │ └── tree-lastnode-expanded.gif ├── bootstrap │ ├── 2.3.1 │ │ ├── font │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ └── img │ │ │ ├── glyphicons-halflings.png │ │ │ └── glyphicons-halflings-white.png │ └── bsie │ │ ├── img │ │ ├── glyphicons-halflings.gif │ │ └── glyphicons-halflings-white.gif │ │ └── js │ │ ├── bootstrap-ie.min.js │ │ └── bootstrap-ie.old.js ├── jquery-ztree │ └── 3.5.12 │ │ ├── css │ │ └── zTreeStyle │ │ │ ├── img │ │ │ ├── diy │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ ├── 6.png │ │ │ │ ├── 7.png │ │ │ │ ├── 8.png │ │ │ │ ├── 9.png │ │ │ │ ├── 1_close.png │ │ │ │ └── 1_open.png │ │ │ ├── loading.gif │ │ │ ├── line_conn.gif │ │ │ ├── zTreeStandard.gif │ │ │ └── zTreeStandard.png │ │ │ ├── zTreeStyle.min.css │ │ │ └── zTreeStyle.css │ │ └── js │ │ ├── jquery.ztree.exhide-3.5.min.js │ │ ├── jquery.ztree.excheck-3.5.min.js │ │ └── jquery.ztree.exhide-3.5.js ├── css │ └── common.css └── common │ └── wsize.js ├── __init__.py ├── locale └── zh_CN │ └── LC_MESSAGES │ ├── PyRedisAdmin.mo │ └── PyRedisAdmin.po ├── templates ├── overview.html ├── site_media.html ├── main.html ├── db_view.html ├── view.html ├── server_tree.html ├── auth │ └── login.html └── db_tree.html ├── mole ├── __init__.py ├── cookie.py ├── utils.py ├── common.py ├── const.py ├── response.py ├── route.py ├── server.py ├── request.py └── structs.py ├── .gitattributes ├── config.py ├── redis ├── utils.py ├── __init__.py ├── exceptions.py ├── _compat.py ├── lock.py └── sentinel.py ├── README.md ├── data_change.py ├── .gitignore ├── redis_api.py ├── over_view.py ├── i18n.py ├── data_view.py └── routes.py /media/none.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/add.png -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Redis数据的界面管理工具 4 | ''' 5 | 6 | __version__ = "1.0.1" -------------------------------------------------------------------------------- /media/images/close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/close.gif -------------------------------------------------------------------------------- /media/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/close.png -------------------------------------------------------------------------------- /media/images/data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/data.jpg -------------------------------------------------------------------------------- /media/images/desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/desc.png -------------------------------------------------------------------------------- /media/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/edit.png -------------------------------------------------------------------------------- /media/images/info.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/info.jpg -------------------------------------------------------------------------------- /media/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/info.png -------------------------------------------------------------------------------- /media/images/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/input.png -------------------------------------------------------------------------------- /media/images/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/reset.png -------------------------------------------------------------------------------- /media/images/ruler.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/ruler.gif -------------------------------------------------------------------------------- /media/images/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/save.png -------------------------------------------------------------------------------- /media/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/delete.png -------------------------------------------------------------------------------- /media/images/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/export.png -------------------------------------------------------------------------------- /media/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/favicon.png -------------------------------------------------------------------------------- /media/images/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/import.png -------------------------------------------------------------------------------- /media/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/loading.gif -------------------------------------------------------------------------------- /media/images/userinfo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/userinfo.jpg -------------------------------------------------------------------------------- /media/images/openclose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/openclose.png -------------------------------------------------------------------------------- /media/images/tree-node.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-node.gif -------------------------------------------------------------------------------- /media/images/tree-vline.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-vline.gif -------------------------------------------------------------------------------- /media/images/close_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/close_hover.png -------------------------------------------------------------------------------- /media/images/folder-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/folder-open.png -------------------------------------------------------------------------------- /media/images/tree-lastnode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-lastnode.gif -------------------------------------------------------------------------------- /media/images/userinfobig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/userinfobig.jpg -------------------------------------------------------------------------------- /media/images/grid-18px-masked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/grid-18px-masked.png -------------------------------------------------------------------------------- /media/images/tree-folder-expanded.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-folder-expanded.gif -------------------------------------------------------------------------------- /locale/zh_CN/LC_MESSAGES/PyRedisAdmin.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/locale/zh_CN/LC_MESSAGES/PyRedisAdmin.mo -------------------------------------------------------------------------------- /media/images/tree-folder-collapsed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-folder-collapsed.gif -------------------------------------------------------------------------------- /media/images/tree-lastnode-collapsed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-lastnode-collapsed.gif -------------------------------------------------------------------------------- /media/images/tree-lastnode-expanded.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/images/tree-lastnode-expanded.gif -------------------------------------------------------------------------------- /media/bootstrap/2.3.1/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/2.3.1/font/FontAwesome.otf -------------------------------------------------------------------------------- /media/bootstrap/2.3.1/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/2.3.1/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /media/bootstrap/2.3.1/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/2.3.1/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /media/bootstrap/2.3.1/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/2.3.1/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /media/bootstrap/bsie/img/glyphicons-halflings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/bsie/img/glyphicons-halflings.gif -------------------------------------------------------------------------------- /media/bootstrap/2.3.1/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/2.3.1/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /media/bootstrap/bsie/img/glyphicons-halflings-white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/bsie/img/glyphicons-halflings-white.gif -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/2.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/3.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/4.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/5.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/6.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/7.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/8.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/9.png -------------------------------------------------------------------------------- /media/bootstrap/2.3.1/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/bootstrap/2.3.1/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/loading.gif -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/1_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/1_close.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/1_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/diy/1_open.png -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/line_conn.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/line_conn.gif -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/zTreeStandard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/zTreeStandard.gif -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/img/zTreeStandard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoneXiong/PyRedisAdmin/HEAD/media/jquery-ztree/3.5.12/css/zTreeStyle/img/zTreeStandard.png -------------------------------------------------------------------------------- /templates/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PyRedisAdmin 6 | %include site_media.html media_prefix=media_prefix 7 | 8 | 9 | {{! redis_info }} 10 | 11 | -------------------------------------------------------------------------------- /mole/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = [ 4 | 'route', 'run', 'static_file', 'error','get', 'post', 'put', 'delete', 'Mole', 5 | 'request', 'response', 6 | 'abort', 'redirect', 7 | 'DEBUG', 'HTTP_CODES', 8 | '__version__', 9 | ] 10 | 11 | from mole import route, run, static_file, error,get, post, put, delete, Mole 12 | from mole import request, response 13 | from mole import abort, redirect 14 | from mole import DEBUG, HTTP_CODES 15 | 16 | __version__ = "1.0.1" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /templates/site_media.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | base = { 2 | 'servers':[ 3 | {'index':0, 4 | 'name': 'server1', 5 | 'host': '127.0.0.1', 6 | 'port': 6379, 7 | 'password': '', 8 | 'databases':16 9 | }, 10 | {'index':1, 11 | 'name': 'server2', 12 | 'host': '127.0.0.1', 13 | 'port': 6379, 14 | 'databases':16 15 | }, 16 | ], 17 | 'seperator' : ':', 18 | 'maxkeylen' : 100 19 | } 20 | media_prefix = "pyred_media" 21 | 22 | host = '0.0.0.0' 23 | port = 8085 24 | debug = True 25 | 26 | scan_batch = 10000 27 | show_key_self_count = False 28 | 29 | lang = 'zh_CN' 30 | 31 | admin_user = 'admin' 32 | admin_pwd = 'admin' -------------------------------------------------------------------------------- /redis/utils.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | 4 | try: 5 | import hiredis 6 | HIREDIS_AVAILABLE = True 7 | except ImportError: 8 | HIREDIS_AVAILABLE = False 9 | 10 | 11 | def from_url(url, db=None, **kwargs): 12 | """ 13 | Returns an active Redis client generated from the given database URL. 14 | 15 | Will attempt to extract the database id from the path url fragment, if 16 | none is provided. 17 | """ 18 | from redis.client import Redis 19 | return Redis.from_url(url, db, **kwargs) 20 | 21 | 22 | @contextmanager 23 | def pipeline(redis_obj): 24 | p = redis_obj.pipeline() 25 | yield p 26 | p.execute() 27 | 28 | 29 | class dummy(object): 30 | """ 31 | Instances of this class can be used as an attribute container. 32 | """ 33 | pass 34 | -------------------------------------------------------------------------------- /media/css/common.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-size: x-small; /* Wikipedia font-size scaling method */ 4 | } 5 | 6 | body { 7 | font: 116%/1.4em Verdana, sans-serif; 8 | color: #000; 9 | margin: 0; 10 | padding: 0; 11 | border: 0; 12 | height: 100%; 13 | max-height: 100%; 14 | background-color: #fff; 15 | } 16 | 17 | h1 { 18 | font: bold 190% "Helvetica Neue", Helvetica, Arial, sans-serif; 19 | } 20 | 21 | h2 { 22 | font: bold 160% "Helvetica Neue", Helvetica, Arial, sans-serif; 23 | } 24 | 25 | h3 { 26 | font: bold 130% "Helvetica Neue", Helvetica, Arial, sans-serif; 27 | } 28 | 29 | #left,#right,#openClose {float:left;} #openClose {width:8px;margin:0 1px;cursor:pointer;} 30 | #openClose,#openClose.close {background:#fdfdfd url("../images/openclose.png") no-repeat -29px center;} 31 | #openClose.close {background-position:1px center;} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyRedisAdmin 2 | ============ 3 | 4 | A web GUI for Redis data management 5 | 6 | About 7 | ======== 8 | PyRedisAdmin 是一个方便查看和管理Redis数据的web界面工具,使用Python开发。基于开源的轻量级python Web框架"Mole" 9 | 构建而成,不依赖于其他第三方库,部署相当方便。 10 | 11 | Quick start 12 | ======== 13 | 1. 下载源码 14 | 2. 配置config.py,加入要管理的redis的主机地址和端口、密码等 15 | 3. 运行: python routes.py 16 | 17 | Next work 18 | ======== 19 | 1. ~~完善数据编辑和管理~~[已完成] 20 | 2. ~~加入redis数据分库的管理~~[已完成] 21 | 3. ~~加入redis账号验证功能~~[已完成] 22 | 4. ~~千万级以上海量数据的支持~~[已完成] 23 | 5. 完善的数据导入导出功能 24 | 6. 加入json数据的友好支持,提供json编辑器 25 | 26 | Screenshots 27 | ======== 28 | ![info](/media/images/info.jpg) 29 | 30 | ![info](/media/images/desc.png) 31 | 32 | ![data](/media/images/data.jpg) 33 | 34 | 1. 模糊搜索 35 | 2. 查看下一批 36 | 3. 精确查看某key 37 | 4. 展开/折叠所有 38 | 5. 删除选定的key 39 | 6. 实时自动刷新key的数据 40 | 7. 重命名key/删除当前查看的key/导出key的数据到文件 41 | 8. 更新key的过期时间 42 | 9. 修改/删除key的成员数据 43 | 10. 为key的数据添加新的成员 44 | 11. 批量清除当前列出的所有key 45 | -------------------------------------------------------------------------------- /mole/cookie.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import base64 4 | import hmac 5 | try: import cPickle as pickle 6 | except ImportError: # pragma: no cover 7 | import pickle 8 | 9 | 10 | from utils import tob,_lscmp 11 | 12 | def cookie_is_encoded(data): 13 | ''' Return True if the argument looks like a encoded cookie.''' 14 | return bool(data.startswith(tob('!')) and tob('?') in data) 15 | 16 | def cookie_encode(data, key): 17 | ''' Encode and sign a pickle-able object. Return a (byte) string ''' 18 | msg = base64.b64encode(pickle.dumps(data, -1)) 19 | sig = base64.b64encode(hmac.new(key, msg).digest()) 20 | return tob('!') + sig + tob('?') + msg 21 | 22 | 23 | def cookie_decode(data, key): 24 | ''' Verify and decode an encoded string. Return an object or None.''' 25 | data = tob(data) 26 | if cookie_is_encoded(data): 27 | sig, msg = data.split(tob('?'), 1) 28 | if _lscmp(sig[1:], base64.b64encode(hmac.new(key, msg).digest())): 29 | return pickle.loads(base64.b64decode(msg)) 30 | return None -------------------------------------------------------------------------------- /redis/__init__.py: -------------------------------------------------------------------------------- 1 | from redis.client import Redis, StrictRedis 2 | from redis.connection import ( 3 | BlockingConnectionPool, 4 | ConnectionPool, 5 | Connection, 6 | SSLConnection, 7 | UnixDomainSocketConnection 8 | ) 9 | from redis.utils import from_url 10 | from redis.exceptions import ( 11 | AuthenticationError, 12 | BusyLoadingError, 13 | ConnectionError, 14 | DataError, 15 | InvalidResponse, 16 | PubSubError, 17 | ReadOnlyError, 18 | RedisError, 19 | ResponseError, 20 | TimeoutError, 21 | WatchError 22 | ) 23 | 24 | 25 | __version__ = '2.10.3' 26 | VERSION = tuple(map(int, __version__.split('.'))) 27 | 28 | __all__ = [ 29 | 'Redis', 'StrictRedis', 'ConnectionPool', 'BlockingConnectionPool', 30 | 'Connection', 'SSLConnection', 'UnixDomainSocketConnection', 'from_url', 31 | 'AuthenticationError', 'BusyLoadingError', 'ConnectionError', 'DataError', 32 | 'InvalidResponse', 'PubSubError', 'ReadOnlyError', 'RedisError', 33 | 'ResponseError', 'TimeoutError', 'WatchError' 34 | ] 35 | -------------------------------------------------------------------------------- /templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | PyRedisAdmin 4 | 5 | %include site_media.html media_prefix=media_prefix 6 | 7 | 8 |
9 |
10 | 12 |
13 |
 
14 | 18 |
19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /templates/db_view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 数据库查看 4 | 5 | %include site_media.html media_prefix=media_prefix 6 | 7 | 8 |
9 |
10 | 12 |
13 |
 
14 | 18 |
19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /media/common/wsize.js: -------------------------------------------------------------------------------- 1 | 2 | // 主框架窗口大小调整 3 | $("#left").width(leftWidth); 4 | $("#openClose").click(function(){ 5 | if($(this).hasClass("close")){ 6 | $(this).removeClass("close"); 7 | $(this).addClass("open"); 8 | $("#left").animate({width:0,opacity:"hide"}); 9 | $("#right").animate({width:$("#content").width()-$("#openClose").width()-5}); 10 | }else{ 11 | $(this).addClass("close"); 12 | $(this).removeClass("open"); 13 | $("#left").animate({width:leftWidth,opacity:"show"}); 14 | $("#right").animate({width:$("#content").width()-$("#openClose").width()-leftWidth-9}); 15 | } 16 | }); 17 | if(!Array.prototype.map) 18 | Array.prototype.map = function(fn,scope) { 19 | var result = [],ri = 0; 20 | for (var i = 0,n = this.length; i < n; i++){ 21 | if(i in this){ 22 | result[ri++] = fn.call(scope ,this[i],i,this); 23 | } 24 | } 25 | return result; 26 | }; 27 | var getWindowSize = function(){ 28 | return ["Height","Width"].map(function(name){ 29 | return window["inner"+name] || 30 | document.compatMode === "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ]; 31 | }); 32 | }; 33 | $(window).resize(function(){ 34 | wSize(); 35 | }); 36 | wSize(); // 在主窗体中定义,设置调整目标 -------------------------------------------------------------------------------- /mole/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import warnings 4 | import sys 5 | 6 | 7 | 8 | 9 | def depr(message, critical=False): 10 | if critical: raise DeprecationWarning(message) 11 | warnings.warn(message, DeprecationWarning, stacklevel=3) 12 | 13 | def tob(data, enc='utf8'): 14 | """ Convert anything to bytes """ 15 | return data.encode(enc) if isinstance(data, unicode) else bytes(data) 16 | 17 | def _lscmp(a, b): 18 | ''' Compares two strings in a cryptographically save way: 19 | Runtime is not affected by a common prefix. ''' 20 | return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b) 21 | 22 | 23 | 24 | if sys.version_info >= (3,0,0): 25 | def touni(x, enc='utf8'): 26 | """ Convert anything to unicode """ 27 | return str(x, encoding=enc) if isinstance(x, bytes) else str(x) 28 | else: 29 | def touni(x, enc='utf8'): 30 | """ Convert anything to unicode """ 31 | return x if isinstance(x, unicode) else unicode(str(x), encoding=enc, errors='ignore') 32 | 33 | # Convert strings and unicode to native strings 34 | if sys.version_info >= (3,0,0): 35 | tonat = touni 36 | else: 37 | tonat = tob 38 | tonat.__doc__ = """ Convert anything to native strings """ -------------------------------------------------------------------------------- /mole/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import const 5 | from response import HeaderDict 6 | 7 | class MoleException(Exception): 8 | """ A base class for exceptions used by Mole. """ 9 | pass 10 | 11 | class HTTPResponse(MoleException): 12 | """ Used to break execution and immediately finish the response """ 13 | def __init__(self, output='', status=200, header=None): 14 | super(MoleException, self).__init__("HTTP Response %d" % status) 15 | self.status = int(status) 16 | self.output = output 17 | self.headers = HeaderDict(header) if header else None 18 | 19 | def apply(self, response): 20 | if self.headers: 21 | for key, value in self.headers.iterallitems(): 22 | response.headers[key] = value 23 | response.status = self.status 24 | 25 | class HTTPError(HTTPResponse): 26 | """ Used to generate an error page """ 27 | def __init__(self, code=500, output='Unknown Error', exception=None, traceback=None, header=None): 28 | super(HTTPError, self).__init__(output, code, header) 29 | self.exception = exception 30 | self.traceback = traceback 31 | 32 | def __repr__(self): 33 | from template import template 34 | return template(const.ERROR_PAGE_TEMPLATE, e=self) 35 | -------------------------------------------------------------------------------- /redis/exceptions.py: -------------------------------------------------------------------------------- 1 | "Core exceptions raised by the Redis client" 2 | from redis._compat import unicode 3 | 4 | 5 | class RedisError(Exception): 6 | pass 7 | 8 | 9 | # python 2.5 doesn't implement Exception.__unicode__. Add it here to all 10 | # our exception types 11 | if not hasattr(RedisError, '__unicode__'): 12 | def __unicode__(self): 13 | if isinstance(self.args[0], unicode): 14 | return self.args[0] 15 | return unicode(self.args[0]) 16 | RedisError.__unicode__ = __unicode__ 17 | 18 | 19 | class AuthenticationError(RedisError): 20 | pass 21 | 22 | 23 | class ConnectionError(RedisError): 24 | pass 25 | 26 | 27 | class TimeoutError(RedisError): 28 | pass 29 | 30 | 31 | class BusyLoadingError(ConnectionError): 32 | pass 33 | 34 | 35 | class InvalidResponse(RedisError): 36 | pass 37 | 38 | 39 | class ResponseError(RedisError): 40 | pass 41 | 42 | 43 | class DataError(RedisError): 44 | pass 45 | 46 | 47 | class PubSubError(RedisError): 48 | pass 49 | 50 | 51 | class WatchError(RedisError): 52 | pass 53 | 54 | 55 | class NoScriptError(ResponseError): 56 | pass 57 | 58 | 59 | class ExecAbortError(ResponseError): 60 | pass 61 | 62 | 63 | class ReadOnlyError(ResponseError): 64 | pass 65 | 66 | 67 | class LockError(RedisError, ValueError): 68 | "Errors acquiring or releasing a lock" 69 | # NOTE: For backwards compatability, this class derives from ValueError. 70 | # This was originally chosen to behave like threading.Lock. 71 | pass 72 | -------------------------------------------------------------------------------- /data_change.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import config 4 | 5 | def delete_key(key,cl, cursor=None): 6 | if cursor!=None: 7 | key = key + '*' 8 | next_cursor,m_all = cl.scan(cursor=cursor, match=key, count=config.scan_batch) 9 | for e in m_all: 10 | cl.delete(e) 11 | else: 12 | cl.delete(key) 13 | 14 | def delete_value(key,value,type,cl): 15 | if type=="hash": 16 | cl.hdel(key, value) 17 | elif type=="list": 18 | cl.lset(key, value, '__delete__') 19 | cl.lrem(key, '__delete__', 0) 20 | elif type=="set": 21 | cl.srem(key, value) 22 | elif type=="zset": 23 | cl.zrem(key, value) 24 | 25 | def rename_key(key,new,cl): 26 | cl.rename(key,new) 27 | 28 | def edit_value(key,value,new,score,type,cl): 29 | if type=="hash": 30 | cl.hset(key,value,new) 31 | elif type=="list": 32 | cl.lset(key, value, new) 33 | elif type=="set": 34 | cl.srem(key, value) 35 | cl.sadd(key, new) 36 | elif type=="zset": 37 | cl.zrem(key, value) 38 | cl.zadd(key,new,float(score) ) 39 | elif type=="string": 40 | cl.set(key, new) 41 | 42 | def add_value(key,value,name,score,type,cl): 43 | if type=="hash": 44 | cl.hset(key,name,value) 45 | elif type=="list": 46 | cl.rpush(key, value) 47 | elif type=="set": 48 | cl.sadd(key, value) 49 | elif type=="zset": 50 | cl.zadd(key,value,float(score) ) 51 | 52 | def change_ttl(key,new,cl): 53 | cl.expire(key,new) -------------------------------------------------------------------------------- /locale/zh_CN/LC_MESSAGES/PyRedisAdmin.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: 2015-11-21 12:47+0800\n" 5 | "PO-Revision-Date: 2015-11-21 12:53+0800\n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: zh_CN\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.7.7\n" 13 | "X-Poedit-Basepath: .\n" 14 | "Plural-Forms: nplurals=1; plural=0;\n" 15 | "X-Poedit-SourceCharset: UTF-8\n" 16 | "X-Poedit-SearchPath-0: D:\\source\\PyRedisAdmin\\templates\n" 17 | 18 | #: D:\source\PyRedisAdmin\templates/db_tree.py:128 19 | msgid "Current cursor positon, you can set a value to scan" 20 | msgstr "当前的游标位置,您可以设置一个值来查看更多键" 21 | 22 | #: D:\source\PyRedisAdmin\templates/db_tree.py:129 23 | msgid "To see next batch of keys" 24 | msgstr "下一批的键值" 25 | 26 | #: D:\source\PyRedisAdmin\templates/db_tree.py:129 27 | msgid "Scan" 28 | msgstr "下一批" 29 | 30 | #: D:\source\PyRedisAdmin\templates/db_tree.py:132 31 | msgid "To see a exact key directly" 32 | msgstr "直接查看指定键" 33 | 34 | #: D:\source\PyRedisAdmin\templates/db_tree.py:134 35 | msgid "Expand all" 36 | msgstr "展开全部" 37 | 38 | #: D:\source\PyRedisAdmin\templates/db_tree.py:134 39 | msgid "Expand" 40 | msgstr "展开" 41 | 42 | #: D:\source\PyRedisAdmin\templates/db_tree.py:135 43 | msgid "Collapse all" 44 | msgstr "折叠全部" 45 | 46 | #: D:\source\PyRedisAdmin\templates/db_tree.py:135 47 | msgid "Collapse" 48 | msgstr "折叠" 49 | 50 | #: D:\source\PyRedisAdmin\templates/server_tree.py:53 51 | msgid "overview" 52 | msgstr "概要" 53 | 54 | #: D:\source\PyRedisAdmin\templates/view.py:11 55 | msgid "Open the monitor" 56 | msgstr "打开实时监视" 57 | -------------------------------------------------------------------------------- /mole/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import httplib 3 | 4 | TEMPLATE_PATH = ['./', './templates/'] 5 | TEMPLATES = {} 6 | DEBUG = True 7 | MEMFILE_MAX = 1024*100 8 | 9 | #: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found') 10 | HTTP_CODES = httplib.responses 11 | HTTP_CODES[418] = "I'm a teapot" # RFC 2324 12 | 13 | #: The default template used for error pages. Override with @error() 14 | ERROR_PAGE_TEMPLATE = """ 15 | %try: 16 | %from mole import DEBUG, HTTP_CODES, request 17 | %status_name = HTTP_CODES.get(e.status, 'Unknown').title() 18 | 19 | 20 | 21 | ops! Error {{e.status}}: {{status_name}} 22 | 27 | 28 | 29 |

ops! Error {{e.status}}: {{status_name}}

30 |

Sorry, the requested URL {{request.url}} caused an error:

31 |
{{str(e.output)}}
32 | %if DEBUG and e.exception: 33 |

Exception:

34 |
{{repr(e.exception)}}
35 | %end 36 | %if DEBUG and e.traceback: 37 |

Traceback:

38 |
{{e.traceback}}
39 | %end 40 | 41 | 42 | %except ImportError: 43 | ImportError: Could not generate the error page. Please add mole to sys.path 44 | %end 45 | """ -------------------------------------------------------------------------------- /templates/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PyRedisAdmin 6 | 7 | %include site_media.html media_prefix=media_prefix 8 | 9 | 10 | 11 | 12 | 77 |
78 | {{! out_html }} 79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /templates/server_tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | db view 4 | 5 | %include site_media.html media_prefix=media_prefix 6 | 7 | 8 | 9 | 14 | 50 | 51 | 52 |
53 |
54 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 | -------------------------------------------------------------------------------- /redis/_compat.py: -------------------------------------------------------------------------------- 1 | """Internal module for Python 2 backwards compatibility.""" 2 | import sys 3 | 4 | 5 | if sys.version_info[0] < 3: 6 | from urlparse import parse_qs, urlparse 7 | from itertools import imap, izip 8 | from string import letters as ascii_letters 9 | from Queue import Queue 10 | try: 11 | from cStringIO import StringIO as BytesIO 12 | except ImportError: 13 | from StringIO import StringIO as BytesIO 14 | 15 | iteritems = lambda x: x.iteritems() 16 | iterkeys = lambda x: x.iterkeys() 17 | itervalues = lambda x: x.itervalues() 18 | nativestr = lambda x: \ 19 | x if isinstance(x, str) else x.encode('utf-8', 'replace') 20 | u = lambda x: x.decode() 21 | b = lambda x: x 22 | next = lambda x: x.next() 23 | byte_to_chr = lambda x: x 24 | unichr = unichr 25 | xrange = xrange 26 | basestring = basestring 27 | unicode = unicode 28 | bytes = str 29 | long = long 30 | else: 31 | from urllib.parse import parse_qs, urlparse 32 | from io import BytesIO 33 | from string import ascii_letters 34 | from queue import Queue 35 | 36 | iteritems = lambda x: iter(x.items()) 37 | iterkeys = lambda x: iter(x.keys()) 38 | itervalues = lambda x: iter(x.values()) 39 | byte_to_chr = lambda x: chr(x) 40 | nativestr = lambda x: \ 41 | x if isinstance(x, str) else x.decode('utf-8', 'replace') 42 | u = lambda x: x 43 | b = lambda x: x.encode('latin-1') if not isinstance(x, bytes) else x 44 | next = next 45 | unichr = chr 46 | imap = map 47 | izip = zip 48 | xrange = range 49 | basestring = str 50 | unicode = str 51 | bytes = bytes 52 | long = int 53 | 54 | try: # Python 3 55 | from queue import LifoQueue, Empty, Full 56 | except ImportError: 57 | from Queue import Empty, Full 58 | try: # Python 2.6 - 2.7 59 | from Queue import LifoQueue 60 | except ImportError: # Python 2.5 61 | from Queue import Queue 62 | # From the Python 2.7 lib. Python 2.5 already extracted the core 63 | # methods to aid implementating different queue organisations. 64 | 65 | class LifoQueue(Queue): 66 | "Override queue methods to implement a last-in first-out queue." 67 | 68 | def _init(self, maxsize): 69 | self.maxsize = maxsize 70 | self.queue = [] 71 | 72 | def _qsize(self, len=len): 73 | return len(self.queue) 74 | 75 | def _put(self, item): 76 | self.queue.append(item) 77 | 78 | def _get(self): 79 | return self.queue.pop() 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Mr Developer 157 | .mr.developer.cfg 158 | 159 | # Mac crap 160 | .DS_Store 161 | .idea/ 162 | -------------------------------------------------------------------------------- /redis_api.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import redis 3 | 4 | import config 5 | 6 | client = None 7 | server_ip = None 8 | db_index = None 9 | 10 | def connect(*args, **kwargs): 11 | """ 连接Redis数据库,参数和redis-py的Redis类一样 """ 12 | global client 13 | client = redis.Redis(*args, **kwargs) 14 | 15 | def get_client(*args, **kwargs): 16 | global server_ip 17 | global db_index 18 | if args or kwargs: 19 | if server_ip!=None and db_index!=None: 20 | if kwargs['host']==server_ip and kwargs['db']==db_index: 21 | pass 22 | else: 23 | print 'switch conn...' 24 | connect(*args, **kwargs) 25 | server_ip = kwargs['host'] 26 | db_index = kwargs['db'] 27 | else: 28 | print 'init conn...' 29 | connect(*args, **kwargs) 30 | server_ip = kwargs['host'] 31 | db_index = kwargs['db'] 32 | 33 | global client 34 | if client: 35 | return client 36 | else: 37 | connect(host='127.0.0.1', port=6379) 38 | return client 39 | 40 | def get_tmp_client(*args, **kwargs): 41 | from redis import Redis 42 | return Redis(*args, **kwargs) 43 | 44 | def get_all_keys_dict(client=None): 45 | if client: 46 | m_all = client.keys() 47 | else: 48 | m_all = get_client().keys() 49 | m_all.sort() 50 | me = {} 51 | for key in m_all: 52 | if len(key)>100: 53 | continue 54 | key_levels = key.split(config.base['seperator']) 55 | cur = me 56 | for lev in key_levels: 57 | if cur.has_key(lev): 58 | if cur.keys()==0: 59 | cur[lev] = {lev:{}}#append(lev) 60 | else: 61 | cur[lev] = {} 62 | cur = cur[lev] 63 | return me 64 | 65 | def get_all_keys_tree(client=None,key='*', cursor=0): 66 | client = client or get_client() 67 | key = key or '*' 68 | if key=='*': 69 | next_cursor,m_all = client.scan(cursor=cursor, match=key, count=config.scan_batch) 70 | else: 71 | key = '*%s*'%key 72 | next_cursor,m_all = 0, client.keys(key) 73 | m_all.sort() 74 | me = {'root':{"pId": "0" ,"id": "root","name":"","count":0, "open":True}} 75 | counter = 0 76 | for key in m_all: 77 | counter +=1 78 | if counter>config.scan_batch: 79 | break 80 | if len(key)>100: 81 | continue 82 | key_levels = key.split(config.base['seperator']) 83 | pre = 'root' 84 | for lev in key_levels: 85 | id = (pre!='root' and '%s%s%s'%(pre,config.base['seperator'],lev) or lev) 86 | if me.has_key(id): 87 | pre = id 88 | else: 89 | tar = {"pId": pre,"id": id,"name":lev,"count":0} 90 | me[id] = tar 91 | me[pre]["count"]+= 1 92 | pre = id 93 | ret = me.values() 94 | for e in ret: 95 | child_len = e['count'] 96 | fullkey = e['id'] 97 | if child_len==0: 98 | m_len = 0 99 | if config.show_key_self_count: 100 | m_type = client.type(fullkey) 101 | if not m_type:return 102 | if m_type=='hash': 103 | m_len = client.hlen(fullkey) 104 | elif m_type=='list': 105 | m_len = client.llen(fullkey) 106 | elif m_type=='set': 107 | m_len = len(client.smembers(fullkey)) 108 | elif m_type=='zset': 109 | m_len = len(client.zrange(fullkey,0,-1)) 110 | else: 111 | m_len = child_len 112 | e['name'] = "%s (%s)"%(e['name'],m_len) 113 | val_list = me.values() 114 | val_list.append( (next_cursor,len(m_all) ) ) 115 | return val_list 116 | 117 | def check_connect(host,port, password=None): 118 | from redis import Connection 119 | try: 120 | conn = Connection(host=host, port=port, password=password) 121 | conn.connect() 122 | return True 123 | except: 124 | return False 125 | -------------------------------------------------------------------------------- /templates/auth/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 登录 - YouMd Blog 13 | 14 | 15 | 16 | 54 | 55 | 56 | 57 |
58 |
59 |

管理登录

60 |
61 |
62 | 63 |
64 | 65 |
66 |
67 |
68 | 69 |
70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | 79 |      80 |
81 |
82 |
83 |
84 |
85 | 86 | 132 | 133 | -------------------------------------------------------------------------------- /over_view.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from time import strftime,localtime 3 | import traceback 4 | 5 | from config import media_prefix 6 | from config import base 7 | 8 | def get_all_trees(cur_server_index=0,key=None, db=0, cursor=0): 9 | from redis_api import get_all_keys_tree,get_client 10 | server = base['servers'][cur_server_index] 11 | 12 | cl = get_client(host=server['host'], port=server['port'],db=db, password=server.has_key('password') and server['password'] or None ) 13 | try: 14 | ret = get_all_keys_tree(client=cl,key=key,cursor=cursor) 15 | return ret 16 | except Exception,e: 17 | traceback.print_exc() 18 | return u'Error to communicate with %s:%s "%s"'%(server['host'],server['port'], e) 19 | 20 | def get_db_trees(): 21 | from redis_api import get_tmp_client,check_connect 22 | servers = base['servers'] 23 | me = [{"pId": "0" ,"id": "root","name":"", "open":True}] 24 | m_index = 0 25 | for server in servers: 26 | id = "server%s"%m_index 27 | status = check_connect(server['host'], server['port'], password=server.has_key('password') and server['password'] or None) 28 | if status: 29 | tar = {"pId": "root", "id": id, "count":"", "name":server["name"]} 30 | client = get_tmp_client(host=server['host'], port=server['port'], password=server.has_key('password') and server['password'] or None) 31 | info_dict =client.info() 32 | else: 33 | tar = {"pId": "root", "id": id, "name": 'ERROR: Could not connect to Redis ('+server['host']+ ':'+ str(server['port'])+')'} 34 | me.append(tar) 35 | 36 | if status: 37 | for i in range(server["databases"]): 38 | if info_dict.has_key("db%s"%i): 39 | count = info_dict["db%s"%i]['keys'] 40 | m_tar = {"pId": id, "id": "db%s"%i, "count": count, "name":"db%s (%s)"%(i, count)} 41 | me.append(m_tar) 42 | m_index+=1 43 | return me 44 | 45 | def get_redis_info(): 46 | from redis_api import check_connect,get_tmp_client 47 | servers = base['servers'] 48 | outstr = '' 49 | ct = 0 50 | for server in servers: 51 | status = check_connect(server['host'], server['port'], password=server.has_key('password') and server['password'] or None) 52 | if status: 53 | client = get_tmp_client(host=server['host'], port=server['port'], password=server.has_key('password') and server['password'] or None) 54 | info_dict =client.info() 55 | if not info_dict.has_key("db0"): 56 | outstr +="Not exist database (db0)" 57 | else: 58 | outstr +=''' 59 |
60 |

%(name)s

61 | 62 | 63 | 64 | 65 | 66 | 73 |
Redis version:
%(redis_version)s
Keys:
%(keys)s
Memory used:
%(used_memory)s kb
Uptime:
%(uptime_in_seconds)s s
Last save:
67 |
68 | %(last_save_time)s 69 | [S] 70 | [E] 71 | [I] 72 |
'''%({ 74 | 'name': server['name'], 75 | 'redis_version': info_dict['redis_version'], 76 | 'keys': info_dict['db0']['keys'], 77 | 'used_memory': info_dict['used_memory']/1024, 78 | 'uptime_in_seconds': info_dict['uptime_in_seconds'], 79 | 'last_save_time': strftime("%Y-%m-%d %H:%M:%S",localtime( int( info_dict.has_key('rdb_last_save_time') and info_dict["rdb_last_save_time"] or info_dict.get('last_save_time', '0') ) )), 80 | 's_index':ct, 81 | 'media_prefix':media_prefix 82 | }) 83 | else: 84 | outstr +='ERROR: Could not connect to Redis ('+server['host']+ ':'+ str(server['port'])+')\n' 85 | ct +=1 86 | return outstr -------------------------------------------------------------------------------- /i18n.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import gettext 4 | from functools import wraps 5 | # import jinja2 6 | from mole.template import BaseTemplate 7 | import config 8 | 9 | def to_unicode(v, encoding='utf8'): 10 | if isinstance(v, unicode): 11 | return v 12 | try: 13 | return v.decode(encoding) 14 | except (AttributeError, UnicodeEncodeError): 15 | return unicode(v) 16 | 17 | def to_bytes(v, encoding='utf8'): 18 | if isinstance(v, bytes): 19 | return v 20 | try: 21 | return v.encode(encoding, errors='ignore') 22 | except AttributeError: 23 | return unicode(v).encode(encoding) 24 | 25 | 26 | class Lazy(object): 27 | """ 28 | Lazy proxy object. This proxy always evaluates the function when it is 29 | used. 30 | Any positional and keyword arguments that are passed to the constructor are 31 | stored and passed to the function except the ``_func`` argument which is 32 | the function itself. Because of this, the wrapped callable cannot use an 33 | argument named ``_func`` itself. 34 | """ 35 | 36 | def __init__(self, _func, *args, **kwargs): 37 | self._func = _func 38 | self._args = args 39 | self._kwargs = kwargs 40 | 41 | def _eval(self): 42 | return self._func(*self._args, **self._kwargs) 43 | 44 | @staticmethod 45 | def _eval_other(other): 46 | try: 47 | return other._eval() 48 | except AttributeError: 49 | return other 50 | 51 | def __getattr__(self, attr): 52 | obj = self._eval() 53 | return getattr(obj, attr) 54 | 55 | # We don't need __setattr__ and __delattr__ because the proxy object is not 56 | # really an object. 57 | 58 | def __getitem__(self, key): 59 | obj = self._eval() 60 | return obj.__getitem__(key) 61 | 62 | @property 63 | def __class__(self): 64 | return self._eval().__class__ 65 | 66 | def __repr__(self): 67 | return repr(self._eval()) 68 | 69 | def __str__(self): 70 | return to_unicode(self._eval()) 71 | 72 | def __bytes__(self): 73 | return to_bytes(self._eval()) 74 | 75 | def __call__(self): 76 | return self._eval()() 77 | 78 | def __format__(self, format_spec): 79 | return self._eval().__format__(format_spec) 80 | 81 | def __mod__(self, other): 82 | return self._eval().__mod__(other) 83 | 84 | # Being explicit about all comparison methods to avoid double-calls 85 | 86 | def __lt__(self, other): 87 | other = self._eval_other(other) 88 | return self._eval() < other 89 | 90 | def __le__(self, other): 91 | other = self._eval_other(other) 92 | return self._eval() <= other 93 | 94 | def __gt__(self, other): 95 | other = self._eval_other(other) 96 | return self._eval() > other 97 | 98 | def __ge__(self, other): 99 | other = self._eval_other(other) 100 | return self._eval() >= other 101 | 102 | def __eq__(self, other): 103 | other = self._eval_other(other) 104 | return self._eval() == other 105 | 106 | def __ne__(self, other): 107 | other = self._eval_other(other) 108 | return self._eval() != other 109 | 110 | # We mostly use this for strings, so having just __add__ is fine 111 | 112 | def __add__(self, other): 113 | other = self._eval_other(other) 114 | return self._eval() + other 115 | 116 | def __radd__(self, other): 117 | return self._eval_other(other) + self._eval() 118 | 119 | def __bool__(self): 120 | return bool(self._eval()) 121 | 122 | __nonzero__ = __bool__ 123 | 124 | def __hash__(self): 125 | return hash(self._eval()) 126 | 127 | def lazy(fn): 128 | """ 129 | Convert a function into lazily evaluated version. This decorator causes the 130 | function to return a :py:class:`~Lazy` proxy instead of the actual results. 131 | Usage is simple:: 132 | @lazy 133 | def my_lazy_func(): 134 | return 'foo' 135 | """ 136 | @wraps(fn) 137 | def wrapper(*args, **kwargs): 138 | return Lazy(fn, *args, **kwargs) 139 | return wrapper 140 | 141 | 142 | 143 | api = gettext.translation('PyRedisAdmin', 'locale', languages=[config.lang]) 144 | 145 | @lazy 146 | def lazy_gettext(message): 147 | return to_unicode(api.gettext(message)) 148 | 149 | BaseTemplate.defaults.update({ 150 | 'trans': lazy_gettext, 151 | }) 152 | 153 | # _ = gettext.gettext 154 | 155 | # SimpleTemplate.global_config('_', _) 156 | 157 | # jjenv = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'),extensions=['jinja2.ext.i18n']) 158 | # 159 | # def set_lang(lang): 160 | # #'lang'表示语言文件名为lang.mo,'i18n'表示语言文件名放在‘i18n'目录下,比如: 161 | # #中文翻译目录和文件:i18n\zh-cn\LC_MESSAGES\lang.mo 162 | # gettext.install('lang', 'i18n', unicode=True) 163 | # tr = gettext.translation('lang', 'i18n', languages=[lang]) 164 | # tr.install(True) 165 | # jjenv.install_gettext_translations(tr) -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/js/jquery.ztree.exhide-3.5.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JQuery zTree exHideNodes 3.5.12 3 | * http://zTree.me/ 4 | * 5 | * Copyright (c) 2010 Hunter.z 6 | * 7 | * Licensed same as jquery - MIT License 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * email: hunter.z@263.net 11 | * Date: 2013-03-11 12 | */ 13 | (function(i){i.extend(!0,i.fn.zTree._z,{view:{clearOldFirstNode:function(c,a){for(var b=a.getNextNode();b;){if(b.isFirstNode){b.isFirstNode=!1;f.setNodeLineIcos(c,b);break}if(b.isLastNode)break;b=b.getNextNode()}},clearOldLastNode:function(c,a){for(var b=a.getPreNode();b;){if(b.isLastNode){b.isLastNode=!1;f.setNodeLineIcos(c,b);break}if(b.isFirstNode)break;b=b.getPreNode()}},makeDOMNodeMainBefore:function(c,a,b){c.push("
  • ")},showNode:function(c,a){a.isHidden=!1;e.initShowForExCheck(c,a);i("#"+a.tId).show()},showNodes:function(c,a,b){if(a&&a.length!=0){var d={},h,j;for(h=0,j=a.length;h0&&!a[b][0].isHidden?a[b][0].isFirstNode=!0:d>0&&f.setFirstNodeForHide(c,a[b])},setLastNode:function(c,a){var b=c.data.key.children,d=a[b].length;d>0&&!a[b][0].isHidden?a[b][d-1].isLastNode=!0:d>0&&f.setLastNodeForHide(c,a[b])},setFirstNodeForHide:function(c,a){var b,d,h;for(d=0,h=a.length;d=0;d--){b=a[d];if(b.isLastNode)break;if(!b.isHidden&&!b.isLastNode){b.isLastNode=!0;f.setNodeLineIcos(c,b);break}else b=null}return b},setLastNodeForShow:function(c,a){var b,d,e,j;for(d=a.length-1;d>= 18 | 0;d--)if(b=a[d],!e&&!b.isHidden&&b.isLastNode){e=b;break}else if(!e&&!b.isHidden&&!b.isLastNode)b.isLastNode=!0,e=b,f.setNodeLineIcos(c,b);else if(e&&b.isLastNode){b.isLastNode=!1;j=b;f.setNodeLineIcos(c,b);break}return{"new":e,old:j}}},data:{initHideForExCheck:function(c,a){if(a.isHidden&&c.check&&c.check.enable){if(typeof a._nocheck=="undefined")a._nocheck=!!a.nocheck,a.nocheck=!0;a.check_Child_State=-1;f.repairParentChkClassWithSelf&&f.repairParentChkClassWithSelf(c,a)}},initShowForExCheck:function(c, 19 | a){if(!a.isHidden&&c.check&&c.check.enable){if(typeof a._nocheck!="undefined")a.nocheck=a._nocheck,delete a._nocheck;if(f.setChkClass){var b=i("#"+a.tId+l.id.CHECK);f.setChkClass(c,b,a)}f.repairParentChkClassWithSelf&&f.repairParentChkClassWithSelf(c,a)}}}});var k=i.fn.zTree,t=k._z.tools,l=k.consts,f=k._z.view,e=k._z.data;e.addInitNode(function(c,a,b){if(typeof b.isHidden=="string")b.isHidden=t.eqs(b.isHidden,"true");b.isHidden=!!b.isHidden;e.initHideForExCheck(c,b)});e.addBeforeA(function(){});e.addZTreeTools(function(c, 20 | a){a.showNodes=function(a,b){f.showNodes(c,a,b)};a.showNode=function(a,b){a&&f.showNodes(c,[a],b)};a.hideNodes=function(a,b){f.hideNodes(c,a,b)};a.hideNode=function(a,b){a&&f.hideNodes(c,[a],b)};var b=a.checkNode;if(b)a.checkNode=function(c,e,f,g){(!c||!c.isHidden)&&b.apply(a,arguments)}});var m=e.initNode;e.tmpHideParent=-1;e.initNode=function(c,a,b,d,h,j,g){if(e.tmpHideParent!==d){e.tmpHideParent=d;var i=(d?d:e.getRoot(c))[c.data.key.children];e.tmpHideFirstNode=f.setFirstNodeForHide(c,i);e.tmpHideLastNode= 21 | f.setLastNodeForHide(c,i);f.setNodeLineIcos(c,e.tmpHideFirstNode);f.setNodeLineIcos(c,e.tmpHideLastNode)}h=e.tmpHideFirstNode===b;j=e.tmpHideLastNode===b;m&&m.apply(e,arguments);j&&f.clearOldLastNode(c,b)};var n=e.makeChkFlag;if(n)e.makeChkFlag=function(c,a){(!a||!a.isHidden)&&n.apply(e,arguments)};var o=e.getTreeCheckedNodes;if(o)e.getTreeCheckedNodes=function(c,a,b,d){if(a&&a.length>0){var f=a[0].getParentNode();if(f&&f.isHidden)return[]}return o.apply(e,arguments)};var p=e.getTreeChangeCheckedNodes; 22 | if(p)e.getTreeChangeCheckedNodes=function(c,a,b){if(a&&a.length>0){var d=a[0].getParentNode();if(d&&d.isHidden)return[]}return p.apply(e,arguments)};var q=f.expandCollapseSonNode;if(q)f.expandCollapseSonNode=function(c,a,b,d,e){(!a||!a.isHidden)&&q.apply(f,arguments)};var r=f.setSonNodeCheckBox;if(r)f.setSonNodeCheckBox=function(c,a,b,d){(!a||!a.isHidden)&&r.apply(f,arguments)};var s=f.repairParentChkClassWithSelf;if(s)f.repairParentChkClassWithSelf=function(c,a){(!a||!a.isHidden)&&s.apply(f,arguments)}})(jQuery); 23 | -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/zTreeStyle.min.css: -------------------------------------------------------------------------------- 1 | .ztree *{padding:0;margin:0;font-size:12px;font-family:Verdana,Arial,Helvetica,AppleGothic,sans-serif}.ztree{margin:0;padding:2px;color:#333}.ztree li{padding:0;margin:0;list-style:none;line-height:14px;text-align:left;white-space:nowrap;outline:0}.ztree li ul{margin:0;padding:0 0 0 18px}.ztree li ul.line{background:url(./img/line_conn.gif) 0 0 repeat-y}.ztree li a{padding:1px 3px 0 0;margin:0;cursor:pointer;height:17px;color:#333;background-color:transparent;text-decoration:none;vertical-align:top;display:inline-block}.ztree li a:hover{text-decoration:underline}.ztree li a.curSelectedNode{padding-top:0;background-color:#f6f6f6;color:#0663a2;height:16px;border:1px #ddd solid;opacity:.8;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.ztree li a.curSelectedNode_Edit{padding-top:0;background-color:#ffe6b0;color:black;height:16px;border:1px #ffb951 solid;opacity:.8}.ztree li a.tmpTargetNode_inner{padding-top:0;background-color:#316ac5;color:white;height:16px;border:1px #316ac5 solid;opacity:.8;filter:alpha(opacity=80)}.ztree li a input.rename{height:14px;width:80px;padding:0;margin:0;font-size:12px;border:1px #7ec4cc solid;*border:0}.ztree li span{line-height:16px;margin-right:2px}.ztree li span.button{line-height:0;margin:0;width:16px;height:16px;display:inline-block;vertical-align:middle;border:0 none;cursor:pointer;outline:0;background-color:transparent;background-repeat:no-repeat;background-attachment:scroll;background-image:url("./img/zTreeStandard.png");*background-image:url("./img/zTreeStandard.gif")}.ztree li span.button.level0{*margin-left:-15px}.ztree li span.button.chk{width:13px;height:13px;margin:0 3px 0 0;cursor:auto}.ztree li span.button.chk.checkbox_false_full{background-position:0 0}.ztree li span.button.chk.checkbox_false_full_focus{background-position:0 -14px}.ztree li span.button.chk.checkbox_false_part{background-position:0 -28px}.ztree li span.button.chk.checkbox_false_part_focus{background-position:0 -42px}.ztree li span.button.chk.checkbox_false_disable{background-position:0 -56px} 2 | .ztree li span.button.chk.checkbox_true_full{background-position:-14px 0}.ztree li span.button.chk.checkbox_true_full_focus{background-position:-14px -14px}.ztree li span.button.chk.checkbox_true_part{background-position:-14px -28px}.ztree li span.button.chk.checkbox_true_part_focus{background-position:-14px -42px}.ztree li span.button.chk.checkbox_true_disable{background-position:-14px -56px}.ztree li span.button.chk.radio_false_full{background-position:-28px 0}.ztree li span.button.chk.radio_false_full_focus{background-position:-28px -14px}.ztree li span.button.chk.radio_false_part{background-position:-28px -28px}.ztree li span.button.chk.radio_false_part_focus{background-position:-28px -42px}.ztree li span.button.chk.radio_false_disable{background-position:-28px -56px}.ztree li span.button.chk.radio_true_full{background-position:-42px 0}.ztree li span.button.chk.radio_true_full_focus{background-position:-42px -14px}.ztree li span.button.chk.radio_true_part{background-position:-42px -28px}.ztree li span.button.chk.radio_true_part_focus{background-position:-42px -42px}.ztree li span.button.chk.radio_true_disable{background-position:-42px -56px}.ztree li span.button.switch{width:18px;height:18px}.ztree li span.button.root_open{background-position:-92px -54px}.ztree li span.button.root_close{background-position:-74px -54px}.ztree li span.button.roots_open{background-position:-92px 0}.ztree li span.button.roots_close{background-position:-74px 0}.ztree li span.button.center_open{background-position:-92px -18px}.ztree li span.button.center_close{background-position:-74px -18px}.ztree li span.button.bottom_open{background-position:-92px -36px}.ztree li span.button.bottom_close{background-position:-74px -36px}.ztree li span.button.noline_open{background-position:-92px -72px}.ztree li span.button.noline_close{background-position:-74px -72px}.ztree li span.button.root_docu{background:0}.ztree li span.button.roots_docu{background-position:-56px 0}.ztree li span.button.center_docu{background-position:-56px -18px}.ztree li span.button.bottom_docu{background-position:-56px -36px} 3 | .ztree li span.button.noline_docu{background:0}.ztree li span.button.ico_open{margin-right:2px;background-position:-110px -16px;vertical-align:top;*vertical-align:middle}.ztree li span.button.ico_close{margin-right:2px;background-position:-110px 0;vertical-align:top;*vertical-align:middle}.ztree li span.button.ico_docu{margin-right:2px;background-position:-110px -32px;vertical-align:top;*vertical-align:middle}.ztree li span.button.edit{margin-right:2px;background-position:-110px -48px;vertical-align:top;*vertical-align:middle}.ztree li span.button.remove{margin-right:2px;background-position:-110px -64px;vertical-align:top;*vertical-align:middle}.ztree li span.button.ico_loading{margin-right:2px;background:url(./img/loading.gif) no-repeat scroll 0 0 transparent;vertical-align:top;*vertical-align:middle}ul.tmpTargetzTree{background-color:#ffe6b0;opacity:.8;filter:alpha(opacity=80)}span.tmpzTreeMove_arrow{width:16px;height:16px;display:inline-block;padding:0;margin:2px 0 0 1px;border:0 none;position:absolute;background-color:transparent;background-repeat:no-repeat;background-attachment:scroll;background-position:-110px -80px;background-image:url("./img/zTreeStandard.png");*background-image:url("./img/zTreeStandard.gif")}ul.ztree.zTreeDragUL{margin:0;padding:0;position:absolute;width:auto;height:auto;overflow:hidden;background-color:#cfcfcf;border:1px #00b83f dotted;opacity:.8;filter:alpha(opacity=80)}.zTreeMask{z-index:10000;background-color:#cfcfcf;opacity:.0;filter:alpha(opacity=0);position:absolute} -------------------------------------------------------------------------------- /mole/response.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from Cookie import SimpleCookie 4 | import threading 5 | 6 | 7 | import utils 8 | from structs import MultiDict 9 | 10 | class HeaderDict(MultiDict): 11 | """ Same as :class:`MultiDict`, but title()s the keys and overwrites by default. """ 12 | def __contains__(self, key): return MultiDict.__contains__(self, self.httpkey(key)) 13 | def __getitem__(self, key): return MultiDict.__getitem__(self, self.httpkey(key)) 14 | def __delitem__(self, key): return MultiDict.__delitem__(self, self.httpkey(key)) 15 | def __setitem__(self, key, value): self.replace(key, value) 16 | def get(self, key, default=None, index=-1): return MultiDict.get(self, self.httpkey(key), default, index) 17 | def append(self, key, value): return MultiDict.append(self, self.httpkey(key), str(value)) 18 | def replace(self, key, value): return MultiDict.replace(self, self.httpkey(key), str(value)) 19 | def getall(self, key): return MultiDict.getall(self, self.httpkey(key)) 20 | def httpkey(self, key): return str(key).replace('_','-').title() 21 | 22 | 23 | class Response(threading.local): 24 | """ Represents a single HTTP response using thread-local attributes. 25 | """ 26 | 27 | def __init__(self): 28 | self.bind() 29 | 30 | def bind(self): 31 | """ Resets the Response object to its factory defaults. """ 32 | self._COOKIES = None 33 | self.status = 200 34 | self.headers = HeaderDict() 35 | self.content_type = 'text/html; charset=UTF-8' 36 | 37 | @property 38 | def header(self): 39 | utils.depr("Response.header renamed to Response.headers") 40 | return self.headers 41 | 42 | def copy(self): 43 | ''' Returns a copy of self. ''' 44 | copy = Response() 45 | copy.status = self.status 46 | copy.headers = self.headers.copy() 47 | copy.content_type = self.content_type 48 | return copy 49 | 50 | def wsgiheader(self): 51 | ''' Returns a wsgi conform list of header/value pairs. ''' 52 | for c in self.COOKIES.values(): 53 | if c.OutputString() not in self.headers.getall('Set-Cookie'): 54 | self.headers.append('Set-Cookie', c.OutputString()) 55 | # rfc2616 section 10.2.3, 10.3.5 56 | if self.status in (204, 304) and 'content-type' in self.headers: 57 | del self.headers['content-type'] 58 | if self.status == 304: 59 | for h in ('allow', 'content-encoding', 'content-language', 60 | 'content-length', 'content-md5', 'content-range', 61 | 'content-type', 'last-modified'): # + c-location, expires? 62 | if h in self.headers: 63 | del self.headers[h] 64 | return list(self.headers.iterallitems()) 65 | headerlist = property(wsgiheader) 66 | 67 | @property 68 | def charset(self): 69 | """ Return the charset specified in the content-type header. 70 | 71 | This defaults to `UTF-8`. 72 | """ 73 | if 'charset=' in self.content_type: 74 | return self.content_type.split('charset=')[-1].split(';')[0].strip() 75 | return 'UTF-8' 76 | 77 | @property 78 | def COOKIES(self): 79 | """ A dict-like SimpleCookie instance. Use :meth:`set_cookie` instead. """ 80 | if not self._COOKIES: 81 | self._COOKIES = SimpleCookie() 82 | return self._COOKIES 83 | 84 | def set_cookie(self, key, value, secret=None, **kargs): 85 | ''' Add a cookie. If the `secret` parameter is set, this creates a 86 | `Secure Cookie` (described below). 87 | 88 | :param key: the name of the cookie. 89 | :param value: the value of the cookie. 90 | :param secret: required for secure cookies. (default: None) 91 | :param max_age: maximum age in seconds. (default: None) 92 | :param expires: a datetime object or UNIX timestamp. (defaut: None) 93 | :param domain: the domain that is allowed to read the cookie. 94 | (default: current domain) 95 | :param path: limits the cookie to a given path (default: /) 96 | 97 | If neither `expires` nor `max_age` are set (default), the cookie 98 | lasts only as long as the browser is not closed. 99 | 100 | Secure cookies may store any pickle-able object and are 101 | cryptographically signed to prevent manipulation. Keep in mind that 102 | cookies are limited to 4kb in most browsers. 103 | 104 | Warning: Secure cookies are not encrypted (the client can still see 105 | the content) and not copy-protected (the client can restore an old 106 | cookie). The main intention is to make pickling and unpickling 107 | save, not to store secret information at client side. 108 | ''' 109 | if secret: 110 | value = utils.touni(cookie_encode((key, value), secret)) 111 | elif not isinstance(value, basestring): 112 | raise TypeError('Secret missing for non-string Cookie.') 113 | 114 | self.COOKIES[key] = value 115 | for k, v in kargs.iteritems(): 116 | self.COOKIES[key][k.replace('_', '-')] = v 117 | 118 | def delete_cookie(self, key, **kwargs): 119 | ''' Delete a cookie. Be sure to use the same `domain` and `path` 120 | parameters as used to create the cookie. ''' 121 | kwargs['max_age'] = -1 122 | kwargs['expires'] = 0 123 | self.set_cookie(key, '', **kwargs) 124 | 125 | def get_content_type(self): 126 | """ Current 'Content-Type' header. """ 127 | return self.headers['Content-Type'] 128 | 129 | def set_content_type(self, value): 130 | self.headers['Content-Type'] = value 131 | 132 | content_type = property(get_content_type, set_content_type, None, 133 | get_content_type.__doc__) 134 | -------------------------------------------------------------------------------- /media/bootstrap/bsie/js/bootstrap-ie.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.eb=b.eb||{};b.eb.ie6=function(){return navigator.userAgent.toLowerCase().indexOf("msie 6.0")>-1};b.eb.color=function(){var c=function(e,d){var f="0";e=e+"";while(e.lengthl){l=m}});b(this).width(l)})};if(b.eb.ie6()){g=g||b("html");b("a").bind("focus",function(){if(this.blur){this.blur()}});b('.row-fluid [class*="span"]:first-child, .row [class*="span"]:first-child').addClass("span-first-child");b(".dropdown-submenu > a",g).after('');b(".dropdown-submenu.pull-left",g).removeClass("pull-left").addClass("dropdown-submenu-pull-left");i(b(".dropdown-menu:visible",g));var f=["btn-primary","btn-warning","btn-danger","btn-success","btn-info","btn-inverse"];var d=["btn-mini","btn-small","btn-large"]; 2 | b(".btn-group",g).parent().find(".btn-group:eq(0)").addClass("btn-group-first");b(".btn",g).parent().find(".btn:eq(0)").addClass("btn-first");b("body",g).on("mouseenter",".btn",function(){var k=b(this);var l="btn-hover";k.data("ie6hover",l);b.each(f,function(n,m){if(k.hasClass(m)){l=m+"-hover";k.data("ie6hover",l);return false}});k.addClass(l)}).on("mouseleave",".btn",function(){var k=b(this);var l=k.data("ie6hover");k.removeData("ie6hover");if(l){k.removeClass(l)}});b(".btn.dropdown-toggle",g).each(function(){var l=b(this);var k="btn-dropdown-toggle";l.addClass(k);k=null;b.each(f,function(n,m){if(l.hasClass(m)){k=m+"-dropdown-toggle";return false}});if(k){l.addClass(k)}k=null;b.each(d,function(n,m){if(l.hasClass(m)){k=m+"-dropdown-toggle";return false}});if(k){l.addClass(k)}});b(".btn + .btn.dropdown-toggle",g).each(function(){var k=b(this);var l=k.css("background-color");k.css("background-color",b.eb.color.darken(l,0.1))});var e=function(n){var l=b(this);var k=n.data.cls;var m=b(".dropdown-menu:visible",this);if(m.length){i(m)}if(l.hasClass("open")&&!l.hasClass(k+"-open")){l.addClass(k+"-open")}else{if(!l.hasClass("open")&&l.hasClass(k+"-open")){l.removeClass(k+"-open")}}l.one("propertychange",{cls:k},e)};b.each(["btn-group","dropdown"],function(m,l){b("."+l,g).one("propertychange",{cls:l},e)});b(".btn.disabled",g).addClass("btn-disabled");var j=function(m){var l=b(this);var k=m.data.cls;if(l.hasClass("disabled")&&!l.hasClass(k+"-disabled")){l.addClass(k+"-disabled")}else{if(!l.hasClass("disabled")&&l.hasClass(k+"-disabled")){l.removeClass(k+"-disabled")}}l.one("propertychange",{cls:k},j)};b.each(["btn"],function(m,l){b("."+l,g).one("propertychange",{cls:l},j)});b("table.table-hover",g).on("mouseenter","tr",function(){b(this).addClass("tr-hover")}).on("mouseleave","tr",function(){b(this).removeClass("tr-hover")});b('input[type="file"], input[type="image"], input[type="submit"], input[type="reset"], input[type="button"], input[type="radio"], input[type="checkbox"], input[type="text"], input[type="password"], input[type="datetime"], input[type="datetime-local"], input[type="date"], input[type="month"], input[type="time"], input[type="week"], input[type="number"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], input[type="color"]',g).each(function(){var k=b(this); 3 | k.addClass("input-"+k.attr("type"))});b(".form-horizontal .controls:first-child",g).addClass("controls-first-child");b(".checkbox.inline",g).addClass("checkbox-inline");b(".radio.inline",g).addClass("radio-inline");b("select[multiple]",g).addClass("select-multiple");b("select[size]",g).addClass("select-size");b("input[disabled], select[disabled], textarea[disabled]",g).each(function(){var k=b(this);k.addClass(k[0].tagName.toLowerCase()+"-disabled")});b("input[readonly], select[readonly], textarea[readonly]",g).each(function(){var k=b(this);k.addClass(k[0].tagName.toLowerCase()+"-readonly")});b('input[type="radio"][disabled], input[type="checkbox"][disabled]',g).each(function(){var k=b(this);k.addClass(k.attr("type").toLowerCase()+"-disabled")});b('input[type="radio"][readonly], input[type="checkbox"][readonly]',g).each(function(){var k=b(this);k.addClass(k.attr("type").toLowerCase()+"-readonly")});var c=["warning","success","error","info"];b.each(c,function(m,l){b(".control-group."+l,g).addClass("control-group-"+l)});var h=function(l){if(l.originalEvent.propertyName.toLowerCase()=="classname"){var k=b(this);b.each(c,function(n,m){var o="control-group-"+m;if(k.hasClass(m)){if(!k.hasClass(o)){k.addClass(o)}}else{if(k.hasClass(o)){k.removeClass(o)}}})}b(this).one("propertychange",h)};b(".control-group",g).one("propertychange",h);b(".pagination ul li:first-child",g).addClass("first-child");b('[class^="icon-"], [class*=" icon-"]').addClass("icon-xxx");b(".carousel-control.left",g).removeClass("left").addClass("carousel-control-left");b(".carousel-control.right",g).removeClass("right").addClass("carousel-control-right");b(".carousel-caption").each(function(){var k=b(this);var l=k.outerWidth()-k.width();k.width(k.parents(".carousel-inner .item").width()-l)})}}b.bootstrapIE6=a;b(document).ready(function(){a()})})(jQuery); -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/css/zTreeStyle/zTreeStyle.css: -------------------------------------------------------------------------------- 1 | /*------------------------------------- 2 | zTree Style 3 | 4 | version: 3.4 5 | author: Hunter.z 6 | email: hunter.z@263.net 7 | website: http://code.google.com/p/jquerytree/ 8 | 9 | -------------------------------------*/ 10 | 11 | .ztree * {padding:0; margin:0; font-size:12px; font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif} 12 | .ztree {margin:0; padding:2px; color:#333} 13 | .ztree li{padding:0; margin:0; list-style:none; line-height:14px; text-align:left; white-space:nowrap; outline:0} 14 | .ztree li ul{ margin:0; padding:0 0 0 18px} 15 | .ztree li ul.line{ background:url(./img/line_conn.gif) 0 0 repeat-y;} 16 | 17 | .ztree li a {padding:1px 3px 0 0; margin:0; cursor:pointer; height:17px; color:#333; background-color: transparent; 18 | text-decoration:none; vertical-align:top; display: inline-block} 19 | .ztree li a:hover {text-decoration:underline} 20 | /*.ztree li a.curSelectedNode {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;}*/ 21 | .ztree li a.curSelectedNode {padding-top:0px; background-color:#F6F6F6; color:#0663A2; height:16px; border:1px #DDDDDD solid; opacity:0.8;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 22 | /*.ztree li a.curSelectedNode {padding-top:0px; color:#0663a2; font-weight:bold; height:16px; opacity:0.8;}*/ 23 | .ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;} 24 | .ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#316AC5; color:white; height:16px; border:1px #316AC5 solid; 25 | opacity:0.8; filter:alpha(opacity=80)} 26 | .ztree li a.tmpTargetNode_prev {} 27 | .ztree li a.tmpTargetNode_next {} 28 | .ztree li a input.rename {height:14px; width:80px; padding:0; margin:0; 29 | font-size:12px; border:1px #7EC4CC solid; *border:0px} 30 | .ztree li span {line-height:16px; margin-right:2px} 31 | .ztree li span.button {line-height:0; margin:0; width:16px; height:16px; display: inline-block; vertical-align:middle; 32 | border:0 none; cursor: pointer;outline:none; 33 | background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; 34 | background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")} 35 | 36 | /* IE7 fix */ 37 | .ztree li span.button.level0 {*margin-left:-15px;} 38 | 39 | .ztree li span.button.chk {width:13px; height:13px; margin:0 3px 0 0; cursor: auto} 40 | .ztree li span.button.chk.checkbox_false_full {background-position:0 0} 41 | .ztree li span.button.chk.checkbox_false_full_focus {background-position:0 -14px} 42 | .ztree li span.button.chk.checkbox_false_part {background-position:0 -28px} 43 | .ztree li span.button.chk.checkbox_false_part_focus {background-position:0 -42px} 44 | .ztree li span.button.chk.checkbox_false_disable {background-position:0 -56px} 45 | .ztree li span.button.chk.checkbox_true_full {background-position:-14px 0} 46 | .ztree li span.button.chk.checkbox_true_full_focus {background-position:-14px -14px} 47 | .ztree li span.button.chk.checkbox_true_part {background-position:-14px -28px} 48 | .ztree li span.button.chk.checkbox_true_part_focus {background-position:-14px -42px} 49 | .ztree li span.button.chk.checkbox_true_disable {background-position:-14px -56px} 50 | .ztree li span.button.chk.radio_false_full {background-position:-28px 0} 51 | .ztree li span.button.chk.radio_false_full_focus {background-position:-28px -14px} 52 | .ztree li span.button.chk.radio_false_part {background-position:-28px -28px} 53 | .ztree li span.button.chk.radio_false_part_focus {background-position:-28px -42px} 54 | .ztree li span.button.chk.radio_false_disable {background-position:-28px -56px} 55 | .ztree li span.button.chk.radio_true_full {background-position:-42px 0} 56 | .ztree li span.button.chk.radio_true_full_focus {background-position:-42px -14px} 57 | .ztree li span.button.chk.radio_true_part {background-position:-42px -28px} 58 | .ztree li span.button.chk.radio_true_part_focus {background-position:-42px -42px} 59 | .ztree li span.button.chk.radio_true_disable {background-position:-42px -56px} 60 | 61 | .ztree li span.button.switch {width:18px; height:18px} 62 | .ztree li span.button.root_open{background-position:-92px -54px} 63 | .ztree li span.button.root_close{background-position:-74px -54px} 64 | .ztree li span.button.roots_open{background-position:-92px 0} 65 | .ztree li span.button.roots_close{background-position:-74px 0} 66 | .ztree li span.button.center_open{background-position:-92px -18px} 67 | .ztree li span.button.center_close{background-position:-74px -18px} 68 | .ztree li span.button.bottom_open{background-position:-92px -36px} 69 | .ztree li span.button.bottom_close{background-position:-74px -36px} 70 | .ztree li span.button.noline_open{background-position:-92px -72px} 71 | .ztree li span.button.noline_close{background-position:-74px -72px} 72 | .ztree li span.button.root_docu{ background:none;} 73 | .ztree li span.button.roots_docu{background-position:-56px 0} 74 | .ztree li span.button.center_docu{background-position:-56px -18px} 75 | .ztree li span.button.bottom_docu{background-position:-56px -36px} 76 | .ztree li span.button.noline_docu{ background:none;} 77 | 78 | .ztree li span.button.ico_open{margin-right:2px; background-position:-110px -16px; vertical-align:top; *vertical-align:middle} 79 | .ztree li span.button.ico_close{margin-right:2px; background-position:-110px 0; vertical-align:top; *vertical-align:middle} 80 | .ztree li span.button.ico_docu{margin-right:2px; background-position:-110px -32px; vertical-align:top; *vertical-align:middle} 81 | .ztree li span.button.edit {margin-right:2px; background-position:-110px -48px; vertical-align:top; *vertical-align:middle} 82 | .ztree li span.button.remove {margin-right:2px; background-position:-110px -64px; vertical-align:top; *vertical-align:middle} 83 | 84 | .ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle} 85 | 86 | ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)} 87 | 88 | span.tmpzTreeMove_arrow {width:16px; height:16px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute; 89 | background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; 90 | background-position:-110px -80px; background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")} 91 | 92 | ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)} 93 | .zTreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute} 94 | 95 | /* level style*/ 96 | /*.ztree li span.button.level0 { 97 | display:none; 98 | } 99 | .ztree li ul.level0 { 100 | padding:0; 101 | background:none; 102 | }*/ 103 | -------------------------------------------------------------------------------- /templates/db_tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Keys list 4 | 5 | 6 | %include site_media.html media_prefix=media_prefix 7 | 8 | 9 | 10 | 11 | 16 | 150 | 151 | 152 |
    153 | 169 |
    170 |
    {{ error_msg }} 171 |
    172 |
    173 | 174 | -------------------------------------------------------------------------------- /data_view.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | ''' 3 | 数据相关视图 4 | ''' 5 | 6 | from config import media_prefix 7 | try: 8 | from html import escape # python 3.x 9 | except ImportError: 10 | from cgi import escape # python 2.x 11 | 12 | 13 | def title_html(fullkey, sid, db): 14 | out = ''' 15 |

    %(fullkey)s 16 | 17 | 18 | 19 |

    ''' % ({'sid': sid, 'db': db, 'fullkey': fullkey, 'media_prefix': media_prefix}) 20 | return out 21 | 22 | 23 | def general_html(fullkey, sid, db, client): 24 | cl = client 25 | m_type = cl.type(fullkey) 26 | m_ttl = cl.ttl(fullkey) 27 | m_ttl = m_ttl and m_ttl or '' 28 | redis_version = cl.info()['redis_version'] 29 | if redis_version >= '2.2.3': 30 | m_encoding = cl.object('encoding', fullkey) 31 | else: 32 | m_encoding = '' 33 | m_len = 0 34 | m_detail = '' 35 | m_other = '''

    36 | Add another value 37 |

    ''' % ({'sid': sid, 'db': db, 'type': m_type, 'fullkey': fullkey}) 38 | 39 | if m_type == 'string': 40 | val = cl.get(fullkey) 41 | m_len = len(val) 42 | m_detail = string_html(fullkey, sid, db, client) 43 | m_other = '' 44 | if m_type == 'hash': 45 | m_len = cl.hlen(fullkey) 46 | m_detail = hash_html(fullkey, sid, db, client) 47 | elif m_type == 'list': 48 | m_len = cl.llen(fullkey) 49 | m_detail = list_html(fullkey, sid, db, client) 50 | elif m_type == 'set': 51 | m_len = len(cl.smembers(fullkey)) 52 | m_detail = set_html(fullkey, sid, db, client) 53 | elif m_type == 'zset': 54 | m_len = len(cl.zrange(fullkey, 0, -1)) 55 | m_detail = zset_html(fullkey, sid, db, client) 56 | 57 | out = ''' 58 | 59 | 60 | 61 | 62 | 63 |
    Type:
    %(type)s
    TTL:
    %(ttl)s
    Encoding:
    %(encoding)s
    Size:
    %(size)s
    ''' % ( 64 | {'type': m_type, 'ttl': m_ttl, 'sid': sid, 'db': db, 'fullkey': fullkey, 'encoding': m_encoding, 'size': m_len, 65 | 'media_prefix': media_prefix}) 66 | return out + m_detail + m_other 67 | 68 | 69 | def string_html(fullkey, sid, db, client): 70 | m_value = client.get(fullkey) 71 | out = ''' 72 | 73 | 76 |
    %(value)s
    74 | 75 |
    77 | ''' % ({'value': escape(m_value), 'sid': sid, 'db': db, 'fullkey': fullkey, 'media_prefix': media_prefix}) 78 | return out 79 | 80 | 81 | def hash_html(fullkey, sid, db, client): 82 | out = ''' 83 | 84 | ''' 85 | m_values = client.hgetall(fullkey) 86 | alt = False 87 | for key, value in m_values.items(): 88 | if len(value) > 200: 89 | value = 'data(len:%s)' % len(value) 90 | alt_str = alt and 'class="alt"' or '' 91 | out += ''' 96 | ''' % ({'value': value, 'key': key, 'sid': sid, 'db': db, 'fullkey': fullkey, 'alt_str': alt_str, 97 | 'media_prefix': media_prefix}) 98 | alt = not alt 99 | out += '
    Key
    Value
     
     
    %(key)s
    %(value)s
    92 | 93 |
    94 | 95 |
    ' 100 | return out 101 | 102 | 103 | def list_html(fullkey, sid, db, client): 104 | out = ''' 105 | 106 | ''' 107 | m_values = client.lrange(fullkey, 0, -1) 108 | alt = False 109 | index = 0 110 | for value in m_values: 111 | alt_str = alt and 'class="alt"' or '' 112 | out += ''' 117 | ''' % ({'value': value.replace('"', '\\\''), 'index': index, 'sid': sid, 'db': db, 'fullkey': fullkey, 118 | 'alt_str': alt_str, 'media_prefix': media_prefix}) 119 | alt = not alt 120 | index += 1 121 | out += '
    Index
    Value
     
     
    %(index)s
    %(value)s
    113 | 114 |
    115 | 116 |
    ' 122 | return out 123 | 124 | 125 | def set_html(fullkey, sid, db, client): 126 | out = ''' 127 | 128 | ''' 129 | m_values = client.smembers(fullkey) 130 | alt = False 131 | for value in m_values: 132 | alt_str = alt and 'class="alt"' or '' 133 | out += ''' 138 | ''' % ( 139 | {'value': value, 'sid': sid, 'db': db, 'fullkey': fullkey, 'alt_str': alt_str, 'media_prefix': media_prefix}) 140 | alt = not alt 141 | out += '
    Value
     
     
    %(value)s
    134 | 135 |
    136 | 137 |
    ' 142 | return out 143 | 144 | 145 | def zset_html(fullkey, sid, db, client): 146 | out = ''' 147 | 148 | ''' 149 | m_values = client.zrange(fullkey, 0, -1) 150 | alt = False 151 | for value in m_values: 152 | score = client.zscore(fullkey, value) 153 | alt_str = alt and 'class="alt"' or '' 154 | out += ''' 159 | ''' % ({'value': value, 'score': score, 'sid': sid, 'db': db, 'fullkey': fullkey, 'alt_str': alt_str, 160 | 'media_prefix': media_prefix}) 161 | alt = not alt 162 | out += '
    Score
    Value
     
     
    %(score)s
    %(value)s
    155 | 156 |
    157 | 158 |
    ' 163 | return out 164 | -------------------------------------------------------------------------------- /mole/route.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | 5 | 6 | from common import HTTPError,MoleException 7 | from structs import lazy_attribute 8 | 9 | 10 | class RouteError(MoleException): 11 | """ This is a base class for all routing related exceptions """ 12 | 13 | class RouteSyntaxError(RouteError): 14 | """ The route parser found something not supported by this router """ 15 | 16 | class RouteBuildError(RouteError): 17 | """ The route could not been built """ 18 | 19 | class Router(object): 20 | ''' 21 | A route is defined by a path-rule and a HTTP method. 22 | wildcards consume characters up to the next slash (`/`). 23 | The path-rule is either a static path or a dynamic path 24 | 25 | The path-rule is either a static path (e.g. `/contact`) or a dynamic 26 | path that contains wildcards (e.g. `/wiki/:page`). By default, wildcards 27 | consume characters up to the next slash (`/`). To change that, you may 28 | add a regular expression pattern (e.g. `/wiki/:page#[a-z]+#`). 29 | 30 | For performance reasons, static routes (rules without wildcards) are 31 | checked first. Dynamic routes are tested in order and the first 32 | matching rule returns. Try to avoid ambiguous or overlapping rules. 33 | 34 | The HTTP method string matches only on equality, with two exceptions: 35 | * ´GET´ routes also match ´HEAD´ requests if there is no appropriate 36 | ´HEAD´ route installed. 37 | * ´ANY´ routes do match if there is no other suitable route installed. 38 | ''' 39 | default = '[^/]+' 40 | 41 | @lazy_attribute 42 | def syntax(cls): 43 | return re.compile(r'(?(rule, build_info) mapping 49 | self.static = {} # Cache for static routes: {path: {method: target}} 50 | self.dynamic = [] # Cache for dynamic routes. See _compile() 51 | 52 | def add(self, rule, method, target, name=None): 53 | ''' Add a new route or overwrite an existing target. ''' 54 | if rule in self.routes:#self.routes.has_key(rule) 55 | self.routes[rule][method.upper()] = target 56 | else: 57 | self.routes[rule] = {method.upper(): target} 58 | self.rules.append(rule) 59 | if self.static or self.dynamic: # Clear precompiler cache. 60 | self.static, self.dynamic = {}, {} 61 | if name: 62 | self.named[name] = (rule, None) 63 | 64 | def delete(self, rule, method=None): 65 | ''' Delete an existing route. Omit `method` to delete all targets. ''' 66 | if rule not in self.routes and rule in self.named: 67 | rule = self.named[rule][0] 68 | if rule in self.routes: 69 | if method: del self.routes[rule][method] 70 | else: self.routes[rule].clear() #清空字典 71 | if not self.routes[rule]: 72 | del self.routes[rule] 73 | self.rules.remove(rule) 74 | 75 | def build(self, _name, *anon, **args): 76 | ''' Return a string that matches a named route. Use keyword arguments 77 | to fill out named wildcards. Remaining arguments are appended as a 78 | query string. Raises RouteBuildError or KeyError.''' 79 | from urllib import urlencode 80 | if _name not in self.named: 81 | raise RouteBuildError("No route with that name.", _name) 82 | rule, pairs = self.named[_name] 83 | if not pairs: 84 | token = self.syntax.split(rule) 85 | parts = [p.replace('\\:',':') for p in token[::3]] 86 | names = token[1::3] 87 | if len(parts) > len(names): names.append(None) 88 | pairs = zip(parts, names) 89 | self.named[_name] = (rule, pairs) 90 | try: 91 | anon = list(anon) # * 参数的处理 92 | url = [s if k is None 93 | else s+str(args.pop(k)) if k else s+str(anon.pop()) 94 | for s, k in pairs] 95 | except IndexError: 96 | msg = "Not enough arguments to fill out anonymous wildcards.缺少参数匹配" 97 | raise RouteBuildError(msg) 98 | except KeyError, e: 99 | raise RouteBuildError(*e.args) 100 | 101 | if args: url += ['?', urlencode(args)] 102 | return ''.join(url) 103 | 104 | def match(self, environ): 105 | ''' Return a (target, url_agrs) tuple or raise HTTPError(404/405). ''' 106 | targets, urlargs = self._match_path(environ) 107 | if not targets: 108 | raise HTTPError(404, "Not found: " + environ.get('PATH_INFO','')) 109 | environ['router.url_args'] = urlargs 110 | method = environ['REQUEST_METHOD'].upper() 111 | if method in targets: 112 | return targets[method], urlargs 113 | if method == 'HEAD' and 'GET' in targets: 114 | return targets['GET'], urlargs 115 | if 'ANY' in targets: 116 | return targets['ANY'], urlargs 117 | allowed = [verb for verb in targets if verb != 'ANY'] 118 | if 'GET' in allowed and 'HEAD' not in allowed: 119 | allowed.append('HEAD') 120 | raise HTTPError(405, "Method not allowed.", 121 | header=[('Allow',",".join(allowed))]) 122 | 123 | def _match_path(self, environ): 124 | ''' Optimized PATH_INFO matcher. ''' 125 | path = environ['PATH_INFO'] or '/' 126 | # Assume we are in a warm state. Search compiled rules first. 127 | match = self.static.get(path) 128 | if match: return match, {} 129 | for combined, rules in self.dynamic: 130 | match = combined.match(path) 131 | if not match: continue 132 | gpat, match = rules[match.lastindex - 1] 133 | return match, gpat.match(path).groupdict() if gpat else {} 134 | # Lazy-check if we are really in a warm state. If yes, stop here. 135 | if self.static or self.dynamic or not self.routes: return None, {} 136 | # Cold state: We have not compiled any rules yet. Do so and try again. 137 | if not environ.get('wsgi.run_once'): 138 | self._compile() 139 | return self._match_path(environ) 140 | # For run_once (CGI) environments, don't compile. Just check one by one. 141 | epath = path.replace(':','\\:') # Turn path into its own static rule. 142 | match = self.routes.get(epath) # This returns static rule only. 143 | if match: return match, {} 144 | for rule in self.rules: 145 | #: Skip static routes to reduce re.compile() calls. 146 | if rule.count(':') < rule.count('\\:'): continue 147 | match = self._compile_pattern(rule).match(path) 148 | if match: return self.routes[rule], match.groupdict() 149 | return None, {} 150 | 151 | def _compile(self): 152 | ''' Prepare static and dynamic search structures. ''' 153 | self.static = {} 154 | self.dynamic = [] 155 | def fpat_sub(m): 156 | return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:' 157 | for rule in self.rules: 158 | target = self.routes[rule] 159 | if not self.syntax.search(rule): 160 | self.static[rule.replace('\\:',':')] = target 161 | continue 162 | gpat = self._compile_pattern(rule) 163 | fpat = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, gpat.pattern) 164 | gpat = gpat if gpat.groupindex else None 165 | try: 166 | combined = '%s|(%s)' % (self.dynamic[-1][0].pattern, fpat) 167 | self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1]) 168 | self.dynamic[-1][1].append((gpat, target)) 169 | except (AssertionError, IndexError), e: # AssertionError: Too many groups 170 | self.dynamic.append((re.compile('(^%s$)'%fpat), 171 | [(gpat, target)])) 172 | except re.error, e: 173 | raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e)) 174 | 175 | def _compile_pattern(self, rule): 176 | ''' Return a regular expression with named groups for each wildcard. ''' 177 | out = '' 178 | for i, part in enumerate(self.syntax.split(rule)): 179 | if i%3 == 0: out += re.escape(part.replace('\\:',':')) 180 | elif i%3 == 1: out += '(?P<%s>' % part if part else '(?:' 181 | else: out += '%s)' % (part or '[^/]+') 182 | return re.compile('^%s$'%out) 183 | -------------------------------------------------------------------------------- /mole/server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Server 4 | 5 | a list Server Adapter for wsgi 6 | """ 7 | import sys 8 | import os 9 | 10 | class ServerAdapter(object): 11 | quiet = False # 是否禁用标准输出和错误输出 12 | def __init__(self, host='127.0.0.1', port=8080, **config): 13 | self.options = config 14 | self.host = host 15 | self.port = int(port) 16 | 17 | def run(self, handler): # pragma: no cover 18 | pass 19 | 20 | def __repr__(self): 21 | ''' 返回一个对象的字符串表示 ''' 22 | args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) 23 | return "%s(%s)" % (self.__class__.__name__, args) 24 | 25 | 26 | class BjoernServer(ServerAdapter): 27 | """ Screamingly fast server written in C: https://github.com/jonashaag/bjoern """ 28 | def run(self, handler): 29 | from bjoern import run 30 | run(handler, self.host, self.port) 31 | 32 | class UVWebServer(ServerAdapter): 33 | def run(self, handler): 34 | from uvweb import run 35 | run(handler, self.host, self.port) 36 | 37 | class CGIServer(ServerAdapter): 38 | quiet = True 39 | def run(self, handler): # pragma: no cover 40 | from wsgiref.handlers import CGIHandler 41 | CGIHandler().run(handler) # Just ignore host and port here 42 | 43 | 44 | class FlupFCGIServer(ServerAdapter): 45 | def run(self, handler): # pragma: no cover 46 | import flup.server.fcgi 47 | kwargs = {'bindAddress':(self.host, self.port)} 48 | kwargs.update(self.options) # allow to override bindAddress and others 49 | flup.server.fcgi.WSGIServer(handler, **kwargs).run() 50 | 51 | 52 | class WSGIRefServer(ServerAdapter): 53 | def run(self, handler): # pragma: no cover 54 | __import__('BaseHTTPServer').BaseHTTPRequestHandler.address_string = lambda x:x.client_address[0] 55 | from wsgiref.simple_server import make_server, WSGIRequestHandler 56 | if self.quiet: 57 | class QuietHandler(WSGIRequestHandler): 58 | def log_request(*args, **kw): pass 59 | self.options['handler_class'] = QuietHandler 60 | srv = make_server(self.host, self.port, handler, **self.options) 61 | srv.serve_forever() 62 | 63 | 64 | class CherryPyServer(ServerAdapter): 65 | def run(self, handler): # pragma: no cover 66 | from cherrypy import wsgiserver 67 | server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) 68 | server.start() 69 | 70 | 71 | class PasteServer(ServerAdapter): 72 | def run(self, handler): # pragma: no cover 73 | from paste import httpserver 74 | if not self.quiet: 75 | from paste.translogger import TransLogger 76 | handler = TransLogger(handler) 77 | httpserver.serve(handler, host=self.host, port=str(self.port), 78 | **self.options) 79 | 80 | class MeinheldServer(ServerAdapter): 81 | def run(self, handler): 82 | from meinheld import server 83 | server.listen((self.host, self.port)) 84 | server.run(handler) 85 | 86 | class FapwsServer(ServerAdapter): 87 | """ Extremely fast webserver using libev. See http://www.fapws.org/ """ 88 | def run(self, handler): # pragma: no cover 89 | import fapws._evwsgi as evwsgi 90 | from fapws import base, config 91 | port = self.port 92 | if float(config.SERVER_IDENT[-2:]) > 0.4: 93 | # fapws3 silently changed its API in 0.5 94 | port = str(port) 95 | evwsgi.start(self.host, port) 96 | # fapws3 never releases the GIL. Complain upstream. I tried. No luck. 97 | if 'MOLE_CHILD' in os.environ and not self.quiet: 98 | print "WARNING: Auto-reloading does not work with Fapws3." 99 | print " (Fapws3 breaks python thread support)" 100 | evwsgi.set_base_module(base) 101 | def app(environ, start_response): 102 | environ['wsgi.multiprocess'] = False 103 | return handler(environ, start_response) 104 | evwsgi.wsgi_cb(('', app)) 105 | evwsgi.run() 106 | 107 | 108 | class TornadoServer(ServerAdapter): 109 | """ The super hyped asynchronous server by facebook. Untested. """ 110 | def run(self, handler): # pragma: no cover 111 | import tornado.wsgi 112 | import tornado.httpserver 113 | import tornado.ioloop 114 | container = tornado.wsgi.WSGIContainer(handler) 115 | server = tornado.httpserver.HTTPServer(container) 116 | server.listen(port=self.port) 117 | tornado.ioloop.IOLoop.instance().start() 118 | 119 | 120 | class AppEngineServer(ServerAdapter): 121 | """ Adapter for Google App Engine. """ 122 | quiet = True 123 | def run(self, handler): 124 | from google.appengine.ext.webapp import util 125 | # A main() function in the handler script enables 'App Caching'. 126 | # Lets makes sure it is there. This _really_ improves performance. 127 | module = sys.modules.get('__main__') 128 | if module and not hasattr(module, 'main'): 129 | module.main = lambda: util.run_wsgi_app(handler) 130 | util.run_wsgi_app(handler) 131 | 132 | 133 | class TwistedServer(ServerAdapter): 134 | """ Untested. """ 135 | def run(self, handler): 136 | from twisted.web import server, wsgi 137 | from twisted.python.threadpool import ThreadPool 138 | from twisted.internet import reactor 139 | thread_pool = ThreadPool() 140 | thread_pool.start() 141 | reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) 142 | factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler)) 143 | reactor.listenTCP(self.port, factory, interface=self.host) 144 | reactor.run() 145 | 146 | 147 | class DieselServer(ServerAdapter): 148 | """ Untested. """ 149 | def run(self, handler): 150 | from diesel.protocols.wsgi import WSGIApplication 151 | app = WSGIApplication(handler, port=self.port) 152 | app.run() 153 | 154 | 155 | class GeventServer(ServerAdapter): 156 | """ Untested. """ 157 | def run(self, handler): 158 | from gevent import wsgi 159 | #from gevent.hub import getcurrent 160 | #self.set_context_ident(getcurrent, weakref=True) # see contextlocal 161 | wsgi.WSGIServer((self.host, self.port), handler).serve_forever() 162 | 163 | 164 | class GeventWebSocketServer(ServerAdapter): 165 | def run(self, handler): 166 | from gevent import pywsgi 167 | from geventwebsocket.handler import WebSocketHandler 168 | pywsgi.WSGIServer((self.host, self.port), handler, handler_class=WebSocketHandler).serve_forever() 169 | 170 | 171 | class GunicornServer(ServerAdapter): 172 | """ Untested. """ 173 | def run(self, handler): 174 | from gunicorn.arbiter import Arbiter 175 | from gunicorn.config import Config 176 | handler.cfg = Config({'bind': "%s:%d" % (self.host, self.port), 'workers': 4}) 177 | arbiter = Arbiter(handler) 178 | arbiter.run() 179 | 180 | 181 | class EventletServer(ServerAdapter): 182 | """ Untested """ 183 | def run(self, handler): 184 | from eventlet import wsgi, listen 185 | wsgi.server(listen((self.host, self.port)), handler) 186 | 187 | 188 | class RocketServer(ServerAdapter): 189 | """ Untested. As requested in issue 63 190 | https://github.com/defnull/mole/issues/#issue/63 """ 191 | def run(self, handler): 192 | from rocket import Rocket 193 | server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler }) 194 | server.start() 195 | 196 | class AutoServer(ServerAdapter): 197 | """ Untested. """ 198 | adapters = [PasteServer, CherryPyServer, TwistedServer, WSGIRefServer] 199 | def run(self, handler): 200 | for sa in self.adapters: 201 | try: 202 | return sa(self.host, self.port, **self.options).run(handler) 203 | except ImportError: 204 | pass 205 | 206 | 207 | server_names = { 208 | 'cgi': CGIServer, 209 | 'flup': FlupFCGIServer, 210 | 'wsgiref': WSGIRefServer, 211 | 'cherrypy': CherryPyServer, 212 | 'paste': PasteServer, 213 | 'fapws3': FapwsServer, 214 | 'tornado': TornadoServer, 215 | 'gae': AppEngineServer, 216 | 'twisted': TwistedServer, 217 | 'diesel': DieselServer, 218 | 'meinheld': MeinheldServer, 219 | 'gunicorn': GunicornServer, 220 | 'eventlet': EventletServer, 221 | 'gevent': GeventServer, 222 | 'geventws': GeventWebSocketServer, 223 | 'rocket': RocketServer, 224 | 'bjoern' : BjoernServer, 225 | 'uvweb' : UVWebServer, 226 | 'auto': AutoServer, 227 | } 228 | -------------------------------------------------------------------------------- /routes.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from mole import route, run, static_file, error, get, post, put, delete, Mole # 均来自Mole类 4 | from mole.template import template, Jinja2Template 5 | from mole import request 6 | from mole import response 7 | from mole.mole import json_dumps 8 | from mole import redirect 9 | from mole.sessions import get_current_session, authenticator 10 | 11 | from config import media_prefix 12 | import config 13 | import i18n 14 | 15 | auth_required = authenticator(login_url='/auth/login') 16 | 17 | 18 | @route('/%s/:file#.*#' % media_prefix) 19 | def media(file): 20 | return static_file(file, root='./media') 21 | 22 | 23 | @route('/db_tree') 24 | @auth_required() 25 | def db_tree(): 26 | from over_view import get_all_trees 27 | import config 28 | try: 29 | cur_server_index = int(request.GET.get('s', '0')) 30 | cur_db_index = int(request.GET.get('db', '0')) 31 | cur_scan_cursor = int(request.GET.get('cursor', '0')) 32 | except: 33 | cur_server_index = 0 34 | cur_db_index = 0 35 | cur_scan_cursor = 0 36 | key = request.GET.get('k', '*') 37 | all_trees = get_all_trees(cur_server_index, key, db=cur_db_index, cursor=cur_scan_cursor) 38 | if type(all_trees) == list: 39 | next_scan_cursor, count = all_trees.pop() 40 | all_trees_json = json_dumps(all_trees) 41 | error_msg = '' 42 | else: 43 | next_scan_cursor, count = 0, 0 44 | all_trees_json = [] 45 | error_msg = all_trees 46 | m_config = config.base 47 | return template('db_tree', 48 | all_trees=all_trees_json, 49 | config=m_config, 50 | cur_server_index=cur_server_index, 51 | cur_db_index=cur_db_index, 52 | cur_scan_cursor=next_scan_cursor, 53 | pre_scan_cursor=cur_scan_cursor, 54 | cur_search_key=(key != '*' and key or ''), 55 | count=count, 56 | error_msg=error_msg, 57 | media_prefix=media_prefix 58 | ) 59 | 60 | 61 | @route('/db_view') 62 | @auth_required() 63 | def db_view(): 64 | try: 65 | cur_server_index = int(request.GET.get('s', 'server0').replace('server', '')) 66 | cur_db_index = int(request.GET.get('db', 'db0').replace('db', '')) 67 | except: 68 | cur_server_index = 0 69 | cur_db_index = 0 70 | key = request.GET.get('k', '*') 71 | return template("db_view", media_prefix=media_prefix, cur_server_index=cur_server_index, cur_db_index=cur_db_index, 72 | keyword=key) 73 | 74 | 75 | @route('/server_tree') 76 | @auth_required() 77 | def server_tree(): 78 | from over_view import get_db_trees 79 | all_trees = get_db_trees() 80 | return template("server_tree", all_trees=json_dumps(all_trees), media_prefix=media_prefix) 81 | 82 | 83 | @route('/') 84 | @auth_required() 85 | def server_view(): 86 | return template("main", media_prefix=media_prefix) 87 | 88 | 89 | @route('/overview') 90 | @auth_required() 91 | def overview(): 92 | from over_view import get_redis_info 93 | return template('overview', redis_info=get_redis_info(), media_prefix=media_prefix) 94 | 95 | 96 | @route('/view') 97 | @auth_required() 98 | def view(): 99 | from data_view import general_html, title_html 100 | fullkey = request.GET.get('key', '') 101 | refmodel = request.GET.get('refmodel', None) 102 | 103 | cl, cur_server_index, cur_db_index = get_cl() 104 | if cl.exists(fullkey): 105 | title_html = title_html(fullkey, cur_server_index, cur_db_index) 106 | general_html = general_html(fullkey, cur_server_index, cur_db_index, cl) 107 | out_html = title_html + general_html 108 | if refmodel: 109 | return out_html 110 | else: 111 | return template('view', out_html=out_html, media_prefix=media_prefix) 112 | else: 113 | return ' This key does not exist.' 114 | 115 | 116 | @route('/edit') 117 | @auth_required() 118 | def edit(): 119 | from data_change import edit_value 120 | key = request.GET.get('key', None) 121 | value = request.GET.get('value', None) 122 | type = request.GET.get('type', None) 123 | new = request.GET.get('new', None) 124 | score = request.GET.get('score', None) 125 | cl, cur_server_index, cur_db_index = get_cl() 126 | edit_value(key, value, new, score, type, cl) 127 | if new: 128 | return '' 129 | else: 130 | return '' 132 | 133 | 134 | @route('/add') 135 | @auth_required() 136 | def add(): 137 | from data_change import add_value 138 | key = request.GET.get('key', None) 139 | value = request.GET.get('value', None) 140 | type = request.GET.get('type', None) 141 | name = request.GET.get('name', None) 142 | score = request.GET.get('score', None) 143 | cl, cur_server_index, cur_db_index = get_cl() 144 | add_value(key, value, name, score, type, cl) 145 | return '' 146 | 147 | 148 | def get_cl(): 149 | from config import base 150 | from redis_api import get_client 151 | try: 152 | cur_server_index = int(request.GET.get('s', '0')) 153 | cur_db_index = int(request.GET.get('db', '0')) 154 | except: 155 | cur_server_index = 0 156 | cur_db_index = 0 157 | server = base['servers'][cur_server_index] 158 | cl = get_client(host=server['host'], port=server['port'], db=cur_db_index, 159 | password=server.has_key('password') and server['password'] or None) 160 | return cl, cur_server_index, cur_db_index 161 | 162 | 163 | @route('/delete') 164 | @auth_required() 165 | def delete(): 166 | from data_change import delete_key, delete_value 167 | key = request.GET.get('key', '') 168 | value = request.GET.get('value', None) 169 | type = request.GET.get('type', None) 170 | cur_scan_cursor = request.GET.get('cursor', None) 171 | cl, cur_server_index, cur_db_index = get_cl() 172 | if value: 173 | delete_value(key, value, type, cl) 174 | else: 175 | delete_key(key, cl, cursor=cur_scan_cursor) 176 | return '' 177 | return '' 178 | 179 | 180 | @route('/ttl') 181 | @auth_required() 182 | def ttl(): 183 | from data_change import change_ttl 184 | cl, cur_server_index, cur_db_index = get_cl() 185 | key = request.GET.get('key', None) 186 | new = request.GET.get('new', None) 187 | if new: 188 | change_ttl(key, int(new), cl) 189 | return '' 190 | 191 | 192 | @route('/rename') 193 | @auth_required() 194 | def rename(): 195 | from data_change import rename_key 196 | cl, cur_server_index, cur_db_index = get_cl() 197 | key = request.GET.get('key', None) 198 | new = request.GET.get('new', None) 199 | rename_key(key, new, cl) 200 | return '' 201 | 202 | 203 | @route('/export') 204 | def export(): 205 | return 'Still in developme. You can see it in next version.' 206 | 207 | 208 | @route('/import') 209 | def iimport(): 210 | return 'Still in developme. You can see it in next version.' 211 | 212 | 213 | @route('/save') 214 | @auth_required() 215 | def save(): 216 | cl, cur_server_index, cur_db_index = get_cl() 217 | cl.bgsave() 218 | return '' 219 | 220 | 221 | @route('/auth/login', method=['GET', 'POST']) 222 | def login(): 223 | if request.method == 'POST': 224 | username = request.POST.get("username", '') 225 | password = request.POST.get("password", '') 226 | if password == config.admin_pwd and username == config.admin_user: 227 | session = get_current_session() 228 | session['username'] = username 229 | return {'code': 0, 'msg': 'OK'} 230 | else: 231 | return {'code': -1, 'msg': '用户名或密码错误'} 232 | else: 233 | return template('auth/login.html', config=config, media_prefix=media_prefix) 234 | 235 | 236 | @route('/auth/logout') 237 | def logout(): 238 | session = get_current_session() 239 | del session['username'] 240 | return redirect(request.params.get('next') or '/') 241 | 242 | 243 | if __name__ == "__main__": 244 | from mole.mole import default_app 245 | from mole.sessions import SessionMiddleware 246 | 247 | app = SessionMiddleware(app=default_app(), cookie_key="457rxK3w54tkKiqkfqwfoiQS@kaJSFOo8h", no_datastore=True) 248 | run(app=app, host=config.host, port=config.port, reloader=config.debug) 249 | -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/js/jquery.ztree.excheck-3.5.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JQuery zTree excheck 3.5.12 3 | * http://zTree.me/ 4 | * 5 | * Copyright (c) 2010 Hunter.z 6 | * 7 | * Licensed same as jquery - MIT License 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * email: hunter.z@263.net 11 | * Date: 2013-03-11 12 | */ 13 | (function(m){var p,q,r,n={event:{CHECK:"ztree_check"},id:{CHECK:"_check"},checkbox:{STYLE:"checkbox",DEFAULT:"chk",DISABLED:"disable",FALSE:"false",TRUE:"true",FULL:"full",PART:"part",FOCUS:"focus"},radio:{STYLE:"radio",TYPE_ALL:"all",TYPE_LEVEL:"level"}},v={check:{enable:!1,autoCheckTrigger:!1,chkStyle:n.checkbox.STYLE,nocheckInherit:!1,chkDisabledInherit:!1,radioType:n.radio.TYPE_LEVEL,chkboxType:{Y:"ps",N:"ps"}},data:{key:{checked:"checked"}},callback:{beforeCheck:null,onCheck:null}};p=function(c, 14 | a){if(a.chkDisabled===!0)return!1;var b=g.getSetting(c.data.treeId),d=b.data.key.checked;if(l.apply(b.callback.beforeCheck,[b.treeId,a],!0)==!1)return!0;a[d]=!a[d];e.checkNodeRelation(b,a);d=m("#"+a.tId+i.id.CHECK);e.setChkClass(b,d,a);e.repairParentChkClassWithSelf(b,a);b.treeObj.trigger(i.event.CHECK,[c,b.treeId,a]);return!0};q=function(c,a){if(a.chkDisabled===!0)return!1;var b=g.getSetting(c.data.treeId),d=m("#"+a.tId+i.id.CHECK);a.check_Focus=!0;e.setChkClass(b,d,a);return!0};r=function(c,a){if(a.chkDisabled=== 15 | !0)return!1;var b=g.getSetting(c.data.treeId),d=m("#"+a.tId+i.id.CHECK);a.check_Focus=!1;e.setChkClass(b,d,a);return!0};m.extend(!0,m.fn.zTree.consts,n);m.extend(!0,m.fn.zTree._z,{tools:{},view:{checkNodeRelation:function(c,a){var b,d,h,k=c.data.key.children,j=c.data.key.checked;b=i.radio;if(c.check.chkStyle==b.STYLE){var f=g.getRadioCheckedList(c);if(a[j])if(c.check.radioType==b.TYPE_ALL){for(d=f.length-1;d>=0;d--)b=f[d],b[j]=!1,f.splice(d,1),e.setChkClass(c,m("#"+b.tId+i.id.CHECK),b),b.parentTId!= 16 | a.parentTId&&e.repairParentChkClassWithSelf(c,b);f.push(a)}else{f=a.parentTId?a.getParentNode():g.getRoot(c);for(d=0,h=f[k].length;d-1)&&e.setSonNodeCheckBox(c,a,!0),!a[j]&&(!a[k]||a[k].length==0||c.check.chkboxType.N.indexOf("s")>-1)&&e.setSonNodeCheckBox(c, 17 | a,!1),a[j]&&c.check.chkboxType.Y.indexOf("p")>-1&&e.setParentNodeCheckBox(c,a,!0),!a[j]&&c.check.chkboxType.N.indexOf("p")>-1&&e.setParentNodeCheckBox(c,a,!1)},makeChkClass:function(c,a){var b=c.data.key.checked,d=i.checkbox,h=i.radio,k="",k=a.chkDisabled===!0?d.DISABLED:a.halfCheck?d.PART:c.check.chkStyle==h.STYLE?a.check_Child_State<1?d.FULL:d.PART:a[b]?a.check_Child_State===2||a.check_Child_State===-1?d.FULL:d.PART:a.check_Child_State<1?d.FULL:d.PART,b=c.check.chkStyle+"_"+(a[b]?d.TRUE:d.FALSE)+ 18 | "_"+k,b=a.check_Focus&&a.chkDisabled!==!0?b+"_"+d.FOCUS:b;return i.className.BUTTON+" "+d.DEFAULT+" "+b},repairAllChk:function(c,a){if(c.check.enable&&c.check.chkStyle===i.checkbox.STYLE)for(var b=c.data.key.checked,d=c.data.key.children,h=g.getRoot(c),k=0,j=h[d].length;k0?e.repairParentChkClass(c,a[b][0]):e.repairParentChkClass(c,a)}},repairSonChkDisabled:function(c,a,b,d){if(a){var h=c.data.key.children;if(a.chkDisabled!=b)a.chkDisabled=b;e.repairChkClass(c,a);if(a[h]&&d)for(var k=0,j=a[h].length;k0){j=!1;break}j&&e.setParentNodeCheckBox(c,a.getParentNode(),b,d)}},setSonNodeCheckBox:function(c,a,b,d){if(a){var h=c.data.key.children,k=c.data.key.checked,j=m("#"+a.tId+i.id.CHECK);d||(d=a);var f=!1;if(a[h])for(var o=0,l=a[h].length;o0?b?2:0:-1}else a.check_Child_State=-1;e.setChkClass(c,j,a);c.check.autoCheckTrigger&&a!=d&&a.nocheck!==!0&&a.chkDisabled!==!0&&c.treeObj.trigger(i.event.CHECK,[null,c.treeId,a])}}}},event:{},data:{getRadioCheckedList:function(c){for(var a=g.getRoot(c).radioCheckedList,b=0,d= 23 | a.length;b-1&&a.check_Child_State<2:a.check_Child_State>0}},getTreeCheckedNodes:function(c,a,b,d){if(!a)return[];for(var h=c.data.key.children,k=c.data.key.checked,e=b&&c.check.chkStyle==i.radio.STYLE&& 24 | c.check.radioType==i.radio.TYPE_ALL,d=!d?[]:d,f=0,o=a.length;f0)break}return d},getTreeChangeCheckedNodes:function(c,a,b){if(!a)return[];for(var d=c.data.key.children,h=c.data.key.checked,b=!b?[]:b,k=0,e=a.length;k0?2:0,g==2){h=2;break}else g==0&&(h=0);else if(c.check.chkStyle==i.checkbox.STYLE)if(g=f.nocheck===!0||f.chkDisabled===!0?f.check_Child_State:f.halfCheck===!0?1:f[d]?f.check_Child_State===-1||f.check_Child_State===2?2:1:f.check_Child_State> 26 | 0?1:0,g===1){h=1;break}else if(g===2&&h>-1&&e>0&&g!==h){h=1;break}else if(h===2&&g>-1&&g<2){h=1;break}else g>-1&&(h=g)}a.check_Child_State=h}}}});var n=m.fn.zTree,l=n._z.tools,i=n.consts,e=n._z.view,g=n._z.data;g.exSetting(v);g.addInitBind(function(c){c.treeObj.bind(i.event.CHECK,function(a,b,d,h){l.apply(c.callback.onCheck,[b?b:a,d,h])})});g.addInitUnBind(function(c){c.treeObj.unbind(i.event.CHECK)});g.addInitCache(function(){});g.addInitNode(function(c,a,b,d){if(b){a=c.data.key.checked;typeof b[a]== 27 | "string"&&(b[a]=l.eqs(b[a],"true"));b[a]=!!b[a];b.checkedOld=b[a];if(typeof b.nocheck=="string")b.nocheck=l.eqs(b.nocheck,"true");b.nocheck=!!b.nocheck||c.check.nocheckInherit&&d&&!!d.nocheck;if(typeof b.chkDisabled=="string")b.chkDisabled=l.eqs(b.chkDisabled,"true");b.chkDisabled=!!b.chkDisabled||c.check.chkDisabledInherit&&d&&!!d.chkDisabled;if(typeof b.halfCheck=="string")b.halfCheck=l.eqs(b.halfCheck,"true");b.halfCheck=!!b.halfCheck;b.check_Child_State=-1;b.check_Focus=!1;b.getCheckStatus=function(){return g.getCheckStatus(c, 28 | b)}}});g.addInitProxy(function(c){var a=c.target,b=g.getSetting(c.data.treeId),d="",h=null,e="",j=null;if(l.eqs(c.type,"mouseover")){if(b.check.enable&&l.eqs(a.tagName,"span")&&a.getAttribute("treeNode"+i.id.CHECK)!==null)d=a.parentNode.id,e="mouseoverCheck"}else if(l.eqs(c.type,"mouseout")){if(b.check.enable&&l.eqs(a.tagName,"span")&&a.getAttribute("treeNode"+i.id.CHECK)!==null)d=a.parentNode.id,e="mouseoutCheck"}else if(l.eqs(c.type,"click")&&b.check.enable&&l.eqs(a.tagName,"span")&&a.getAttribute("treeNode"+ 29 | i.id.CHECK)!==null)d=a.parentNode.id,e="checkNode";if(d.length>0)switch(h=g.getNodeCache(b,d),e){case "checkNode":j=p;break;case "mouseoverCheck":j=q;break;case "mouseoutCheck":j=r}return{stop:!1,node:h,nodeEventType:e,nodeEventCallback:j,treeEventType:"",treeEventCallback:null}});g.addInitRoot(function(c){g.getRoot(c).radioCheckedList=[]});g.addBeforeA(function(c,a,b){var d=c.data.key.checked;c.check.enable&&(g.makeChkFlag(c,a),c.check.chkStyle==i.radio.STYLE&&c.check.radioType==i.radio.TYPE_ALL&& 30 | a[d]&&g.getRoot(c).radioCheckedList.push(a),b.push(""))});g.addZTreeTools(function(c,a){a.checkNode=function(a,b,g,j){var f=this.setting.data.key.checked;if(a.chkDisabled!==!0&&(b!==!0&&b!==!1&&(b=!a[f]),j=!!j,(a[f]!==b||g)&&!(j&&l.apply(this.setting.callback.beforeCheck,[this.setting.treeId,a],!0)==!1)&&l.uCanDo(this.setting)&&this.setting.check.enable&&a.nocheck!== 31 | !0))a[f]=b,b=m("#"+a.tId+i.id.CHECK),(g||this.setting.check.chkStyle===i.radio.STYLE)&&e.checkNodeRelation(this.setting,a),e.setChkClass(this.setting,b,a),e.repairParentChkClassWithSelf(this.setting,a),j&&c.treeObj.trigger(i.event.CHECK,[null,c.treeId,a])};a.checkAllNodes=function(a){e.repairAllChk(this.setting,!!a)};a.getCheckedNodes=function(a){var b=this.setting.data.key.children;return g.getTreeCheckedNodes(this.setting,g.getRoot(c)[b],a!==!1)};a.getChangeCheckedNodes=function(){var a=this.setting.data.key.children; 32 | return g.getTreeChangeCheckedNodes(this.setting,g.getRoot(c)[a])};a.setChkDisabled=function(a,b,c,g){b=!!b;c=!!c;e.repairSonChkDisabled(this.setting,a,b,!!g);e.repairParentChkDisabled(this.setting,a.getParentNode(),b,c)};var b=a.updateNode;a.updateNode=function(c,h){b&&b.apply(a,arguments);if(c&&this.setting.check.enable&&m("#"+c.tId).get(0)&&l.uCanDo(this.setting)){var g=m("#"+c.tId+i.id.CHECK);(h==!0||this.setting.check.chkStyle===i.radio.STYLE)&&e.checkNodeRelation(this.setting,c);e.setChkClass(this.setting, 33 | g,c);e.repairParentChkClassWithSelf(this.setting,c)}}});var s=e.createNodes;e.createNodes=function(c,a,b,d){s&&s.apply(e,arguments);b&&e.repairParentChkClassWithSelf(c,d)};var t=e.removeNode;e.removeNode=function(c,a){var b=a.getParentNode();t&&t.apply(e,arguments);a&&b&&(e.repairChkClass(c,b),e.repairParentChkClass(c,b))};var u=e.appendNodes;e.appendNodes=function(c,a,b,d,h,i){var j="";u&&(j=u.apply(e,arguments));d&&g.makeChkFlag(c,d);return j}})(jQuery); 34 | -------------------------------------------------------------------------------- /redis/lock.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time as mod_time 3 | import uuid 4 | from redis.exceptions import LockError, WatchError 5 | from redis.utils import dummy 6 | from redis._compat import b 7 | 8 | 9 | class Lock(object): 10 | """ 11 | A shared, distributed Lock. Using Redis for locking allows the Lock 12 | to be shared across processes and/or machines. 13 | 14 | It's left to the user to resolve deadlock issues and make sure 15 | multiple clients play nicely together. 16 | """ 17 | def __init__(self, redis, name, timeout=None, sleep=0.1, 18 | blocking=True, blocking_timeout=None, thread_local=True): 19 | """ 20 | Create a new Lock instance named ``name`` using the Redis client 21 | supplied by ``redis``. 22 | 23 | ``timeout`` indicates a maximum life for the lock. 24 | By default, it will remain locked until release() is called. 25 | ``timeout`` can be specified as a float or integer, both representing 26 | the number of seconds to wait. 27 | 28 | ``sleep`` indicates the amount of time to sleep per loop iteration 29 | when the lock is in blocking mode and another client is currently 30 | holding the lock. 31 | 32 | ``blocking`` indicates whether calling ``acquire`` should block until 33 | the lock has been acquired or to fail immediately, causing ``acquire`` 34 | to return False and the lock not being acquired. Defaults to True. 35 | Note this value can be overridden by passing a ``blocking`` 36 | argument to ``acquire``. 37 | 38 | ``blocking_timeout`` indicates the maximum amount of time in seconds to 39 | spend trying to acquire the lock. A value of ``None`` indicates 40 | continue trying forever. ``blocking_timeout`` can be specified as a 41 | float or integer, both representing the number of seconds to wait. 42 | 43 | ``thread_local`` indicates whether the lock token is placed in 44 | thread-local storage. By default, the token is placed in thread local 45 | storage so that a thread only sees its token, not a token set by 46 | another thread. Consider the following timeline: 47 | 48 | time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds. 49 | thread-1 sets the token to "abc" 50 | time: 1, thread-2 blocks trying to acquire `my-lock` using the 51 | Lock instance. 52 | time: 5, thread-1 has not yet completed. redis expires the lock 53 | key. 54 | time: 5, thread-2 acquired `my-lock` now that it's available. 55 | thread-2 sets the token to "xyz" 56 | time: 6, thread-1 finishes its work and calls release(). if the 57 | token is *not* stored in thread local storage, then 58 | thread-1 would see the token value as "xyz" and would be 59 | able to successfully release the thread-2's lock. 60 | 61 | In some use cases it's necessary to disable thread local storage. For 62 | example, if you have code where one thread acquires a lock and passes 63 | that lock instance to a worker thread to release later. If thread 64 | local storage isn't disabled in this case, the worker thread won't see 65 | the token set by the thread that acquired the lock. Our assumption 66 | is that these cases aren't common and as such default to using 67 | thread local storage. 68 | """ 69 | self.redis = redis 70 | self.name = name 71 | self.timeout = timeout 72 | self.sleep = sleep 73 | self.blocking = blocking 74 | self.blocking_timeout = blocking_timeout 75 | self.thread_local = bool(thread_local) 76 | self.local = threading.local() if self.thread_local else dummy() 77 | self.local.token = None 78 | if self.timeout and self.sleep > self.timeout: 79 | raise LockError("'sleep' must be less than 'timeout'") 80 | 81 | def __enter__(self): 82 | # force blocking, as otherwise the user would have to check whether 83 | # the lock was actually acquired or not. 84 | self.acquire(blocking=True) 85 | return self 86 | 87 | def __exit__(self, exc_type, exc_value, traceback): 88 | self.release() 89 | 90 | def acquire(self, blocking=None, blocking_timeout=None): 91 | """ 92 | Use Redis to hold a shared, distributed lock named ``name``. 93 | Returns True once the lock is acquired. 94 | 95 | If ``blocking`` is False, always return immediately. If the lock 96 | was acquired, return True, otherwise return False. 97 | 98 | ``blocking_timeout`` specifies the maximum number of seconds to 99 | wait trying to acquire the lock. 100 | """ 101 | sleep = self.sleep 102 | token = b(uuid.uuid1().hex) 103 | if blocking is None: 104 | blocking = self.blocking 105 | if blocking_timeout is None: 106 | blocking_timeout = self.blocking_timeout 107 | stop_trying_at = None 108 | if blocking_timeout is not None: 109 | stop_trying_at = mod_time.time() + blocking_timeout 110 | while 1: 111 | if self.do_acquire(token): 112 | self.local.token = token 113 | return True 114 | if not blocking: 115 | return False 116 | if stop_trying_at is not None and mod_time.time() > stop_trying_at: 117 | return False 118 | mod_time.sleep(sleep) 119 | 120 | def do_acquire(self, token): 121 | if self.redis.setnx(self.name, token): 122 | if self.timeout: 123 | # convert to milliseconds 124 | timeout = int(self.timeout * 1000) 125 | self.redis.pexpire(self.name, timeout) 126 | return True 127 | return False 128 | 129 | def release(self): 130 | "Releases the already acquired lock" 131 | expected_token = self.local.token 132 | if expected_token is None: 133 | raise LockError("Cannot release an unlocked lock") 134 | self.local.token = None 135 | self.do_release(expected_token) 136 | 137 | def do_release(self, expected_token): 138 | name = self.name 139 | 140 | def execute_release(pipe): 141 | lock_value = pipe.get(name) 142 | if lock_value != expected_token: 143 | raise LockError("Cannot release a lock that's no longer owned") 144 | pipe.delete(name) 145 | 146 | self.redis.transaction(execute_release, name) 147 | 148 | def extend(self, additional_time): 149 | """ 150 | Adds more time to an already acquired lock. 151 | 152 | ``additional_time`` can be specified as an integer or a float, both 153 | representing the number of seconds to add. 154 | """ 155 | if self.local.token is None: 156 | raise LockError("Cannot extend an unlocked lock") 157 | if self.timeout is None: 158 | raise LockError("Cannot extend a lock with no timeout") 159 | return self.do_extend(additional_time) 160 | 161 | def do_extend(self, additional_time): 162 | pipe = self.redis.pipeline() 163 | pipe.watch(self.name) 164 | lock_value = pipe.get(self.name) 165 | if lock_value != self.local.token: 166 | raise LockError("Cannot extend a lock that's no longer owned") 167 | expiration = pipe.pttl(self.name) 168 | if expiration is None or expiration < 0: 169 | # Redis evicted the lock key between the previous get() and now 170 | # we'll handle this when we call pexpire() 171 | expiration = 0 172 | pipe.multi() 173 | pipe.pexpire(self.name, expiration + int(additional_time * 1000)) 174 | 175 | try: 176 | response = pipe.execute() 177 | except WatchError: 178 | # someone else acquired the lock 179 | raise LockError("Cannot extend a lock that's no longer owned") 180 | if not response[0]: 181 | # pexpire returns False if the key doesn't exist 182 | raise LockError("Cannot extend a lock that's no longer owned") 183 | return True 184 | 185 | 186 | class LuaLock(Lock): 187 | """ 188 | A lock implementation that uses Lua scripts rather than pipelines 189 | and watches. 190 | """ 191 | lua_acquire = None 192 | lua_release = None 193 | lua_extend = None 194 | 195 | # KEYS[1] - lock name 196 | # ARGV[1] - token 197 | # ARGV[2] - timeout in milliseconds 198 | # return 1 if lock was acquired, otherwise 0 199 | LUA_ACQUIRE_SCRIPT = """ 200 | if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then 201 | if ARGV[2] ~= '' then 202 | redis.call('pexpire', KEYS[1], ARGV[2]) 203 | end 204 | return 1 205 | end 206 | return 0 207 | """ 208 | 209 | # KEYS[1] - lock name 210 | # ARGS[1] - token 211 | # return 1 if the lock was released, otherwise 0 212 | LUA_RELEASE_SCRIPT = """ 213 | local token = redis.call('get', KEYS[1]) 214 | if not token or token ~= ARGV[1] then 215 | return 0 216 | end 217 | redis.call('del', KEYS[1]) 218 | return 1 219 | """ 220 | 221 | # KEYS[1] - lock name 222 | # ARGS[1] - token 223 | # ARGS[2] - additional milliseconds 224 | # return 1 if the locks time was extended, otherwise 0 225 | LUA_EXTEND_SCRIPT = """ 226 | local token = redis.call('get', KEYS[1]) 227 | if not token or token ~= ARGV[1] then 228 | return 0 229 | end 230 | local expiration = redis.call('pttl', KEYS[1]) 231 | if not expiration then 232 | expiration = 0 233 | end 234 | if expiration < 0 then 235 | return 0 236 | end 237 | redis.call('pexpire', KEYS[1], expiration + ARGV[2]) 238 | return 1 239 | """ 240 | 241 | def __init__(self, *args, **kwargs): 242 | super(LuaLock, self).__init__(*args, **kwargs) 243 | LuaLock.register_scripts(self.redis) 244 | 245 | @classmethod 246 | def register_scripts(cls, redis): 247 | if cls.lua_acquire is None: 248 | cls.lua_acquire = redis.register_script(cls.LUA_ACQUIRE_SCRIPT) 249 | if cls.lua_release is None: 250 | cls.lua_release = redis.register_script(cls.LUA_RELEASE_SCRIPT) 251 | if cls.lua_extend is None: 252 | cls.lua_extend = redis.register_script(cls.LUA_EXTEND_SCRIPT) 253 | 254 | def do_acquire(self, token): 255 | timeout = self.timeout and int(self.timeout * 1000) or '' 256 | return bool(self.lua_acquire(keys=[self.name], 257 | args=[token, timeout], 258 | client=self.redis)) 259 | 260 | def do_release(self, expected_token): 261 | if not bool(self.lua_release(keys=[self.name], 262 | args=[expected_token], 263 | client=self.redis)): 264 | raise LockError("Cannot release a lock that's no longer owned") 265 | 266 | def do_extend(self, additional_time): 267 | additional_time = int(additional_time * 1000) 268 | if not bool(self.lua_extend(keys=[self.name], 269 | args=[self.local.token, additional_time], 270 | client=self.redis)): 271 | raise LockError("Cannot extend a lock that's no longer owned") 272 | return True 273 | -------------------------------------------------------------------------------- /media/jquery-ztree/3.5.12/js/jquery.ztree.exhide-3.5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JQuery zTree exHideNodes 3.5.12 3 | * http://zTree.me/ 4 | * 5 | * Copyright (c) 2010 Hunter.z 6 | * 7 | * Licensed same as jquery - MIT License 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * email: hunter.z@263.net 11 | * Date: 2013-03-11 12 | */ 13 | (function($){ 14 | //default init node of exLib 15 | var _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { 16 | if (typeof n.isHidden == "string") n.isHidden = tools.eqs(n.isHidden, "true"); 17 | n.isHidden = !!n.isHidden; 18 | data.initHideForExCheck(setting, n); 19 | }, 20 | //add dom for check 21 | _beforeA = function(setting, node, html) {}, 22 | //update zTreeObj, add method of exLib 23 | _zTreeTools = function(setting, zTreeTools) { 24 | zTreeTools.showNodes = function(nodes, options) { 25 | view.showNodes(setting, nodes, options); 26 | } 27 | zTreeTools.showNode = function(node, options) { 28 | if (!node) { 29 | return; 30 | } 31 | view.showNodes(setting, [node], options); 32 | } 33 | zTreeTools.hideNodes = function(nodes, options) { 34 | view.hideNodes(setting, nodes, options); 35 | } 36 | zTreeTools.hideNode = function(node, options) { 37 | if (!node) { 38 | return; 39 | } 40 | view.hideNodes(setting, [node], options); 41 | } 42 | 43 | var _checkNode = zTreeTools.checkNode; 44 | if (_checkNode) { 45 | zTreeTools.checkNode = function(node, checked, checkTypeFlag, callbackFlag) { 46 | if (!!node && !!node.isHidden) { 47 | return; 48 | } 49 | _checkNode.apply(zTreeTools, arguments); 50 | } 51 | } 52 | }, 53 | //method of operate data 54 | _data = { 55 | initHideForExCheck: function(setting, n) { 56 | if (n.isHidden && setting.check && setting.check.enable) { 57 | if(typeof n._nocheck == "undefined") { 58 | n._nocheck = !!n.nocheck 59 | n.nocheck = true; 60 | } 61 | n.check_Child_State = -1; 62 | if (view.repairParentChkClassWithSelf) { 63 | view.repairParentChkClassWithSelf(setting, n); 64 | } 65 | } 66 | }, 67 | initShowForExCheck: function(setting, n) { 68 | if (!n.isHidden && setting.check && setting.check.enable) { 69 | if(typeof n._nocheck != "undefined") { 70 | n.nocheck = n._nocheck; 71 | delete n._nocheck; 72 | } 73 | if (view.setChkClass) { 74 | var checkObj = $("#" + n.tId + consts.id.CHECK); 75 | view.setChkClass(setting, checkObj, n); 76 | } 77 | if (view.repairParentChkClassWithSelf) { 78 | view.repairParentChkClassWithSelf(setting, n); 79 | } 80 | } 81 | } 82 | }, 83 | //method of operate ztree dom 84 | _view = { 85 | clearOldFirstNode: function(setting, node) { 86 | var n = node.getNextNode(); 87 | while(!!n){ 88 | if (n.isFirstNode) { 89 | n.isFirstNode = false; 90 | view.setNodeLineIcos(setting, n); 91 | break; 92 | } 93 | if (n.isLastNode) { 94 | break; 95 | } 96 | n = n.getNextNode(); 97 | } 98 | }, 99 | clearOldLastNode: function(setting, node) { 100 | var n = node.getPreNode(); 101 | while(!!n){ 102 | if (n.isLastNode) { 103 | n.isLastNode = false; 104 | view.setNodeLineIcos(setting, n); 105 | break; 106 | } 107 | if (n.isFirstNode) { 108 | break; 109 | } 110 | n = n.getPreNode(); 111 | } 112 | }, 113 | makeDOMNodeMainBefore: function(html, setting, node) { 114 | html.push("
  • "); 115 | }, 116 | showNode: function(setting, node, options) { 117 | node.isHidden = false; 118 | data.initShowForExCheck(setting, node); 119 | $("#" + node.tId).show(); 120 | }, 121 | showNodes: function(setting, nodes, options) { 122 | if (!nodes || nodes.length == 0) { 123 | return; 124 | } 125 | var pList = {}, i, j; 126 | for (i=0, j=nodes.length; i 0 && !parentNode[childKey][0].isHidden) { 170 | parentNode[childKey][0].isFirstNode = true; 171 | } else if (childLength > 0) { 172 | view.setFirstNodeForHide(setting, parentNode[childKey]); 173 | } 174 | }, 175 | setLastNode: function(setting, parentNode) { 176 | var childKey = setting.data.key.children, childLength = parentNode[childKey].length; 177 | if (childLength > 0 && !parentNode[childKey][0].isHidden) { 178 | parentNode[childKey][childLength - 1].isLastNode = true; 179 | } else if (childLength > 0) { 180 | view.setLastNodeForHide(setting, parentNode[childKey]); 181 | } 182 | }, 183 | setFirstNodeForHide: function(setting, nodes) { 184 | var n,i,j; 185 | for (i=0, j=nodes.length; i=0; i--) { 225 | n = nodes[i]; 226 | if (n.isLastNode) { 227 | break; 228 | } 229 | if (!n.isHidden && !n.isLastNode) { 230 | n.isLastNode = true; 231 | view.setNodeLineIcos(setting, n); 232 | break; 233 | } else { 234 | n = null; 235 | } 236 | } 237 | return n; 238 | }, 239 | setLastNodeForShow: function(setting, nodes) { 240 | var n,i,j, last, old; 241 | for (i=nodes.length-1; i>=0; i--) { 242 | n = nodes[i]; 243 | if (!last && !n.isHidden && n.isLastNode) { 244 | last = n; 245 | break; 246 | } else if (!last && !n.isHidden && !n.isLastNode) { 247 | n.isLastNode = true; 248 | last = n; 249 | view.setNodeLineIcos(setting, n); 250 | } else if (last && n.isLastNode) { 251 | n.isLastNode = false; 252 | old = n; 253 | view.setNodeLineIcos(setting, n); 254 | break; 255 | } else { 256 | n = null; 257 | } 258 | } 259 | return {"new":last, "old":old}; 260 | } 261 | }, 262 | 263 | _z = { 264 | view: _view, 265 | data: _data 266 | }; 267 | $.extend(true, $.fn.zTree._z, _z); 268 | 269 | var zt = $.fn.zTree, 270 | tools = zt._z.tools, 271 | consts = zt.consts, 272 | view = zt._z.view, 273 | data = zt._z.data, 274 | event = zt._z.event; 275 | 276 | data.addInitNode(_initNode); 277 | data.addBeforeA(_beforeA); 278 | data.addZTreeTools(_zTreeTools); 279 | 280 | // Override method in core 281 | var _dInitNode = data.initNode; 282 | data.tmpHideParent = -1; 283 | data.initNode = function(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag) { 284 | if (data.tmpHideParent !== parentNode) { 285 | data.tmpHideParent = parentNode; 286 | var tmpPNode = (parentNode) ? parentNode: data.getRoot(setting), 287 | children = tmpPNode[setting.data.key.children]; 288 | data.tmpHideFirstNode = view.setFirstNodeForHide(setting, children); 289 | data.tmpHideLastNode = view.setLastNodeForHide(setting, children); 290 | view.setNodeLineIcos(setting, data.tmpHideFirstNode); 291 | view.setNodeLineIcos(setting, data.tmpHideLastNode); 292 | } 293 | isFirstNode = (data.tmpHideFirstNode === node); 294 | isLastNode = (data.tmpHideLastNode === node); 295 | if (_dInitNode) _dInitNode.apply(data, arguments); 296 | if (isLastNode) { 297 | view.clearOldLastNode(setting, node); 298 | } 299 | } 300 | 301 | var _makeChkFlag = data.makeChkFlag; 302 | if (!!_makeChkFlag) { 303 | data.makeChkFlag = function(setting, node) { 304 | if (!!node && !!node.isHidden) { 305 | return; 306 | } 307 | _makeChkFlag.apply(data, arguments); 308 | } 309 | } 310 | 311 | var _getTreeCheckedNodes = data.getTreeCheckedNodes; 312 | if (!!_getTreeCheckedNodes) { 313 | data.getTreeCheckedNodes = function(setting, nodes, checked, results) { 314 | if (!!nodes && nodes.length > 0) { 315 | var p = nodes[0].getParentNode(); 316 | if (!!p && !!p.isHidden) { 317 | return []; 318 | } 319 | } 320 | return _getTreeCheckedNodes.apply(data, arguments); 321 | } 322 | } 323 | 324 | var _getTreeChangeCheckedNodes = data.getTreeChangeCheckedNodes; 325 | if (!!_getTreeChangeCheckedNodes) { 326 | data.getTreeChangeCheckedNodes = function(setting, nodes, results) { 327 | if (!!nodes && nodes.length > 0) { 328 | var p = nodes[0].getParentNode(); 329 | if (!!p && !!p.isHidden) { 330 | return []; 331 | } 332 | } 333 | return _getTreeChangeCheckedNodes.apply(data, arguments); 334 | } 335 | } 336 | 337 | var _expandCollapseSonNode = view.expandCollapseSonNode; 338 | if (!!_expandCollapseSonNode) { 339 | view.expandCollapseSonNode = function(setting, node, expandFlag, animateFlag, callback) { 340 | if (!!node && !!node.isHidden) { 341 | return; 342 | } 343 | _expandCollapseSonNode.apply(view, arguments); 344 | } 345 | } 346 | 347 | var _setSonNodeCheckBox = view.setSonNodeCheckBox; 348 | if (!!_setSonNodeCheckBox) { 349 | view.setSonNodeCheckBox = function(setting, node, value, srcNode) { 350 | if (!!node && !!node.isHidden) { 351 | return; 352 | } 353 | _setSonNodeCheckBox.apply(view, arguments); 354 | } 355 | } 356 | 357 | var _repairParentChkClassWithSelf = view.repairParentChkClassWithSelf; 358 | if (!!_repairParentChkClassWithSelf) { 359 | view.repairParentChkClassWithSelf = function(setting, node) { 360 | if (!!node && !!node.isHidden) { 361 | return; 362 | } 363 | _repairParentChkClassWithSelf.apply(view, arguments); 364 | } 365 | } 366 | })(jQuery); -------------------------------------------------------------------------------- /redis/sentinel.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import weakref 4 | 5 | from redis.client import StrictRedis 6 | from redis.connection import ConnectionPool, Connection 7 | from redis.exceptions import ConnectionError, ResponseError, ReadOnlyError 8 | from redis._compat import iteritems, nativestr, xrange 9 | 10 | 11 | class MasterNotFoundError(ConnectionError): 12 | pass 13 | 14 | 15 | class SlaveNotFoundError(ConnectionError): 16 | pass 17 | 18 | 19 | class SentinelManagedConnection(Connection): 20 | def __init__(self, **kwargs): 21 | self.connection_pool = kwargs.pop('connection_pool') 22 | super(SentinelManagedConnection, self).__init__(**kwargs) 23 | 24 | def __repr__(self): 25 | pool = self.connection_pool 26 | s = '%s' % (type(self).__name__, pool.service_name) 27 | if self.host: 28 | host_info = ',host=%s,port=%s' % (self.host, self.port) 29 | s = s % host_info 30 | return s 31 | 32 | def connect_to(self, address): 33 | self.host, self.port = address 34 | super(SentinelManagedConnection, self).connect() 35 | if self.connection_pool.check_connection: 36 | self.send_command('PING') 37 | if nativestr(self.read_response()) != 'PONG': 38 | raise ConnectionError('PING failed') 39 | 40 | def connect(self): 41 | if self._sock: 42 | return # already connected 43 | if self.connection_pool.is_master: 44 | self.connect_to(self.connection_pool.get_master_address()) 45 | else: 46 | for slave in self.connection_pool.rotate_slaves(): 47 | try: 48 | return self.connect_to(slave) 49 | except ConnectionError: 50 | continue 51 | raise SlaveNotFoundError # Never be here 52 | 53 | def read_response(self): 54 | try: 55 | return super(SentinelManagedConnection, self).read_response() 56 | except ReadOnlyError: 57 | if self.connection_pool.is_master: 58 | # When talking to a master, a ReadOnlyError when likely 59 | # indicates that the previous master that we're still connected 60 | # to has been demoted to a slave and there's a new master. 61 | # calling disconnect will force the connection to re-query 62 | # sentinel during the next connect() attempt. 63 | self.disconnect() 64 | raise ConnectionError('The previous master is now a slave') 65 | raise 66 | 67 | 68 | class SentinelConnectionPool(ConnectionPool): 69 | """ 70 | Sentinel backed connection pool. 71 | 72 | If ``check_connection`` flag is set to True, SentinelManagedConnection 73 | sends a PING command right after establishing the connection. 74 | """ 75 | 76 | def __init__(self, service_name, sentinel_manager, **kwargs): 77 | kwargs['connection_class'] = kwargs.get( 78 | 'connection_class', SentinelManagedConnection) 79 | self.is_master = kwargs.pop('is_master', True) 80 | self.check_connection = kwargs.pop('check_connection', False) 81 | super(SentinelConnectionPool, self).__init__(**kwargs) 82 | self.connection_kwargs['connection_pool'] = weakref.proxy(self) 83 | self.service_name = service_name 84 | self.sentinel_manager = sentinel_manager 85 | 86 | def __repr__(self): 87 | return "%s>> from redis.sentinel import Sentinel 142 | >>> sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1) 143 | >>> master = sentinel.master_for('mymaster', socket_timeout=0.1) 144 | >>> master.set('foo', 'bar') 145 | >>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1) 146 | >>> slave.get('foo') 147 | 'bar' 148 | 149 | ``sentinels`` is a list of sentinel nodes. Each node is represented by 150 | a pair (hostname, port). 151 | 152 | ``min_other_sentinels`` defined a minimum number of peers for a sentinel. 153 | When querying a sentinel, if it doesn't meet this threshold, responses 154 | from that sentinel won't be considered valid. 155 | 156 | ``sentinel_kwargs`` is a dictionary of connection arguments used when 157 | connecting to sentinel instances. Any argument that can be passed to 158 | a normal Redis connection can be specified here. If ``sentinel_kwargs`` is 159 | not specified, any socket_timeout and socket_keepalive options specified 160 | in ``connection_kwargs`` will be used. 161 | 162 | ``connection_kwargs`` are keyword arguments that will be used when 163 | establishing a connection to a Redis server. 164 | """ 165 | 166 | def __init__(self, sentinels, min_other_sentinels=0, sentinel_kwargs=None, 167 | **connection_kwargs): 168 | # if sentinel_kwargs isn't defined, use the socket_* options from 169 | # connection_kwargs 170 | if sentinel_kwargs is None: 171 | sentinel_kwargs = dict([(k, v) 172 | for k, v in iteritems(connection_kwargs) 173 | if k.startswith('socket_') 174 | ]) 175 | self.sentinel_kwargs = sentinel_kwargs 176 | 177 | self.sentinels = [StrictRedis(hostname, port, **self.sentinel_kwargs) 178 | for hostname, port in sentinels] 179 | self.min_other_sentinels = min_other_sentinels 180 | self.connection_kwargs = connection_kwargs 181 | 182 | def __repr__(self): 183 | sentinel_addresses = [] 184 | for sentinel in self.sentinels: 185 | sentinel_addresses.append('%s:%s' % ( 186 | sentinel.connection_pool.connection_kwargs['host'], 187 | sentinel.connection_pool.connection_kwargs['port'], 188 | )) 189 | return '%s' % ( 190 | type(self).__name__, 191 | ','.join(sentinel_addresses)) 192 | 193 | def check_master_state(self, state, service_name): 194 | if not state['is_master'] or state['is_sdown'] or state['is_odown']: 195 | return False 196 | # Check if our sentinel doesn't see other nodes 197 | if state['num-other-sentinels'] < self.min_other_sentinels: 198 | return False 199 | return True 200 | 201 | def discover_master(self, service_name): 202 | """ 203 | Asks sentinel servers for the Redis master's address corresponding 204 | to the service labeled ``service_name``. 205 | 206 | Returns a pair (address, port) or raises MasterNotFoundError if no 207 | master is found. 208 | """ 209 | for sentinel_no, sentinel in enumerate(self.sentinels): 210 | try: 211 | masters = sentinel.sentinel_masters() 212 | except ConnectionError: 213 | continue 214 | state = masters.get(service_name) 215 | if state and self.check_master_state(state, service_name): 216 | # Put this sentinel at the top of the list 217 | self.sentinels[0], self.sentinels[sentinel_no] = ( 218 | sentinel, self.sentinels[0]) 219 | return state['ip'], state['port'] 220 | raise MasterNotFoundError("No master found for %r" % (service_name,)) 221 | 222 | def filter_slaves(self, slaves): 223 | "Remove slaves that are in an ODOWN or SDOWN state" 224 | slaves_alive = [] 225 | for slave in slaves: 226 | if slave['is_odown'] or slave['is_sdown']: 227 | continue 228 | slaves_alive.append((slave['ip'], slave['port'])) 229 | return slaves_alive 230 | 231 | def discover_slaves(self, service_name): 232 | "Returns a list of alive slaves for service ``service_name``" 233 | for sentinel in self.sentinels: 234 | try: 235 | slaves = sentinel.sentinel_slaves(service_name) 236 | except (ConnectionError, ResponseError): 237 | continue 238 | slaves = self.filter_slaves(slaves) 239 | if slaves: 240 | return slaves 241 | return [] 242 | 243 | def master_for(self, service_name, redis_class=StrictRedis, 244 | connection_pool_class=SentinelConnectionPool, **kwargs): 245 | """ 246 | Returns a redis client instance for the ``service_name`` master. 247 | 248 | A SentinelConnectionPool class is used to retrive the master's 249 | address before establishing a new connection. 250 | 251 | NOTE: If the master's address has changed, any cached connections to 252 | the old master are closed. 253 | 254 | By default clients will be a redis.StrictRedis instance. Specify a 255 | different class to the ``redis_class`` argument if you desire 256 | something different. 257 | 258 | The ``connection_pool_class`` specifies the connection pool to use. 259 | The SentinelConnectionPool will be used by default. 260 | 261 | All other keyword arguments are merged with any connection_kwargs 262 | passed to this class and passed to the connection pool as keyword 263 | arguments to be used to initialize Redis connections. 264 | """ 265 | kwargs['is_master'] = True 266 | connection_kwargs = dict(self.connection_kwargs) 267 | connection_kwargs.update(kwargs) 268 | return redis_class(connection_pool=connection_pool_class( 269 | service_name, self, **connection_kwargs)) 270 | 271 | def slave_for(self, service_name, redis_class=StrictRedis, 272 | connection_pool_class=SentinelConnectionPool, **kwargs): 273 | """ 274 | Returns redis client instance for the ``service_name`` slave(s). 275 | 276 | A SentinelConnectionPool class is used to retrive the slave's 277 | address before establishing a new connection. 278 | 279 | By default clients will be a redis.StrictRedis instance. Specify a 280 | different class to the ``redis_class`` argument if you desire 281 | something different. 282 | 283 | The ``connection_pool_class`` specifies the connection pool to use. 284 | The SentinelConnectionPool will be used by default. 285 | 286 | All other keyword arguments are merged with any connection_kwargs 287 | passed to this class and passed to the connection pool as keyword 288 | arguments to be used to initialize Redis connections. 289 | """ 290 | kwargs['is_master'] = False 291 | connection_kwargs = dict(self.connection_kwargs) 292 | connection_kwargs.update(kwargs) 293 | return redis_class(connection_pool=connection_pool_class( 294 | service_name, self, **connection_kwargs)) 295 | -------------------------------------------------------------------------------- /mole/request.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Request 4 | 5 | Request class for wsgi 6 | """ 7 | import sys 8 | import cgi 9 | import threading 10 | import base64 11 | import tempfile 12 | from urlparse import urlunsplit 13 | from urllib import quote as urlquote 14 | try: from collections import MutableMapping as DictMixin 15 | except ImportError: # pragma: no cover 16 | from UserDict import DictMixin 17 | 18 | try: from urlparse import parse_qs 19 | except ImportError: # pragma: no cover 20 | from cgi import parse_qs 21 | from StringIO import StringIO as BytesIO 22 | from tempfile import TemporaryFile 23 | from Cookie import SimpleCookie 24 | 25 | if sys.version_info >= (3,0,0): # pragma: no cover 26 | # See Request.POST 27 | from io import TextIOWrapper 28 | class NCTextIOWrapper(TextIOWrapper): 29 | ''' Garbage collecting an io.TextIOWrapper(buffer) instance closes the 30 | wrapped buffer. This subclass keeps it open. ''' 31 | def close(self): pass 32 | else: 33 | NCTextIOWrapper = None 34 | 35 | import utils 36 | from cookie import cookie_decode 37 | from structs import DictProperty,MultiDict 38 | import const 39 | 40 | def parse_auth(header): 41 | """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None""" 42 | try: 43 | method, data = header.split(None, 1) 44 | if method.lower() == 'basic': 45 | name, pwd = base64.b64decode(data).split(':', 1) 46 | return name, pwd 47 | except (KeyError, ValueError, TypeError): 48 | return None 49 | 50 | class WSGIHeaderDict(DictMixin): 51 | ''' This dict-like class wraps a WSGI environ dict and provides convenient 52 | access to HTTP_* fields. Keys and values are native strings 53 | (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI 54 | environment contains non-native string values, these are de- or encoded 55 | using a lossless 'latin1' character set. 56 | 57 | The API will remain stable even on changes to the relevant PEPs. 58 | Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one 59 | that uses non-native strings.) 60 | ''' 61 | 62 | def __init__(self, environ): 63 | self.environ = environ 64 | 65 | def _ekey(self, key): # Translate header field name to environ key. 66 | return 'HTTP_' + key.replace('-','_').upper() 67 | 68 | def raw(self, key, default=None): 69 | ''' Return the header value as is (may be bytes or unicode). ''' 70 | return self.environ.get(self._ekey(key), default) 71 | 72 | def __getitem__(self, key): 73 | return utils.tonat(self.environ[self._ekey(key)], 'latin1') 74 | 75 | def __setitem__(self, key, value): 76 | raise TypeError("%s is read-only." % self.__class__) 77 | 78 | def __delitem__(self, key): 79 | raise TypeError("%s is read-only." % self.__class__) 80 | 81 | def __iter__(self): 82 | for key in self.environ: 83 | if key[:5] == 'HTTP_': 84 | yield key[5:].replace('_', '-').title() 85 | 86 | def keys(self): return list(self) 87 | def __len__(self): return len(list(self)) 88 | def __contains__(self, key): return self._ekey(key) in self.environ 89 | 90 | class Request(threading.local, DictMixin): 91 | """ Represents a single HTTP request using thread-local attributes. 92 | The Request object wraps a WSGI environment and can be used as such. 93 | """ 94 | def __init__(self, environ=None): 95 | """ Create a new Request instance. 96 | 97 | You usually don't do this but use the global `mole.request` 98 | instance instead. 99 | """ 100 | self.bind(environ or {},) 101 | 102 | def bind(self, environ): 103 | """ Bind a new WSGI environment. 104 | 105 | This is done automatically for the global `mole.request` 106 | instance on every request. 107 | """ 108 | self.environ = environ 109 | # These attributes are used anyway, so it is ok to compute them here 110 | self.path = '/' + environ.get('PATH_INFO', '/').lstrip('/') 111 | self.method = environ.get('REQUEST_METHOD', 'GET').upper() 112 | 113 | @property 114 | def _environ(self): 115 | utils.depr("Request._environ renamed to Request.environ") 116 | return self.environ 117 | 118 | def copy(self): 119 | ''' Returns a copy of self ''' 120 | return Request(self.environ.copy()) 121 | 122 | def path_shift(self, shift=1): 123 | ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa. 124 | 125 | :param shift: The number of path fragments to shift. May be negative 126 | to change the shift direction. (default: 1) 127 | ''' 128 | script_name = self.environ.get('SCRIPT_NAME','/') 129 | self['SCRIPT_NAME'], self.path = path_shift(script_name, self.path, shift) 130 | self['PATH_INFO'] = self.path 131 | 132 | def __getitem__(self, key): return self.environ[key] 133 | def __delitem__(self, key): self[key] = ""; del(self.environ[key]) 134 | def __iter__(self): return iter(self.environ) 135 | def __len__(self): return len(self.environ) 136 | def keys(self): return self.environ.keys() 137 | def __setitem__(self, key, value): 138 | """ Shortcut for Request.environ.__setitem__ """ 139 | self.environ[key] = value 140 | todelete = [] 141 | if key in ('PATH_INFO','REQUEST_METHOD'): 142 | self.bind(self.environ) 143 | elif key == 'wsgi.input': todelete = ('body','forms','files','params') 144 | elif key == 'QUERY_STRING': todelete = ('get','params') 145 | elif key.startswith('HTTP_'): todelete = ('headers', 'cookies') 146 | for key in todelete: 147 | if 'mole.' + key in self.environ: 148 | del self.environ['mole.' + key] 149 | 150 | @property 151 | def query_string(self): 152 | """ The part of the URL following the '?'. """ 153 | return self.environ.get('QUERY_STRING', '') 154 | 155 | @property 156 | def fullpath(self): 157 | """ Request path including SCRIPT_NAME (if present). """ 158 | return self.environ.get('SCRIPT_NAME', '').rstrip('/') + self.path 159 | 160 | @property 161 | def url(self): 162 | """ Full URL as requested by the client (computed). 163 | 164 | This value is constructed out of different environment variables 165 | and includes scheme, host, port, scriptname, path and query string. 166 | """ 167 | scheme = self.environ.get('wsgi.url_scheme', 'http') 168 | host = self.environ.get('HTTP_X_FORWARDED_HOST') 169 | host = host or self.environ.get('HTTP_HOST', None) 170 | if not host: 171 | host = self.environ.get('SERVER_NAME') 172 | port = self.environ.get('SERVER_PORT', '80') 173 | if (scheme, port) not in (('https','443'), ('http','80')): 174 | host += ':' + port 175 | parts = (scheme, host, urlquote(self.fullpath), self.query_string, '') 176 | return urlunsplit(parts) 177 | 178 | @property 179 | def content_length(self): 180 | """ Content-Length header as an integer, -1 if not specified """ 181 | return int(self.environ.get('CONTENT_LENGTH', '') or -1) 182 | 183 | @property 184 | def header(self): 185 | utils.depr("The Request.header property was renamed to Request.headers") 186 | return self.headers 187 | 188 | @DictProperty('environ', 'mole.headers', read_only=True) 189 | def headers(self): 190 | ''' Request HTTP Headers stored in a :cls:`HeaderDict`. ''' 191 | return WSGIHeaderDict(self.environ) 192 | 193 | @DictProperty('environ', 'mole.get', read_only=True) 194 | def GET(self): 195 | """ The QUERY_STRING parsed into an instance of :class:`MultiDict`. """ 196 | data = parse_qs(self.query_string, keep_blank_values=True) 197 | get = self.environ['mole.get'] = MultiDict() 198 | for key, values in data.iteritems(): 199 | for value in values: 200 | get[key] = value 201 | return get 202 | 203 | @DictProperty('environ', 'mole.post', read_only=True) 204 | def POST(self): 205 | """ The combined values from :attr:`forms` and :attr:`files`. Values are 206 | either strings (form values) or instances of 207 | :class:`cgi.FieldStorage` (file uploads). 208 | """ 209 | post = MultiDict() 210 | safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi 211 | for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): 212 | if key in self.environ: safe_env[key] = self.environ[key] 213 | if NCTextIOWrapper: 214 | fb = NCTextIOWrapper(self.body, encoding='ISO-8859-1', newline='\n') 215 | else: 216 | fb = self.body 217 | data = cgi.FieldStorage(fp=fb, environ=safe_env, keep_blank_values=True) 218 | for item in data.list or []: 219 | post[item.name] = item if item.filename else item.value 220 | return post 221 | 222 | @DictProperty('environ', 'mole.forms', read_only=True) 223 | def forms(self): 224 | """ POST form values parsed into an instance of :class:`MultiDict`. 225 | 226 | This property contains form values parsed from an `url-encoded` 227 | or `multipart/form-data` encoded POST request bidy. The values are 228 | native strings. 229 | """ 230 | forms = MultiDict() 231 | for name, item in self.POST.iterallitems(): 232 | if not hasattr(item, 'filename'): 233 | forms[name] = item 234 | return forms 235 | 236 | @DictProperty('environ', 'mole.files', read_only=True) 237 | def files(self): 238 | """ File uploads parsed into an instance of :class:`MultiDict`. 239 | 240 | This property contains file uploads parsed from an 241 | `multipart/form-data` encoded POST request body. The values are 242 | instances of :class:`cgi.FieldStorage`. 243 | """ 244 | files = MultiDict() 245 | for name, item in self.POST.iterallitems(): 246 | if hasattr(item, 'filename'): 247 | files[name] = item 248 | return files 249 | 250 | @DictProperty('environ', 'mole.params', read_only=True) 251 | def params(self): 252 | """ A combined :class:`MultiDict` with values from :attr:`forms` and 253 | :attr:`GET`. File-uploads are not included. """ 254 | params = MultiDict(self.GET) 255 | for key, value in self.forms.iterallitems(): 256 | params[key] = value 257 | return params 258 | 259 | @DictProperty('environ', 'mole.req', read_only=True) 260 | def REQUEST(self): 261 | """ A combined :class:`MultiDict` with values from :attr:`forms` and 262 | :attr:`GET`. File-uploads are not included. """ 263 | req = MultiDict(self.GET) 264 | for key, value in self.forms.iterallitems(): 265 | req[key] = value 266 | return req 267 | 268 | @DictProperty('environ', 'mole.body', read_only=True) 269 | def _body(self): 270 | """ The HTTP request body as a seekable file-like object. 271 | 272 | This property returns a copy of the `wsgi.input` stream and should 273 | be used instead of `environ['wsgi.input']`. 274 | """ 275 | maxread = max(0, self.content_length) 276 | stream = self.environ['wsgi.input'] 277 | body = BytesIO() if maxread < const.MEMFILE_MAX else TemporaryFile(mode='w+b') 278 | while maxread > 0: 279 | part = stream.read(min(maxread, const.MEMFILE_MAX)) 280 | if not part: break 281 | body.write(part) 282 | maxread -= len(part) 283 | self.environ['wsgi.input'] = body 284 | body.seek(0) 285 | return body 286 | 287 | @property 288 | def body(self): 289 | self._body.seek(0) 290 | return self._body 291 | 292 | @property 293 | def auth(self): #TODO: Tests and docs. Add support for digest. namedtuple? 294 | """ HTTP authorization data as a (user, passwd) tuple. (experimental) 295 | 296 | This implementation currently only supports basic auth and returns 297 | None on errors. 298 | """ 299 | return parse_auth(self.headers.get('Authorization','')) 300 | 301 | @DictProperty('environ', 'mole.cookies', read_only=True) 302 | def COOKIES(self): 303 | """ Cookies parsed into a dictionary. Secure cookies are NOT decoded 304 | automatically. See :meth:`get_cookie` for details. 305 | """ 306 | raw_dict = SimpleCookie(self.headers.get('Cookie','')) 307 | cookies = {} 308 | for cookie in raw_dict.itervalues(): 309 | cookies[cookie.key] = cookie.value 310 | return cookies 311 | 312 | def get_cookie(self, key, secret=None): 313 | """ Return the content of a cookie. To read a `Secure Cookies`, use the 314 | same `secret` as used to create the cookie (see 315 | :meth:`Response.set_cookie`). If anything goes wrong, None is 316 | returned. 317 | """ 318 | value = self.COOKIES.get(key) 319 | if secret and value: 320 | dec = cookie_decode(value, secret) # (key, value) tuple or None 321 | return dec[1] if dec and dec[0] == key else None 322 | return value or None 323 | 324 | @property 325 | def is_ajax(self): 326 | ''' True if the request was generated using XMLHttpRequest ''' 327 | #TODO: write tests 328 | return self.header.get('X-Requested-With') == 'XMLHttpRequest' -------------------------------------------------------------------------------- /mole/structs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from types import GeneratorType 3 | 4 | import functools 5 | try: from collections import MutableMapping as DictMixin 6 | except ImportError: # pragma: no cover 7 | from UserDict import DictMixin 8 | 9 | 10 | class MultiDict(DictMixin): 11 | """ A dict that remembers old values for each key """ 12 | # collections.MutableMapping would be better for Python >= 2.6 13 | def __init__(self, *a, **k): 14 | self.dict = dict() 15 | for k, v in dict(*a, **k).iteritems(): 16 | self[k] = v 17 | 18 | def __len__(self): return len(self.dict) 19 | def __iter__(self): return iter(self.dict) 20 | def __contains__(self, key): return key in self.dict 21 | def __delitem__(self, key): del self.dict[key] 22 | def keys(self): return self.dict.keys() 23 | def __getitem__(self, key): return self.get(key, KeyError, -1) 24 | def __setitem__(self, key, value): self.append(key, value) 25 | 26 | def append(self, key, value): self.dict.setdefault(key, []).append(value) 27 | def replace(self, key, value): self.dict[key] = [value] 28 | def getall(self, key): return self.dict.get(key) or [] 29 | 30 | def get(self, key, default=None, index=-1): 31 | if key not in self.dict and default != KeyError: 32 | return [default][index] 33 | return self.dict[key][index] 34 | 35 | def iterallitems(self): 36 | for key, values in self.dict.iteritems(): 37 | for value in values: 38 | yield key, value 39 | 40 | def has_key(self, key): 41 | return key in self.dict 42 | 43 | class DictProperty(object): 44 | ''' Property that maps to a key in a local dict-like attribute. ''' 45 | def __init__(self, attr, key=None, read_only=False): 46 | self.attr, self.key, self.read_only = attr, key, read_only 47 | 48 | def __call__(self, func): 49 | functools.update_wrapper(self, func, updated=[]) 50 | self.getter, self.key = func, self.key or func.__name__ 51 | return self 52 | 53 | def __get__(self, obj, cls): 54 | if not obj: return self 55 | key, storage = self.key, getattr(obj, self.attr) 56 | if key not in storage: storage[key] = self.getter(obj) 57 | return storage[key] 58 | 59 | def __set__(self, obj, value): 60 | if self.read_only: raise AttributeError("Read-Only property.") 61 | getattr(obj, self.attr)[self.key] = value 62 | 63 | def __delete__(self, obj): 64 | if self.read_only: raise AttributeError("Read-Only property.") 65 | del getattr(obj, self.attr)[self.key] 66 | 67 | def cached_property(func): 68 | ''' A property that, if accessed, replaces itself with the computed 69 | value. Subsequent accesses won't call the getter again. ''' 70 | return DictProperty('__dict__')(func) 71 | 72 | class lazy_attribute(object): # Does not need configuration -> lower-case name 73 | ''' A property that caches itself to the class object. ''' 74 | def __init__(self, func): 75 | functools.update_wrapper(self, func, updated=[]) 76 | self.getter = func 77 | 78 | def __get__(self, obj, cls): 79 | value = self.getter(cls) 80 | setattr(cls, self.__name__, value) 81 | return value 82 | 83 | class SortedDict(dict): 84 | """ 85 | A dictionary that keeps its keys in the order in which they're inserted. 86 | """ 87 | def __new__(cls, *args, **kwargs): 88 | instance = super(SortedDict, cls).__new__(cls, *args, **kwargs) 89 | instance.keyOrder = [] 90 | return instance 91 | 92 | def __init__(self, data=None): 93 | if data is None: 94 | data = {} 95 | elif isinstance(data, GeneratorType): 96 | # Unfortunately we need to be able to read a generator twice. Once 97 | # to get the data into self with our super().__init__ call and a 98 | # second time to setup keyOrder correctly 99 | data = list(data) 100 | super(SortedDict, self).__init__(data) 101 | if isinstance(data, dict): 102 | self.keyOrder = data.keys() 103 | else: 104 | self.keyOrder = [] 105 | for key, value in data: 106 | if key not in self.keyOrder: 107 | self.keyOrder.append(key) 108 | 109 | def __deepcopy__(self, memo): 110 | return self.__class__([(key, deepcopy(value, memo)) 111 | for key, value in self.iteritems()]) 112 | 113 | def __setitem__(self, key, value): 114 | if key not in self: 115 | self.keyOrder.append(key) 116 | super(SortedDict, self).__setitem__(key, value) 117 | 118 | def __delitem__(self, key): 119 | super(SortedDict, self).__delitem__(key) 120 | self.keyOrder.remove(key) 121 | 122 | def __iter__(self): 123 | return iter(self.keyOrder) 124 | 125 | def pop(self, k, *args): 126 | result = super(SortedDict, self).pop(k, *args) 127 | try: 128 | self.keyOrder.remove(k) 129 | except ValueError: 130 | # Key wasn't in the dictionary in the first place. No problem. 131 | pass 132 | return result 133 | 134 | def popitem(self): 135 | result = super(SortedDict, self).popitem() 136 | self.keyOrder.remove(result[0]) 137 | return result 138 | 139 | def items(self): 140 | return zip(self.keyOrder, self.values()) 141 | 142 | def iteritems(self): 143 | for key in self.keyOrder: 144 | yield key, self[key] 145 | 146 | def keys(self): 147 | return self.keyOrder[:] 148 | 149 | def iterkeys(self): 150 | return iter(self.keyOrder) 151 | 152 | def values(self): 153 | return map(self.__getitem__, self.keyOrder) 154 | 155 | def itervalues(self): 156 | for key in self.keyOrder: 157 | yield self[key] 158 | 159 | def update(self, dict_): 160 | for k, v in dict_.iteritems(): 161 | self[k] = v 162 | 163 | def setdefault(self, key, default): 164 | if key not in self: 165 | self.keyOrder.append(key) 166 | return super(SortedDict, self).setdefault(key, default) 167 | 168 | def value_for_index(self, index): 169 | """Returns the value of the item at the given zero-based index.""" 170 | return self[self.keyOrder[index]] 171 | 172 | def insert(self, index, key, value): 173 | """Inserts the key, value pair before the item with the given index.""" 174 | if key in self.keyOrder: 175 | n = self.keyOrder.index(key) 176 | del self.keyOrder[n] 177 | if n < index: 178 | index -= 1 179 | self.keyOrder.insert(index, key) 180 | super(SortedDict, self).__setitem__(key, value) 181 | 182 | def copy(self): 183 | """Returns a copy of this object.""" 184 | # This way of initializing the copy means it works for subclasses, too. 185 | obj = self.__class__(self) 186 | obj.keyOrder = self.keyOrder[:] 187 | return obj 188 | 189 | def __repr__(self): 190 | """ 191 | Replaces the normal dict.__repr__ with a version that returns the keys 192 | in their sorted order. 193 | """ 194 | return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) 195 | 196 | def clear(self): 197 | super(SortedDict, self).clear() 198 | self.keyOrder = [] 199 | 200 | 201 | class MultiValueDict(dict): 202 | """ 203 | A subclass of dictionary customized to handle multiple values for the 204 | same key. 205 | 206 | >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) 207 | >>> d['name'] 208 | 'Simon' 209 | >>> d.getlist('name') 210 | ['Adrian', 'Simon'] 211 | >>> d.get('lastname', 'nonexistent') 212 | 'nonexistent' 213 | >>> d.setlist('lastname', ['Holovaty', 'Willison']) 214 | 215 | This class exists to solve the irritating problem raised by cgi.parse_qs, 216 | which returns a list for every key, even though most Web forms submit 217 | single name-value pairs. 218 | """ 219 | def __init__(self, key_to_list_mapping=()): 220 | super(MultiValueDict, self).__init__(key_to_list_mapping) 221 | 222 | def __repr__(self): 223 | return "<%s: %s>" % (self.__class__.__name__, 224 | super(MultiValueDict, self).__repr__()) 225 | 226 | def __getitem__(self, key): 227 | """ 228 | Returns the last data value for this key, or [] if it's an empty list; 229 | raises KeyError if not found. 230 | """ 231 | try: 232 | list_ = super(MultiValueDict, self).__getitem__(key) 233 | except KeyError: 234 | raise MultiValueDictKeyError("Key %r not found in %r" % (key, self)) 235 | try: 236 | return list_[-1] 237 | except IndexError: 238 | return [] 239 | 240 | def __setitem__(self, key, value): 241 | super(MultiValueDict, self).__setitem__(key, [value]) 242 | 243 | def __copy__(self): 244 | return self.__class__(super(MultiValueDict, self).items()) 245 | 246 | def __deepcopy__(self, memo=None): 247 | import django.utils.copycompat as copy 248 | if memo is None: 249 | memo = {} 250 | result = self.__class__() 251 | memo[id(self)] = result 252 | for key, value in dict.items(self): 253 | dict.__setitem__(result, copy.deepcopy(key, memo), 254 | copy.deepcopy(value, memo)) 255 | return result 256 | 257 | def __getstate__(self): 258 | obj_dict = self.__dict__.copy() 259 | obj_dict['_data'] = dict([(k, self.getlist(k)) for k in self]) 260 | return obj_dict 261 | 262 | def __setstate__(self, obj_dict): 263 | data = obj_dict.pop('_data', {}) 264 | for k, v in data.items(): 265 | self.setlist(k, v) 266 | self.__dict__.update(obj_dict) 267 | 268 | def get(self, key, default=None): 269 | """ 270 | Returns the last data value for the passed key. If key doesn't exist 271 | or value is an empty list, then default is returned. 272 | """ 273 | try: 274 | val = self[key] 275 | except KeyError: 276 | return default 277 | if val == []: 278 | return default 279 | return val 280 | 281 | def getlist(self, key): 282 | """ 283 | Returns the list of values for the passed key. If key doesn't exist, 284 | then an empty list is returned. 285 | """ 286 | try: 287 | return super(MultiValueDict, self).__getitem__(key) 288 | except KeyError: 289 | return [] 290 | 291 | def setlist(self, key, list_): 292 | super(MultiValueDict, self).__setitem__(key, list_) 293 | 294 | def setdefault(self, key, default=None): 295 | if key not in self: 296 | self[key] = default 297 | return self[key] 298 | 299 | def setlistdefault(self, key, default_list=()): 300 | if key not in self: 301 | self.setlist(key, default_list) 302 | return self.getlist(key) 303 | 304 | def appendlist(self, key, value): 305 | """Appends an item to the internal list associated with key.""" 306 | self.setlistdefault(key, []) 307 | super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value]) 308 | 309 | def items(self): 310 | """ 311 | Returns a list of (key, value) pairs, where value is the last item in 312 | the list associated with the key. 313 | """ 314 | return [(key, self[key]) for key in self.keys()] 315 | 316 | def iteritems(self): 317 | """ 318 | Yields (key, value) pairs, where value is the last item in the list 319 | associated with the key. 320 | """ 321 | for key in self.keys(): 322 | yield (key, self[key]) 323 | 324 | def lists(self): 325 | """Returns a list of (key, list) pairs.""" 326 | return super(MultiValueDict, self).items() 327 | 328 | def iterlists(self): 329 | """Yields (key, list) pairs.""" 330 | return super(MultiValueDict, self).iteritems() 331 | 332 | def values(self): 333 | """Returns a list of the last value on every key list.""" 334 | return [self[key] for key in self.keys()] 335 | 336 | def itervalues(self): 337 | """Yield the last value on every key list.""" 338 | for key in self.iterkeys(): 339 | yield self[key] 340 | 341 | def copy(self): 342 | """Returns a copy of this object.""" 343 | return self.__deepcopy__() 344 | 345 | def update(self, *args, **kwargs): 346 | """ 347 | update() extends rather than replaces existing key lists. 348 | Also accepts keyword args. 349 | """ 350 | if len(args) > 1: 351 | raise TypeError("update expected at most 1 arguments, got %d" % len(args)) 352 | if args: 353 | other_dict = args[0] 354 | if isinstance(other_dict, MultiValueDict): 355 | for key, value_list in other_dict.lists(): 356 | self.setlistdefault(key, []).extend(value_list) 357 | else: 358 | try: 359 | for key, value in other_dict.items(): 360 | self.setlistdefault(key, []).append(value) 361 | except TypeError: 362 | raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") 363 | for key, value in kwargs.iteritems(): 364 | self.setlistdefault(key, []).append(value) 365 | 366 | class MergeDict(object): 367 | """ 368 | A simple class for creating new "virtual" dictionaries that actually look 369 | up values in more than one dictionary, passed in the constructor. 370 | 371 | If a key appears in more than one of the given dictionaries, only the 372 | first occurrence will be used. 373 | """ 374 | def __init__(self, *dicts): 375 | self.dicts = dicts 376 | 377 | def __getitem__(self, key): 378 | for dict_ in self.dicts: 379 | try: 380 | return dict_[key] 381 | except KeyError: 382 | pass 383 | raise KeyError 384 | 385 | def __copy__(self): 386 | return self.__class__(*self.dicts) 387 | 388 | def get(self, key, default=None): 389 | try: 390 | return self[key] 391 | except KeyError: 392 | return default 393 | 394 | def getlist(self, key): 395 | for dict_ in self.dicts: 396 | if key in dict_.keys(): 397 | return dict_.getlist(key) 398 | return [] 399 | 400 | def iteritems(self): 401 | seen = set() 402 | for dict_ in self.dicts: 403 | for item in dict_.iteritems(): 404 | k, v = item 405 | if k in seen: 406 | continue 407 | seen.add(k) 408 | yield item 409 | 410 | def iterkeys(self): 411 | for k, v in self.iteritems(): 412 | yield k 413 | 414 | def itervalues(self): 415 | for k, v in self.iteritems(): 416 | yield v 417 | 418 | def items(self): 419 | return list(self.iteritems()) 420 | 421 | def keys(self): 422 | return list(self.iterkeys()) 423 | 424 | def values(self): 425 | return list(self.itervalues()) 426 | 427 | def has_key(self, key): 428 | for dict_ in self.dicts: 429 | if key in dict_: 430 | return True 431 | return False 432 | 433 | __contains__ = has_key 434 | __iter__ = iterkeys 435 | 436 | def copy(self): 437 | """Returns a copy of this object.""" 438 | return self.__copy__() 439 | -------------------------------------------------------------------------------- /media/bootstrap/bsie/js/bootstrap-ie.old.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.eb = $.eb || {}; 3 | 4 | // $.eb.ie = function (min,max) { 5 | // // return true; 6 | // if ($.browser.msie) { 7 | // var v = Math.floor($.browser.version); 8 | // if (v >= min && v <= max) { 9 | // return true; 10 | // } 11 | // } 12 | // return false; 13 | // } 14 | $.eb.ie6 = function () { 15 | return navigator.userAgent.toLowerCase().indexOf('msie 6.0') > -1; 16 | // alert(navigator.userAgent.toLowerCase().indexOf('msie 6.0')); 17 | } 18 | 19 | 20 | $.eb.color = function () { 21 | var pad = function(num, totalChars) { 22 | var pad = '0'; 23 | num = num + ''; 24 | while (num.length < totalChars) { 25 | num = pad + num; 26 | } 27 | return num; 28 | }; 29 | 30 | // Ratio is between 0 and 1 31 | this.changeColor = function(color, ratio, darker) { 32 | // Trim trailing/leading whitespace 33 | color = color.replace(/^\s*|\s*$/, ''); 34 | 35 | // Expand three-digit hex 36 | color = color.replace( 37 | /^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i, 38 | '#$1$1$2$2$3$3' 39 | ); 40 | 41 | // Calculate ratio 42 | var difference = Math.round(ratio * 256) * (darker ? -1 : 1), 43 | // Determine if input is RGB(A) 44 | rgb = color.match(new RegExp('^rgba?\\(\\s*' + 45 | '(\\d|[1-9]\\d|1\\d{2}|2[0-4][0-9]|25[0-5])' + 46 | '\\s*,\\s*' + 47 | '(\\d|[1-9]\\d|1\\d{2}|2[0-4][0-9]|25[0-5])' + 48 | '\\s*,\\s*' + 49 | '(\\d|[1-9]\\d|1\\d{2}|2[0-4][0-9]|25[0-5])' + 50 | '(?:\\s*,\\s*' + 51 | '(0|1|0?\\.\\d+))?' + 52 | '\\s*\\)$' 53 | , 'i')), 54 | alpha = !!rgb && rgb[4] != null ? rgb[4] : null, 55 | 56 | // Convert hex to decimal 57 | decimal = !!rgb? [rgb[1], rgb[2], rgb[3]] : color.replace( 58 | /^#?([a-f0-9][a-f0-9])([a-f0-9][a-f0-9])([a-f0-9][a-f0-9])/i, 59 | function() { 60 | return parseInt(arguments[1], 16) + ',' + 61 | parseInt(arguments[2], 16) + ',' + 62 | parseInt(arguments[3], 16); 63 | } 64 | ).split(/,/), 65 | returnValue; 66 | 67 | // Return RGB(A) 68 | return !!rgb ? 69 | 'rgb' + (alpha !== null ? 'a' : '') + '(' + 70 | Math[darker ? 'max' : 'min']( 71 | parseInt(decimal[0], 10) + difference, darker ? 0 : 255 72 | ) + ', ' + 73 | Math[darker ? 'max' : 'min']( 74 | parseInt(decimal[1], 10) + difference, darker ? 0 : 255 75 | ) + ', ' + 76 | Math[darker ? 'max' : 'min']( 77 | parseInt(decimal[2], 10) + difference, darker ? 0 : 255 78 | ) + 79 | (alpha !== null ? ', ' + alpha : '') + 80 | ')' : 81 | // Return hex 82 | [ 83 | '#', 84 | pad(Math[darker ? 'max' : 'min']( 85 | parseInt(decimal[0], 10) + difference, darker ? 0 : 255 86 | ).toString(16), 2), 87 | pad(Math[darker ? 'max' : 'min']( 88 | parseInt(decimal[1], 10) + difference, darker ? 0 : 255 89 | ).toString(16), 2), 90 | pad(Math[darker ? 'max' : 'min']( 91 | parseInt(decimal[2], 10) + difference, darker ? 0 : 255 92 | ).toString(16), 2) 93 | ].join(''); 94 | }; 95 | this.lighten = function(color, ratio) { 96 | return changeColor(color, ratio, false); 97 | }; 98 | this.darken = function(color, ratio) { 99 | return changeColor(color, ratio, true); 100 | }; 101 | return this; 102 | }(); 103 | 104 | 105 | function bootstrapIE6(el) { 106 | var dropdownWidthFix = function (el) { 107 | el.each(function () { 108 | var w = 0; 109 | $(this).children('li').each(function() { 110 | var aw = $(this).outerWidth(); 111 | if (aw > w) w = aw; 112 | }); 113 | 114 | $(this).width(w); 115 | }); 116 | } 117 | 118 | if ($.eb.ie6()) { 119 | el = el || $('html'); 120 | 121 | //------------- 122 | // GRID 123 | //------------- 124 | $('.row-fluid [class*="span"]:first-child, .row [class*="span"]:first-child').addClass('span-first-child'); 125 | 126 | //------------- 127 | // dropdown 128 | //------------- 129 | // fix for IE6 not support li:hover 130 | var lis = ['dropdown-submenu']; 131 | for (var i in lis) { 132 | var child = 'li.' + lis[i]; 133 | var hover = lis[i] + '-hover'; 134 | $('ul', el).on('mouseenter', child, function () { 135 | $(this).addClass(hover); 136 | }).on('mouseleave', child, function () { 137 | $(this).removeClass(hover); 138 | }); 139 | } 140 | 141 | /// fix :after selector -- dropdown-submenu > a:after 142 | $('.dropdown-submenu > a', el).after(''); 143 | 144 | /// fix multi class selector -- .dropdown-submenu.pull-left 145 | $('.dropdown-submenu.pull-left', el).removeClass('pull-left').addClass('dropdown-submenu-pull-left'); 146 | // $('.navbar .nav.pull-right').removeClass('pull-right').addClass('nav-pull-right'); 147 | 148 | /// fix ul li 100% width bug, set ul width to max width of it's sub li 149 | dropdownWidthFix($('.dropdown-menu:visible', el)); 150 | 151 | 152 | //------------- 153 | // buttons 154 | //------------- 155 | var btnColorCls = ['btn-primary','btn-warning','btn-danger','btn-success','btn-info','btn-inverse']; 156 | var btnSizeCls = ['btn-mini','btn-small','btn-large']; 157 | $('.btn-group', el).parent().find('.btn-group:eq(0)').addClass('btn-group-first'); 158 | $('.btn', el).parent().find('.btn:eq(0)').addClass('btn-first'); 159 | 160 | // fix button:hover 161 | $('body', el).on('mouseenter', '.btn', function () { 162 | var btn = $(this); 163 | var hover = 'btn-hover'; 164 | btn.data('ie6hover',hover); 165 | $.each(btnColorCls, function (k,v) { 166 | if (btn.hasClass(v)) { 167 | hover = v + '-hover'; 168 | btn.data('ie6hover',hover); 169 | return false; 170 | } 171 | }); 172 | btn.addClass(hover); 173 | }).on('mouseleave', '.btn', function () { 174 | var btn = $(this); 175 | var hover = btn.data('ie6hover'); 176 | btn.removeData('ie6hover'); 177 | if (hover) btn.removeClass(hover); 178 | }); 179 | 180 | // fix .btn.dropdown-toggle, .btn-primary.dropdown-toggle ... 181 | // fix .btn.dropdown-toggle, .btn-small.dropdown-toggle ... 182 | $('.btn.dropdown-toggle', el).each(function () { 183 | var btn = $(this); 184 | var ddt = 'btn-dropdown-toggle'; 185 | btn.addClass(ddt); 186 | 187 | ddt = null; 188 | $.each(btnColorCls, function (k,v) { 189 | if (btn.hasClass(v)) { 190 | ddt = v + '-dropdown-toggle'; 191 | return false; 192 | } 193 | }); 194 | if (ddt) btn.addClass(ddt); 195 | 196 | ddt = null; 197 | $.each(btnSizeCls, function (k,v) { 198 | if (btn.hasClass(v)) { 199 | ddt = v + '-dropdown-toggle'; 200 | return false; 201 | } 202 | }); 203 | if (ddt) btn.addClass(ddt); 204 | }); 205 | 206 | // fix split button dropdown toggle background color 207 | $('.btn + .btn.dropdown-toggle', el).each(function () { 208 | var btn = $(this); 209 | var c = btn.css('background-color'); 210 | // alert($.eb.color.darken(c, .2)); 211 | btn.css('background-color', $.eb.color.darken(c, .1)); 212 | }); 213 | 214 | // fix .btn-group.open 215 | var dropdownPropertyChange = function(e) { 216 | var self = $(this); 217 | var cls = e.data.cls; 218 | 219 | /// fix ul li 100% width bug, set ul width to max width of it's sub li 220 | var el = $('.dropdown-menu:visible', this); 221 | if (el.length) dropdownWidthFix(el); 222 | 223 | if (self.hasClass('open') && !self.hasClass(cls+'-open')) { 224 | self.addClass(cls+'-open'); 225 | } 226 | else if (!self.hasClass('open') && self.hasClass(cls+'-open')) { 227 | self.removeClass(cls+'-open'); 228 | } 229 | 230 | self.one('propertychange', {cls:cls}, dropdownPropertyChange); 231 | }; 232 | $.each(['btn-group', 'dropdown'], function (k,cls) { 233 | $('.'+cls, el).one('propertychange', {cls:cls}, dropdownPropertyChange); 234 | }); 235 | 236 | // fix .btn.disabled selector 237 | $('.btn.disabled', el).addClass('btn-disabled'); 238 | 239 | var btnPropertyChange = function (e) { 240 | var self = $(this); 241 | var cls = e.data.cls; 242 | 243 | if (self.hasClass('disabled') && !self.hasClass(cls+'-disabled')) { 244 | self.addClass(cls+'-disabled'); 245 | } 246 | else if (!self.hasClass('disabled') && self.hasClass(cls+'-disabled')) { 247 | self.removeClass(cls+'-disabled'); 248 | } 249 | 250 | self.one('propertychange', {cls:cls}, btnPropertyChange); 251 | } 252 | $.each(['btn'], function (k,cls) { 253 | $('.'+cls, el).one('propertychange', {cls:cls}, btnPropertyChange); 254 | }); 255 | 256 | 257 | //------------- 258 | // table 259 | //------------- 260 | 261 | // fix table-hover effect 262 | $('table.table-hover', el).on('mouseenter', 'tr', function () { 263 | $(this).addClass('tr-hover'); 264 | }).on('mouseleave', 'tr', function () { 265 | $(this).removeClass('tr-hover'); 266 | }); 267 | 268 | //------------- 269 | // form 270 | //------------- 271 | 272 | // fix input[type=xxx] selector 273 | $('input[type="file"], input[type="image"], input[type="submit"], input[type="reset"], input[type="button"], input[type="radio"], input[type="checkbox"], input[type="text"], input[type="password"], input[type="datetime"], input[type="datetime-local"], input[type="date"], input[type="month"], input[type="time"], input[type="week"], input[type="number"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], input[type="color"]', el).each(function () { 274 | var input = $(this); 275 | input.addClass('input-'+input.attr('type')); 276 | }); 277 | 278 | // fix form-horizontal controls margin-left 279 | $('.form-horizontal .controls:first-child', el).addClass('controls-first-child'); 280 | 281 | // fix .checkbox.inline 282 | $('.checkbox.inline', el).addClass('checkbox-inline'); 283 | $('.radio.inline', el).addClass('radio-inline'); 284 | 285 | // fix select[multiple], select[size] 286 | $('select[multiple]', el).addClass('select-multiple'); 287 | $('select[size]', el).addClass('select-size'); 288 | 289 | // fix tag[disabled] 290 | $('input[disabled], select[disabled], textarea[disabled]', el).each(function () { 291 | var self = $(this); 292 | self.addClass(self[0].tagName.toLowerCase()+'-disabled'); 293 | }); 294 | 295 | // $('input,select,textarea', el).on('propertychange', function() { 296 | // var self = $(this); 297 | // if (self.data('chgDisabled')) return; 298 | 299 | // var cls = self[0].tagName.toLowerCase(); 300 | // // alert(self.attr('disabled')); 301 | // if (self.attr('disabled') && !self.hasClass(cls+'-disabled')) { 302 | // // alert('abc'); 303 | // self.addClass(cls+'-disabled'); 304 | // self.data('chgDisabled', true); 305 | // } 306 | // else if (!self.attr('disabled') && self.hasClass(cls+'-disabled')) { 307 | // self.removeClass(cls+'-disabled'); 308 | // self.data('chgDisabled', true); 309 | // } 310 | // }); 311 | 312 | // $('input,select,textarea', el).on('propertychange', function() { 313 | // var self = $(this); 314 | // if (self.data('chgReadonly')) return; 315 | 316 | // var cls = self[0].tagName.toLowerCase(); 317 | 318 | // if (self.attr('readonly') && !self.hasClass(cls+'-readonly')) { 319 | // self.addClass(cls+'-readonly'); 320 | // self.data('chgReadonly', true); 321 | // } 322 | // else if (typeof self.attr('readonly') == 'undefined' && self.hasClass(cls+'-readonly')) { 323 | // self.removeClass(cls+'-readonly'); 324 | // self.data('chgReadonly', true); 325 | // } 326 | // }); 327 | 328 | // fix tag[readonly] 329 | $('input[readonly], select[readonly], textarea[readonly]', el).each(function () { 330 | var self = $(this); 331 | self.addClass(self[0].tagName.toLowerCase()+'-readonly'); 332 | }); 333 | 334 | // fix input[type=xxx][disabled] 335 | $('input[type="radio"][disabled], input[type="checkbox"][disabled]', el).each(function () { 336 | var self = $(this); 337 | self.addClass(self.attr('type').toLowerCase()+'-disabled'); 338 | }); 339 | 340 | // fix input[type=xxx][readonly] 341 | $('input[type="radio"][readonly], input[type="checkbox"][readonly]', el).each(function () { 342 | var self = $(this); 343 | self.addClass(self.attr('type').toLowerCase()+'-readonly'); 344 | }); 345 | 346 | // fix.control-group.warning ... 347 | var ctlGrpTypeCls = ['warning','success','error','info']; 348 | $.each(ctlGrpTypeCls, function (k,v) { 349 | $('.control-group.'+v, el).addClass('control-group-'+v); 350 | }); 351 | 352 | var controlGroupPropertyChange = function(e) { 353 | if(e.originalEvent.propertyName.toLowerCase() == 'classname') { 354 | var self = $(this); 355 | $.each(ctlGrpTypeCls, function (k,v) { 356 | var ieCls = 'control-group-'+v; 357 | if (self.hasClass(v)) { 358 | if (!self.hasClass(ieCls)) { 359 | self.addClass(ieCls); 360 | } 361 | } 362 | else { 363 | if (self.hasClass(ieCls)) { 364 | self.removeClass(ieCls); 365 | } 366 | } 367 | }); 368 | } 369 | $(this).one('propertychange', controlGroupPropertyChange); 370 | }; 371 | $('.control-group', el).one('propertychange', controlGroupPropertyChange); 372 | 373 | //------------- 374 | // popover 375 | //------------- 376 | // $('.popover .arrow', el).after(''); 377 | 378 | //------------- 379 | // pagination 380 | //------------- 381 | $('.pagination ul li:first-child', el).addClass('first-child'); 382 | 383 | 384 | //------------- 385 | // icons 386 | //------------- 387 | $('[class^="icon-"],[class*=" icon-"]').each(function () { 388 | var self = $(this); 389 | if (!self.hasClass('icon-xxx')) { 390 | self.addClass('icon-xxx'); 391 | self.css('background-position-y', 392 | (parseInt(self.css('background-position-y')) + 1)+'px'); 393 | } 394 | }); 395 | 396 | //------------- 397 | // carousel 398 | //------------- 399 | $('.carousel-control.left', el).removeClass('left').addClass('carousel-control-left'); 400 | $('.carousel-control.right', el).removeClass('right').addClass('carousel-control-right'); 401 | $('.carousel-caption').each(function() { 402 | var self = $(this); 403 | var padding = self.outerWidth() - self.width(); 404 | self.width(self.parents('.carousel-inner .item').width() - padding); 405 | }); 406 | 407 | 408 | } 409 | } 410 | $.bootstrapIE6 = bootstrapIE6; 411 | 412 | 413 | $(document).ready(function () { 414 | bootstrapIE6(); 415 | }); 416 | 417 | })(jQuery); --------------------------------------------------------------------------------