├── .gitignore ├── README.md ├── doc └── Pyweb.pdf ├── snapshoot ├── pub.gif ├── pyweb_1.png └── pyweb_2.png ├── src ├── BaseHandler.py ├── IndexHandler.py ├── LoginHandler.py ├── Mongo.py ├── PageHandler.py ├── Publish.py ├── R.py ├── Session.py ├── SocketHandler.py ├── Tools.py ├── main.py ├── static │ ├── css │ │ └── bootstrap.min.css │ ├── favicon.ico │ ├── images │ │ ├── animated-overlay.gif │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── app.common.js │ │ ├── bootstrap.min.js │ │ └── jquery.min.js └── tpl │ ├── about.html │ ├── error.html │ ├── history.html │ ├── index.html │ ├── login.html │ └── publish.html └── test ├── dev_init_servers.py ├── mock_pub.py ├── mock_syc.py ├── mock_zip.py ├── pub.sh ├── single_pub.py ├── single_syc.py ├── syc.sh ├── test_init_servers.py ├── test_pub_response.py ├── test_syc_response.py ├── test_zip_response.py └── zip.sh /.gitignore: -------------------------------------------------------------------------------- 1 | Make* 2 | .deps 3 | .libs 4 | acinclude.m4 5 | aclocal.m4 6 | autom4te.cache/ 7 | build/ 8 | config.guess 9 | config.h 10 | config.h.in 11 | config.log 12 | config.nice 13 | config.status 14 | config.sub 15 | configure 16 | configure.in 17 | debug_run.sh 18 | install-sh 19 | libtool 20 | ltmain.sh 21 | missing 22 | mkinstalldirs 23 | modules/ 24 | *.la 25 | *.lo 26 | run-tests.php 27 | .idea/ 28 | .DS_Store 29 | config.h.in~ 30 | *.pyc 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyweb 2 | a simple game publish tool framework that use tornado and websocket 3 | 4 | snapshoot 5 | 6 | ![](./snapshoot/pub.gif) 7 | 8 | ##Step 0 初始化 9 | ``` 10 | {node}/server_list/s1 {"update_time":x,"server_name":x,"server_id":x} 11 | ... 12 | {node}/server_list/sN {"update_time":x,"server_name":x,"server_id":x} 13 | ``` 14 | 15 | ##Step 1 打包 16 | ``` 17 | {node}/to_zip_notice/v1 {"update_time":x,"pub_id":x,"pub_node_id":x,"config_version":x,"game_version":x,"status":"ok","finish_time":x} 18 | {node}/to_zip_result/v1 {"update_time":x,"status":"ok"} 19 | ``` 20 | * svn导出脚本可以参考[http://xingqiba.sinaapp.com/?p=846](http://xingqiba.sinaapp.com/?p=846) 21 | 22 | ![](http://xingqiba-wordpress.stor.sinaapp.com/uploads/2012/09/svn_export_patch.png) 23 | 24 | 25 | ##Step 2 同步 26 | ``` 27 | {node}/to_syc_notice/v1 {"update_time":x,"pub_id":x,"pub_node_id":x,"config_version":x,"game_version":x,"servers":[x,x,...],"status":"ok","finish_time":x} 28 | {node}/to_syc_result/v1/s1 {"update_time":x,"status":"ok"} 29 | ... 30 | {node}/to_syc_result/v1/sN {"update_time":x,"status":"ok"} 31 | {node}/to_syc_result/v1 {"update_time":x,"status":"ok"} 32 | ``` 33 | 34 | ##Step 3 发布 35 | ``` 36 | {node}/to_pub_notice/v1 {"update_time":x,"pub_id":x,"pub_node_id":x,"config_version":x,"game_version":x,"servers":[x,x,...],"status":"ok","finish_time":x} 37 | {node}/to_pub_result/v1/s1 {"update_time":x,"status":"ok"} 38 | ... 39 | {node}/to_pub_result/v1/sN {"update_time":x,"status":"ok"} 40 | {node}/to_pub_result/v1 {"update_time":x,"status":"ok"} 41 | ``` 42 | 43 | ##Step 4 回滚 @TODO 44 | ``` 45 | {node}/to_rol_notice/v1 {"update_time":x,"pub_id":x,"pub_node_id":x, "servers":[x,x,...]} 46 | {node}/to_rol_result/v1/s1 {"update_time":x,"status":"ok"} 47 | ... 48 | {node}/to_rol_result/v1/sN {"update_time":x,"status":"ok"} 49 | {node}/to_rol_result/v1 {"update_time":x,"status":"ok"} 50 | ``` 51 | 52 | 53 | -------------------------------------------------------------------------------- /doc/Pyweb.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/doc/Pyweb.pdf -------------------------------------------------------------------------------- /snapshoot/pub.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/snapshoot/pub.gif -------------------------------------------------------------------------------- /snapshoot/pyweb_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/snapshoot/pyweb_1.png -------------------------------------------------------------------------------- /snapshoot/pyweb_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/snapshoot/pyweb_2.png -------------------------------------------------------------------------------- /src/BaseHandler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import tornado.web 4 | import tornado.template 5 | 6 | import Session 7 | 8 | LOG = logging.getLogger(__name__) 9 | 10 | class BaseHandler(tornado.web.RequestHandler): 11 | 12 | check_session = False 13 | use_session = False 14 | session = None 15 | 16 | def __init__(self, application, request, **kwargs): 17 | super(BaseHandler, self).__init__(application, request, **kwargs) 18 | LOG.info('web request init') 19 | 20 | if self.use_session: 21 | self.session = Session.Session(self) 22 | self.session.load() 23 | 24 | def check_login(self): 25 | if self.use_session \ 26 | and self.check_session \ 27 | and self.session.get('name') is None: 28 | self.redirect('/login') 29 | 30 | def prepare(self): 31 | LOG.info('web request prepare') 32 | self.check_login() 33 | 34 | def on_finish(self): 35 | LOG.info('web request finish') 36 | if self.use_session: 37 | self.session.save() 38 | 39 | def write_error(self, status_code, **kwargs): 40 | self.write('Error:%d' % status_code) -------------------------------------------------------------------------------- /src/IndexHandler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import BaseHandler 4 | 5 | LOG = logging.getLogger(__name__) 6 | 7 | class IndexHandler(BaseHandler.BaseHandler): 8 | 9 | check_session = True 10 | use_session = True 11 | 12 | def get(self): 13 | page_val = { 14 | 'manager_name' : self.session.get('name') 15 | } 16 | self.render('index.html', **page_val) 17 | -------------------------------------------------------------------------------- /src/LoginHandler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import BaseHandler 4 | 5 | import Tools 6 | 7 | LOG = logging.getLogger(__name__) 8 | 9 | class LoginHandler(BaseHandler.BaseHandler): 10 | 11 | check_session = False 12 | use_session = True 13 | 14 | def get(self): 15 | if self.session.get('name') is not None: 16 | self.redirect('/') 17 | else: 18 | self.render('login.html') 19 | 20 | def post(self): 21 | username = self.get_argument('username', '') 22 | password = self.get_argument('password', '') 23 | LOG.info('login page post %s-%s' % (username, password,)) 24 | 25 | if username == 'admin' and password == 'todo': 26 | self.session.init({'name':username,'time':Tools.g_time()}) 27 | self.redirect('/') -------------------------------------------------------------------------------- /src/Mongo.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | 3 | import R 4 | 5 | class MongoDB(object): 6 | 7 | _mongo_db = None 8 | 9 | def __init__(self, mongo_db): 10 | self._mongo_db = mongo_db 11 | 12 | def use_collection(self, collection_name): 13 | return self._mongo_db[collection_name] 14 | 15 | class Mongo(object): 16 | 17 | _mongo_cl = None 18 | _mongo_db = {} 19 | 20 | def __init__(self, host, port): 21 | self._mongo_cl = pymongo.MongoClient(host, port) 22 | 23 | def get(self, db_name = 'jzgps'): 24 | if self._mongo_db.get(db_name, None) is None: 25 | self._mongo_db[db_name] = MongoDB(self._mongo_cl[db_name]) 26 | 27 | return self._mongo_db[db_name] 28 | 29 | def gen_uuid(self, uuid_name = 'common'): 30 | result = self.get().use_collection(R.collection_uuid).find_and_modify({R.uuid_name:uuid_name}, {'$inc' : {R.uuid_id : 1}}, upsert = True, new = True) 31 | if result.get(R.uuid_id, None) is None: 32 | raise 'mongo execute find_and_modify failed' 33 | 34 | return int(result[R.uuid_id]) -------------------------------------------------------------------------------- /src/PageHandler.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import logging 4 | 5 | import BaseHandler 6 | 7 | import R 8 | import pymongo 9 | 10 | LOG = logging.getLogger(__name__) 11 | 12 | class PageHandler(BaseHandler.BaseHandler): 13 | 14 | check_session = True 15 | use_session = True 16 | 17 | def get(self): 18 | action_page = self.get_argument('action', '') 19 | 20 | if len(action_page): 21 | cls_method = getattr(self, action_page, None) 22 | if cls_method: 23 | cls_method() 24 | else: 25 | self.error('not found `%s`' % action_page) 26 | else: 27 | self.error('error action page') 28 | 29 | def about(self): 30 | self.render('about.html') 31 | 32 | def logout(self): 33 | self.session.clear() 34 | self.redirect('/login') 35 | 36 | def error(self, message = ''): 37 | page_val = { 38 | 'message' : message 39 | } 40 | self.render('error.html', **page_val) 41 | 42 | def publish(self): 43 | page_val = { 44 | 'pub_data' : {} 45 | } 46 | 47 | pub_id = self.get_argument('id', 0) 48 | if pub_id > 0: 49 | mongo_col_data = self.application.get_mongo().get().use_collection(R.collection_publish).find_one({R.mongo_id : int(pub_id)}) 50 | if mongo_col_data: 51 | page_val['pub_data'] = json.dumps({ 52 | 'pub_id' : mongo_col_data[R.mongo_id], 53 | 'pub_config_version' : mongo_col_data[R.pub_config_version], 54 | 'pub_game_version' : mongo_col_data[R.pub_game_version], 55 | 'pub_desc' : mongo_col_data[R.pub_description], 56 | 'pub_status' : mongo_col_data[R.pub_status], 57 | 'pub_servers' : mongo_col_data[R.pub_servers], 58 | }) 59 | 60 | self.render('publish.html', **page_val) 61 | 62 | def deprecated(self): 63 | pub_id = self.get_argument('id', 0) 64 | if pub_id > 0: 65 | self.application.get_mongo().get().use_collection(R.collection_publish).update({ 66 | R.mongo_id : int(pub_id) 67 | }, {'$set' : {R.pub_status : 'deprecated'}}) 68 | 69 | #deprecated 70 | self.application.get_publish().deprecated(pub_id) 71 | 72 | return self.history() 73 | else: 74 | page_val = { 75 | 'message' : 'error params' 76 | } 77 | self.render('error.html', **page_val) 78 | 79 | def history(self): 80 | page_val = { 81 | 'page' : int(self.get_argument('page', 1)), 82 | 'total_page' : 0, 83 | 'total' : 0, 84 | 'prev' : False, 85 | 'next' : False, 86 | 'pub_list' : [] 87 | } 88 | 89 | page_num = 20 90 | skip_num = (page_val['page'] - 1) * page_num if page_val['page'] >= 1 else 1 91 | 92 | mongo_col_cursor = self.application.get_mongo().get().use_collection(R.collection_publish).find(sort=[('_id',pymongo.DESCENDING)]) 93 | 94 | page_val['total'] = mongo_col_cursor.count() 95 | page_val['total_page'] = page_val['total'] / page_num if page_val['total'] % page_num == 0 else 1 + page_val['total'] / page_num 96 | page_val['prev'] = True if page_val['page'] > 1 else False 97 | page_val['next'] = True if page_val['page'] < page_val['total_page'] else False 98 | 99 | mongo_col_cursor.batch_size(page_num) 100 | mongo_col_cursor.limit(page_val['page'] * page_num) 101 | 102 | for mongo_col_data in mongo_col_cursor[skip_num:skip_num + page_num]: 103 | page_val['pub_list'].append({ 104 | 'id' : mongo_col_data[R.mongo_id], 105 | 'pub_node_id' : 'v%s' % mongo_col_data[R.mongo_id], 106 | 'config_version' : mongo_col_data[R.pub_config_version], 107 | 'game_version' : mongo_col_data[R.pub_game_version], 108 | 'description' : mongo_col_data[R.pub_description], 109 | 'servers' : ','.join(mongo_col_data[R.pub_servers]) if len(mongo_col_data[R.pub_servers]) > 0 else '', 110 | 'status' : mongo_col_data[R.pub_status], 111 | 'pub_time' : time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mongo_col_data[R.pub_time])) 112 | }) 113 | 114 | mongo_col_cursor.close() 115 | 116 | self.render('history.html', **page_val) 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/Publish.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import time 4 | import Tools 5 | 6 | import kazoo 7 | from kazoo.client import KazooClient 8 | 9 | LOG = logging.getLogger(__name__) 10 | 11 | class Publish(object): 12 | 13 | _to_zip_node = dict() 14 | _to_syc_node = dict() 15 | _to_pub_node = dict() 16 | _server_list = dict() 17 | _root_node = '' 18 | _zookeeper = None 19 | 20 | def __init__(self, host = '127.0.0.1', port = 2181, root_node = '/jzqps'): 21 | self._root_node = root_node if root_node[0] == '/' else '/jzgps' 22 | self._zookeeper = KazooClient('%s:%s' % (host, port,)) 23 | self._zookeeper.start() 24 | 25 | default_node = [ 26 | self._root_node, 27 | self._root_node + '/server_list', 28 | self._root_node + '/to_zip_notice', 29 | self._root_node + '/to_zip_result', 30 | self._root_node + '/to_syc_notice', 31 | self._root_node + '/to_syc_result', 32 | self._root_node + '/to_pub_notice', 33 | self._root_node + '/to_pub_result', 34 | self._root_node + '/to_rol_notice', 35 | self._root_node + '/to_rol_result', 36 | ] 37 | 38 | default_node_value = json.dumps({'update_time' : Tools.g_time()}) 39 | 40 | try: 41 | for node in default_node: 42 | if self._zookeeper.exists(node) is None: 43 | self._zookeeper.create(node, default_node_value, makepath = True) 44 | except kazoo.exceptions.NodeExistsError: 45 | pass 46 | 47 | def server(self, server_node, now_timestamp): 48 | server_detail = self._zookeeper.get('%s/server_list/%s' % (self._root_node, server_node, )) 49 | if 0 != len(server_detail[0]): 50 | tmp_server_detail = json.loads(server_detail[0]) 51 | if tmp_server_detail['update_time'] + 10 > now_timestamp: 52 | self._server_list[server_node] = tmp_server_detail 53 | elif self._server_list.get(server_node, None) is not None: 54 | del self._server_list[server_node] 55 | 56 | return self._server_list.get(server_node, None) 57 | 58 | def get_pub_node_id(self, pub_id): 59 | return 'v%s' % pub_id 60 | 61 | def get_server_list(self): 62 | server_list = [] 63 | server_node = self._zookeeper.get_children('/test/server_list/') 64 | if len(server_node): 65 | now_timestamp = time.time() 66 | for s in sorted(server_node): 67 | if self.server(s, now_timestamp) is not None: 68 | server_list.append(self._server_list[s]) 69 | 70 | return server_list 71 | 72 | def to_zip(self, pub_id, zip_callback = None, **ext_data): 73 | pub_node_id = self.get_pub_node_id(pub_id) 74 | 75 | ext_data['pub_id'] = pub_id 76 | ext_data['pub_node_id'] = pub_node_id 77 | ext_data['update_time'] = Tools.g_time() 78 | 79 | try: 80 | if self._zookeeper.exists(self._root_node + '/to_zip_notice/' + pub_node_id) is None: 81 | self._zookeeper.create(self._root_node + '/to_zip_notice/' + pub_node_id, json.dumps(ext_data), makepath = True) 82 | else: 83 | self._zookeeper.set(self._root_node + '/to_zip_notice/' + pub_node_id, json.dumps(ext_data)) 84 | 85 | if self._zookeeper.exists(self._root_node + '/to_zip_result/' + pub_node_id) is None: 86 | self._zookeeper.create(self._root_node + '/to_zip_result/' + pub_node_id, '', makepath = True) 87 | else: 88 | self._zookeeper.set(self._root_node + '/to_zip_result/' + pub_node_id, '') 89 | 90 | except kazoo.exceptions.NodeExistsError: 91 | pass 92 | 93 | if self._to_zip_node.get(pub_node_id, None) is None: 94 | self._to_zip_node[pub_node_id] = [zip_callback] 95 | self.zip_notice(pub_id, pub_node_id) 96 | else: 97 | self._to_zip_node[pub_node_id].append(zip_callback) 98 | 99 | return self 100 | 101 | def zip_notice(self, pub_id, pub_node_id): 102 | 103 | @self._zookeeper.DataWatch('%s/to_zip_result/%s' % (self._root_node, pub_node_id, )) 104 | def to_zip_notice(data, stat, event): 105 | if 0 == len(data) or \ 106 | event is None \ 107 | or event.type == 'CREATED' \ 108 | or event.type == 'DELETED': 109 | return 110 | 111 | LOG.info('%s/to_zip_result/%s changed %s' % (self._root_node, pub_node_id, data, )) 112 | 113 | for zip_callback in self._to_zip_node[pub_node_id]: 114 | zip_callback(data) 115 | self._to_zip_node[pub_node_id] = [] 116 | 117 | return self 118 | 119 | def to_syc(self, pub_id, target_servers, syc_process_callback=None, syc_success_callback = None, **ext_data): 120 | pub_node_id = self.get_pub_node_id(pub_id) 121 | 122 | ext_data['pub_id'] = pub_id 123 | ext_data['pub_node_id'] = pub_node_id 124 | ext_data['update_time'] = Tools.g_time() 125 | ext_data['servers'] = target_servers 126 | 127 | try: 128 | if self._zookeeper.exists(self._root_node + '/to_syc_notice/' + pub_node_id) is None: 129 | self._zookeeper.create(self._root_node + '/to_syc_notice/' + pub_node_id, json.dumps(ext_data), makepath = True) 130 | else: 131 | self._zookeeper.set(self._root_node + '/to_syc_notice/' + pub_node_id, json.dumps(ext_data)) 132 | 133 | if self._zookeeper.exists(self._root_node + '/to_syc_result/' + pub_node_id) is None: 134 | self._zookeeper.create(self._root_node + '/to_syc_result/' + pub_node_id, '', makepath = True) 135 | else: 136 | self._zookeeper.set(self._root_node + '/to_syc_result/' + pub_node_id, '') 137 | 138 | for target_server_id in target_servers: 139 | target_node = self._root_node + '/to_syc_result/' + pub_node_id + '/s' + str(target_server_id) 140 | if self._zookeeper.exists(target_node) is not None: 141 | self._zookeeper.delete(target_node) 142 | 143 | except kazoo.exceptions.NodeExistsError: 144 | pass 145 | 146 | if self._to_syc_node.get(pub_node_id, None) is None: 147 | self._to_syc_node[pub_node_id] = { 148 | 'callback' : [syc_process_callback, syc_success_callback], 149 | 'servers' : target_servers, 150 | 'notices' : [], 151 | 'results' : {}, 152 | 'update_time' : Tools.g_time() 153 | } 154 | self.syc_children_notice(pub_id, pub_node_id) 155 | else : 156 | self._to_syc_node[pub_node_id]['callback'] = [syc_process_callback, syc_success_callback] 157 | self._to_syc_node[pub_node_id]['servers'] = target_servers 158 | self._to_syc_node[pub_node_id]['results'] = {} 159 | self._to_syc_node[pub_node_id]['time'] = Tools.g_time() 160 | 161 | return self 162 | 163 | def syc_children_notice(self, pub_id, pub_node_id): 164 | 165 | @self._zookeeper.ChildrenWatch('%s/to_syc_result/%s' % (self._root_node, pub_node_id, )) 166 | def to_syc_process(server_list): 167 | for server_node in server_list: 168 | if server_node not in self._to_syc_node[pub_node_id]['notices']: 169 | self._to_syc_node[pub_node_id]['notices'].append(server_node) 170 | self.syc_process_notice(pub_id, pub_node_id, server_node) 171 | 172 | return self 173 | 174 | def syc_process_notice(self, pub_id, pub_node_id, server_node): 175 | syc_server_node = '%s/to_syc_result/%s/%s' % (self._root_node, pub_node_id, server_node, ) 176 | 177 | @self._zookeeper.DataWatch(syc_server_node) 178 | def to_syc_process(data, stat, event): 179 | if event is not None and event.type == 'DELETED': 180 | return 181 | 182 | if 0 == len(data): 183 | return 184 | 185 | LOG.info('syc children %s %s' % (syc_server_node, data, )) 186 | 187 | syc_detail = json.loads(data) 188 | if isinstance(syc_detail, dict) == False or \ 189 | syc_detail.get('update_time', None) is None or \ 190 | syc_detail.get('status', None) is None: 191 | return 192 | 193 | if syc_detail['status'] == 'ok': 194 | self._to_syc_node[pub_node_id]['results'][server_node] = True 195 | else: 196 | self._to_syc_node[pub_node_id]['results'][server_node] = False 197 | 198 | self._to_syc_node[pub_node_id]['callback'][0](server_node, data) 199 | 200 | all_syc_finished = True if len(self._to_syc_node[pub_node_id]['servers']) > 0 else False 201 | for server_id in self._to_syc_node[pub_node_id]['servers']: 202 | target_server_node = 's%s' % server_id 203 | if self._to_syc_node[pub_node_id]['results'].get(target_server_node, False) is False: 204 | all_syc_finished = False 205 | break 206 | 207 | if all_syc_finished: 208 | self._to_syc_node[pub_node_id]['callback'][1]() 209 | 210 | self._to_syc_node[pub_node_id]['callback'] = [] 211 | self._to_syc_node[pub_node_id]['results'] = {} 212 | 213 | self._zookeeper.set('%s/to_syc_notice/%s' % (self._root_node, pub_node_id, ), json.dumps({ 214 | 'pub_id' : pub_id, 215 | 'pub_node_id' : pub_node_id, 216 | 'update_time' : self._to_syc_node[pub_node_id]['update_time'], 217 | 'servers' : self._to_syc_node[pub_node_id]['servers'], 218 | 'finish_time' : Tools.g_time(), 219 | 'status' : 'ok' 220 | })) 221 | 222 | self._zookeeper.set('%s/to_syc_result/%s' % (self._root_node, pub_node_id, ), json.dumps({ 223 | 'update_time' : Tools.g_time(), 224 | 'status' : 'ok' 225 | })) 226 | 227 | return self 228 | 229 | def to_pub(self, pub_id, target_servers, pub_process_callback=None, pub_success_callback = None, **ext_data): 230 | pub_node_id = self.get_pub_node_id(pub_id) 231 | 232 | ext_data['pub_id'] = pub_id 233 | ext_data['pub_node_id'] = pub_node_id 234 | ext_data['update_time'] = Tools.g_time() 235 | ext_data['servers'] = target_servers 236 | 237 | try: 238 | if self._zookeeper.exists(self._root_node + '/to_pub_notice/' + pub_node_id) is None: 239 | self._zookeeper.create(self._root_node + '/to_pub_notice/' + pub_node_id, json.dumps(ext_data), makepath = True) 240 | else: 241 | self._zookeeper.set(self._root_node + '/to_pub_notice/' + pub_node_id, json.dumps(ext_data)) 242 | 243 | if self._zookeeper.exists(self._root_node + '/to_pub_result/' + pub_node_id) is None: 244 | self._zookeeper.create(self._root_node + '/to_pub_result/' + pub_node_id, '', makepath = True) 245 | else: 246 | self._zookeeper.set(self._root_node + '/to_pub_result/' + pub_node_id, '') 247 | 248 | for target_server_id in target_servers: 249 | target_node = self._root_node + '/to_pub_result/' + pub_node_id + '/s' + str(target_server_id) 250 | if self._zookeeper.exists(target_node) is not None: 251 | self._zookeeper.delete(target_node) 252 | 253 | except kazoo.exceptions.NodeExistsError: 254 | pass 255 | 256 | if self._to_pub_node.get(pub_node_id, None) is None: 257 | self._to_pub_node[pub_node_id] = { 258 | 'callback' : [pub_process_callback, pub_success_callback], 259 | 'servers' : target_servers, 260 | 'notices' : [], 261 | 'results' : {}, 262 | 'update_time' : Tools.g_time() 263 | } 264 | self.pub_children_notice(pub_id, pub_node_id) 265 | else : 266 | self._to_pub_node[pub_node_id]['callback'] = [pub_process_callback, pub_success_callback] 267 | self._to_pub_node[pub_node_id]['servers'] = target_servers 268 | self._to_pub_node[pub_node_id]['results'] = {} 269 | self._to_pub_node[pub_node_id]['time'] = Tools.g_time() 270 | 271 | return self 272 | 273 | def pub_children_notice(self, pub_id, pub_node_id): 274 | 275 | @self._zookeeper.ChildrenWatch('%s/to_pub_result/%s' % (self._root_node, pub_node_id, )) 276 | def to_pub_process(server_list): 277 | for server_node in server_list: 278 | if server_node not in self._to_pub_node[pub_node_id]['notices']: 279 | self._to_pub_node[pub_node_id]['notices'].append(server_node) 280 | self.pub_process_notice(pub_id, pub_node_id, server_node) 281 | 282 | return self 283 | 284 | def pub_process_notice(self, pub_id, pub_node_id, server_node): 285 | pub_server_node = '%s/to_pub_result/%s/%s' % (self._root_node, pub_node_id, server_node, ) 286 | 287 | @self._zookeeper.DataWatch(pub_server_node) 288 | def to_pub_process(data, stat, event): 289 | if event is not None and event.type == 'DELETED': 290 | return 291 | 292 | if 0 == len(data): 293 | return 294 | 295 | LOG.info('pub children %s %s' % (pub_server_node, data, )) 296 | 297 | pub_detail = json.loads(data) 298 | if isinstance(pub_detail, dict) == False or \ 299 | pub_detail.get('update_time', None) is None or \ 300 | pub_detail.get('status', None) is None: 301 | return 302 | 303 | if pub_detail['status'] == 'ok': 304 | self._to_pub_node[pub_node_id]['results'][server_node] = True 305 | else: 306 | self._to_pub_node[pub_node_id]['results'][server_node] = False 307 | 308 | self._to_pub_node[pub_node_id]['callback'][0](server_node, data) 309 | 310 | all_pub_finished = True if len(self._to_pub_node[pub_node_id]['servers']) > 0 else False 311 | for server_id in self._to_pub_node[pub_node_id]['servers']: 312 | target_server_node = 's%s' % server_id 313 | if self._to_pub_node[pub_node_id]['results'].get(target_server_node, False) is False: 314 | all_pub_finished = False 315 | break 316 | 317 | if all_pub_finished: 318 | self._to_pub_node[pub_node_id]['callback'][1]() 319 | 320 | self._to_pub_node[pub_node_id]['callback'] = [] 321 | self._to_pub_node[pub_node_id]['results'] = {} 322 | 323 | self._zookeeper.set('%s/to_pub_notice/%s' % (self._root_node, pub_node_id, ), json.dumps({ 324 | 'pub_id' : pub_id, 325 | 'pub_node_id' : pub_node_id, 326 | 'update_time' : self._to_pub_node[pub_node_id]['update_time'], 327 | 'servers' : self._to_pub_node[pub_node_id]['servers'], 328 | 'finish_time' : Tools.g_time(), 329 | 'status' : 'ok' 330 | })) 331 | 332 | self._zookeeper.set('%s/to_pub_result/%s' % (self._root_node, pub_node_id, ), json.dumps({ 333 | 'update_time' : Tools.g_time(), 334 | 'status' : 'ok' 335 | })) 336 | 337 | return self 338 | 339 | def deprecated(self, pub_id): 340 | pub_node_id = self.get_pub_node_id(pub_id) 341 | 342 | if self._zookeeper.exists(self._root_node + '/to_syc_notice/' + pub_node_id): 343 | self._zookeeper.set('%s/to_syc_notice/%s' % (self._root_node, pub_node_id, ), json.dumps({ 344 | 'pub_id' : pub_id, 345 | 'pub_node_id' : pub_node_id, 346 | 'update_time' : Tools.g_time(), 347 | 'servers' : [], 348 | 'finish_time' : Tools.g_time(), 349 | 'status' : 'deprecated' 350 | })) 351 | 352 | if self._zookeeper.exists(self._root_node + '/to_pub_notice/' + pub_node_id): 353 | self._zookeeper.set('%s/to_pub_notice/%s' % (self._root_node, pub_node_id, ), json.dumps({ 354 | 'pub_id' : pub_id, 355 | 'pub_node_id' : pub_node_id, 356 | 'update_time' : Tools.g_time(), 357 | 'servers' : [], 358 | 'finish_time' : Tools.g_time(), 359 | 'status' : 'deprecated' 360 | })) 361 | 362 | 363 | -------------------------------------------------------------------------------- /src/R.py: -------------------------------------------------------------------------------- 1 | ''' 2 | mongo constant field 3 | ''' 4 | 5 | ##normal 6 | mongo_id = '_id' 7 | 8 | ##collection 9 | collection_uuid = 'uuid' 10 | collection_publish = 'publish' 11 | collection_history = 'history' 12 | 13 | ##collection uuid 14 | uuid_name = 'a' 15 | uuid_id = 'b' 16 | 17 | ##collection publish 18 | pub_config_version = 'a' 19 | pub_game_version = 'b' 20 | pub_description = 'c' 21 | pub_time = 'd' 22 | pub_status = 'e' 23 | pub_servers = 'f' 24 | -------------------------------------------------------------------------------- /src/Session.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import logging 3 | 4 | import Tools 5 | 6 | LOG = logging.getLogger(__name__) 7 | 8 | class Session(object): 9 | 10 | _sessionName = 'jzps' 11 | _sessionValue = {} 12 | _sessionID = None 13 | _sessionDataHandler = None 14 | _webApplication = None 15 | _webRequestHandler = None 16 | 17 | def __init__(self, webRequestHandler): 18 | self._sessionDataHandler = webRequestHandler.application.get_redis() 19 | self._webApplication = webRequestHandler.application 20 | self._webRequestHandler = webRequestHandler 21 | 22 | def load(self): 23 | if self._sessionID is None: 24 | self.get_session_id() 25 | 26 | if self._sessionID is not None: 27 | self._sessionValue = self._sessionDataHandler.hgetall(self._sessionID) 28 | 29 | def get_session_id(self): 30 | if self._sessionID is None: 31 | sessionID = self._webRequestHandler.get_secure_cookie(self._sessionName, None) 32 | if sessionID is not None: 33 | flag = sessionID.split('-')[1] if sessionID.count('-') else None 34 | if flag is None or flag != Tools.g_md5(self._webRequestHandler.request.remote_ip + self._webApplication.settings.get('cookie_secret', '')): 35 | LOG.warn('session %s invalid' % sessionID) 36 | sessionID = None 37 | 38 | if sessionID is None: 39 | sessionID = '%s-%s' % (str(uuid.uuid4()).split('-')[0], Tools.g_md5(self._webRequestHandler.request.remote_ip + self._webApplication.settings.get('cookie_secret', '')), ) 40 | self._webRequestHandler.set_secure_cookie(self._sessionName, sessionID, expires_days = 1, httponly = True) 41 | LOG.info('gen session id %s %s' % (self._webRequestHandler.request.remote_ip, sessionID,)) 42 | 43 | self._sessionID = 'session_' + sessionID 44 | 45 | return self._sessionID 46 | 47 | def init(self, value): 48 | if type(value) is dict: 49 | self._sessionValue = value 50 | else: 51 | raise 'Error session data type to init' 52 | 53 | def get(self, key, default_value=None): 54 | return self._sessionValue.get(key, default_value) 55 | 56 | def set(self, key, value): 57 | self._sessionValue[key] = value 58 | 59 | def clear(self): 60 | if self._sessionID is not None: 61 | self._sessionDataHandler.delete(self._sessionID) 62 | self._sessionValue = {} 63 | self._webRequestHandler.set_secure_cookie(self._sessionName, '') 64 | 65 | def save(self): 66 | if self._sessionID is not None and len(self._sessionValue): 67 | self._sessionDataHandler.hmset(self._sessionID, self._sessionValue) 68 | self._sessionDataHandler.expire(self._sessionID, 3600) 69 | -------------------------------------------------------------------------------- /src/SocketHandler.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import logging 4 | import urlparse 5 | 6 | import tornado.websocket 7 | 8 | import R 9 | import Session 10 | import Tools 11 | 12 | LOG = logging.getLogger(__name__) 13 | 14 | class SocketHandler(tornado.websocket.WebSocketHandler): 15 | 16 | _mongo = None 17 | _mongo_col = None 18 | _publish = None 19 | _connected = False 20 | 21 | def check_origin(self, origin): 22 | return urlparse.urlparse(origin).netloc.lower() == '127.0.0.1:8888' 23 | 24 | def open(self): 25 | LOG.info('client connected %s' % self.request.remote_ip) 26 | self.session = Session.Session(self) 27 | self.session.load() 28 | if self.session.get('name') is None: 29 | self.close(500, 'not login') 30 | return 31 | 32 | self._mongo = self.application.get_mongo() 33 | self._mongo_col = self._mongo.get().use_collection(R.collection_publish) 34 | self._publish = self.application.get_publish() 35 | self._connected = True 36 | 37 | def on_close(self): 38 | LOG.info('client disconnected') 39 | self._connected = False 40 | 41 | def client_response(self, message, executor): 42 | if self._connected: 43 | self.write_message(json.dumps({'executor':executor, 'params':message})) 44 | return True 45 | 46 | LOG.error('client already closed') 47 | return False 48 | 49 | def client_debug(self, message): 50 | return self.client_response(message, 'debug') 51 | 52 | def get_server_list(self, executor): 53 | return self.client_response(self._publish.get_server_list(), executor) 54 | 55 | def on_message(self, message): 56 | if isinstance(message, str) is False \ 57 | and isinstance(message, unicode) is False: 58 | self.close(500, 'error message type') 59 | return 60 | 61 | LOG.info('client post %s' % message) 62 | self.client_debug(message) 63 | 64 | client_message = json.loads(message) 65 | client_action = client_message.get('action', None) 66 | client_params = client_message.get('params', dict()) 67 | target_servers = client_params.get('servers').split(',') if len(client_params.get('servers', '')) > 0 else [] 68 | 69 | if client_action == 'to_zip': 70 | self.to_zip(client_params.get('pub_id', 0),\ 71 | client_params.get('config_version', 0),\ 72 | client_params.get('game_version', 0), \ 73 | client_params.get('desc', ''), \ 74 | client_message.get('callback', '')) 75 | elif client_action == 'to_syc': 76 | self.to_syc(client_params.get('pub_id', 0), target_servers, client_message.get('callback', '')) 77 | elif client_action == 'to_pub': 78 | self.to_pub(client_params.get('pub_id', 0), target_servers, client_message.get('callback', '')) 79 | elif client_action == 'servers': 80 | self.get_server_list(client_message.get('callback', '')) 81 | else: 82 | self.client_debug('error action or params') 83 | 84 | def to_zip(self, pub_id, config_version, game_version, desc, executor): 85 | if pub_id <= 0: 86 | pub_id = self._mongo.gen_uuid(R.collection_publish) 87 | self._mongo_col.insert({ 88 | R.mongo_id : int(pub_id), 89 | R.pub_config_version : str(config_version), 90 | R.pub_game_version : str(game_version), 91 | R.pub_description : str(desc), 92 | R.pub_status : 'zip', 93 | R.pub_servers : [], 94 | R.pub_time : Tools.g_time() 95 | }) 96 | LOG.info('new zip_id=%s' % pub_id) 97 | 98 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'zip'}, executor) 99 | 100 | def zip_callback(zip_response): 101 | LOG.info('zip return %s' % zip_response) 102 | zip_result = json.loads(zip_response) 103 | if zip_result.get('status', None) == 'ok': 104 | self._mongo_col.update({ 105 | R.mongo_id : int(pub_id), 106 | R.pub_status : 'zip' 107 | }, {'$set' : {R.pub_status : 'zip_success'}}) 108 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'zip_success'}, executor) 109 | else: 110 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'zip_failed'}, executor) 111 | 112 | ext_data = { 113 | 'config_version' : str(config_version), 114 | 'game_version' : str(game_version) 115 | } 116 | 117 | self._publish.to_zip(pub_id, zip_callback, **ext_data) 118 | 119 | 120 | def to_syc(self, pub_id, target_servers, executor): 121 | pub_col_data = self._mongo_col.find_one({R.mongo_id : int(pub_id)}) 122 | if pub_col_data is None: 123 | self.client_response({'code' : 'err', 'msg' : 'not found pub %s' % pub_id}, executor) 124 | return 125 | 126 | self._mongo_col.update({ 127 | R.mongo_id : int(pub_id), 128 | }, {'$set' : {R.pub_status : 'syc', R.pub_servers : target_servers}}) 129 | 130 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'syc'}, executor) 131 | 132 | def syc_process(server_id, syc_response): 133 | LOG.info('server %s syc finished %s' % (server_id, syc_response, )) 134 | syc_result = json.loads(syc_response) 135 | if syc_result.get('status', None) == 'ok': 136 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'syc_process', 'server' : server_id}, executor) 137 | else: 138 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'syc_failed', 'server' : server_id}, executor) 139 | 140 | def syc_success(): 141 | LOG.info('syc success') 142 | self._mongo_col.update({ 143 | R.mongo_id : int(pub_id), 144 | R.pub_status : 'syc' 145 | }, {'$set' : {R.pub_status : 'syc_success'}}) 146 | return self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'syc_success'}, executor) 147 | 148 | ext_data = { 149 | 'config_version' : str(pub_col_data[R.pub_config_version]), 150 | 'game_version' : str(pub_col_data[R.pub_game_version]) 151 | } 152 | 153 | self._publish.to_syc(pub_id, target_servers, syc_process, syc_success, **ext_data) 154 | 155 | def to_pub(self, pub_id, target_servers, executor): 156 | pub_col_data = self._mongo_col.find_one({R.mongo_id : int(pub_id)}) 157 | if pub_col_data is None: 158 | self.client_response({'code' : 'err', 'msg' : 'not found pub %s' % pub_id}, executor) 159 | return 160 | 161 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'pub'}, executor) 162 | 163 | def pub_process(server_id, pub_response): 164 | LOG.info('server %s pub finished %s' % (server_id, pub_response, )) 165 | pub_result = json.loads(pub_response) 166 | if pub_result.get('status', None) == 'ok': 167 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'pub_process', 'server' : server_id}, executor) 168 | else: 169 | self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'pub_failed', 'server' : server_id}, executor) 170 | 171 | def pub_success(): 172 | LOG.info('syc success') 173 | self._mongo_col.update({ 174 | R.mongo_id : int(pub_id), 175 | R.pub_status : 'syc_success' 176 | }, {'$set' : {R.pub_status : 'pub_success'}}) 177 | return self.client_response({'code' : 'ok', 'pub_id' : pub_id, 'status' : 'pub_success'}, executor) 178 | 179 | ext_data = { 180 | 'config_version' : str(pub_col_data[R.pub_config_version]), 181 | 'game_version' : str(pub_col_data[R.pub_game_version]) 182 | } 183 | 184 | self._publish.to_pub(pub_id, target_servers, pub_process, pub_success, **ext_data) -------------------------------------------------------------------------------- /src/Tools.py: -------------------------------------------------------------------------------- 1 | import time 2 | import hashlib 3 | 4 | def g_md5(value): 5 | m = hashlib.md5() 6 | m.update(value) 7 | return m.hexdigest() 8 | 9 | def g_time(): 10 | return int(time.time()) 11 | 12 | if __name__ == '__main__': 13 | print g_md5('123456') -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | 4 | import os 5 | import sys 6 | import time 7 | import logging 8 | 9 | import redis 10 | import tornado.web 11 | import tornado.ioloop 12 | import tornado.options 13 | 14 | import Mongo 15 | import Publish 16 | 17 | import PageHandler 18 | import IndexHandler 19 | import LoginHandler 20 | import SocketHandler 21 | 22 | logging.basicConfig( 23 | level = logging.INFO, 24 | stream = sys.stdout, 25 | datefmt = "%Y-%m-%d %H:%M:%S", 26 | format = "[%(levelname)s %(asctime)s %(filename)s %(lineno)s] %(message)s" 27 | ) 28 | 29 | LOG = logging.getLogger(__name__) 30 | 31 | tornado.options.define('port', default=8888, help='run on the given port', type=int) 32 | 33 | class Application(tornado.web.Application): 34 | 35 | _redis = None 36 | _mongo = None 37 | _publish = None 38 | 39 | def __init__(self): 40 | handlers = [ 41 | (r'/', IndexHandler.IndexHandler), 42 | (r'/page', PageHandler.PageHandler), 43 | (r'/login', LoginHandler.LoginHandler), 44 | (r'/socket', SocketHandler.SocketHandler), 45 | ] 46 | 47 | settings = { 48 | 'debug' : True, 49 | 'cookie_secret' : 'ZyVB9oXwQt8S0R0kRvJ5/bZJc2sWuQLTos6GkHn/todo=', 50 | 'static_path' : os.path.dirname(__file__) + '/static', 51 | 'template_path' : os.path.dirname(__file__) + '/tpl', 52 | 'ui_modules' : {}, 53 | 'redis_server' : {'host' : '127.0.0.1', 'port' : 6379, 'db' : 1}, 54 | 'mongo_server' : {'host' : '127.0.0.1', 'port' : 27017}, 55 | 'zookeeper_server' : {'host' : '127.0.0.1', 'port' : 2181, 'root_node' : '/test'}, 56 | } 57 | 58 | self._redis = redis.Redis(connection_pool=redis.ConnectionPool(**settings['redis_server'])) 59 | self._mongo = Mongo.Mongo(**settings['mongo_server']) 60 | self._publish = Publish.Publish(**settings['zookeeper_server']) 61 | 62 | tornado.web.Application.__init__(self, handlers, **settings) 63 | 64 | def get_redis(self): 65 | return self._redis 66 | 67 | def get_mongo(self): 68 | return self._mongo 69 | 70 | def get_publish(self): 71 | return self._publish 72 | 73 | if __name__ == '__main__': 74 | #fixed codec can’t encode 75 | reload(sys) 76 | sys.setdefaultencoding('utf-8') 77 | 78 | try: 79 | os.environ['TZ'] = 'Asia/Shanghai' 80 | time.tzset() 81 | 82 | tornado.options.parse_command_line() 83 | Application().listen(tornado.options.options.port) 84 | tornado.ioloop.IOLoop.instance().start() 85 | except KeyboardInterrupt: 86 | print 'Interrupt' 87 | else: 88 | print 'Exit' 89 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/src/static/images/animated-overlay.gif -------------------------------------------------------------------------------- /src/static/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/src/static/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/static/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixqbar/pyweb/9c5bcdf583391763221acae8ed4c10c7b303b40c/src/static/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/static/js/app.common.js: -------------------------------------------------------------------------------- 1 | 2 | App = { 3 | 'connected' : false, 4 | 'pub_data' : { 5 | 'pub_id' : 0, 6 | 'pub_config_version' : '', 7 | 'pub_game_version' : '', 8 | 'pub_desc' : '', 9 | 'pub_status' : 'zip', 10 | 'pub_servers' : [] 11 | }, 12 | 'servers' : {} 13 | } 14 | 15 | App.log = function(message) { 16 | if (typeof message == 'object') { 17 | console.dir(message) 18 | } else { 19 | console.log(message) 20 | } 21 | } 22 | 23 | App.debug = function(message) { 24 | console.group('debug info') 25 | console.log(message) 26 | console.groupEnd() 27 | } 28 | 29 | App.init = function(pub_data) { 30 | this.pub_data = $.extend(this.pub_data, pub_data); 31 | this.debug(this.pub_data); 32 | 33 | return this; 34 | } 35 | 36 | App.run = function() { 37 | this.initHtml(); 38 | this.runSocket(); 39 | this.zipBind(); 40 | 41 | return this; 42 | } 43 | 44 | App.initHtml = function() { 45 | $('#zip_config_version').val(this.pub_data.pub_config_version); 46 | $('#zip_game_version').val(this.pub_data.pub_game_version); 47 | $('#zip_desc').val(this.pub_data.pub_desc); 48 | } 49 | 50 | App.runSocket = function() { 51 | var _this = this; 52 | this.ws = new WebSocket("ws://127.0.0.1:8888/socket") 53 | this.ws.onopen = function() { 54 | _this.connected = true; 55 | _this.debug('connected server') 56 | _this.loadServers(); 57 | } 58 | this.ws.onclose = function(event) { 59 | _this.connected = false; 60 | _this.debug('server disconnected code=' + event.code + ',reason=' + event.reason) 61 | } 62 | this.ws.onmessage = function(event) { 63 | if (0 == event.data.length) { 64 | return; 65 | } 66 | 67 | var response = JSON.parse(event.data) 68 | if (typeof response.executor == 'undefined' 69 | || typeof response.params == 'undefined') { 70 | return; 71 | } 72 | 73 | var executor = response.executor 74 | try { 75 | if (0 == executor.length || typeof executor != 'string') executor = 'log'; 76 | executor = eval('_this.' + executor); 77 | if (0 == response.params.length) { 78 | executor.call(_this); 79 | } else { 80 | executor.call(_this, response.params); 81 | } 82 | } catch (e) { 83 | _this.log(e); 84 | } 85 | } 86 | 87 | //to fixed websocket interrupted while page was loading in Fireox and Chrome 88 | $(window).on('beforeunload', function(){ 89 | _this.ws.close(); 90 | }); 91 | } 92 | 93 | App.loadServers = function() { 94 | this.socketSend({ 95 | 'action' : 'servers', 96 | 'params' : {}, 97 | 'callback' : 'loadServersExecutor' 98 | }); 99 | } 100 | 101 | App.loadServersExecutor = function(servers) { 102 | this.debug(servers); 103 | this.servers = servers; 104 | var _html = [' ALL
']; 105 | for (var i in servers) { 106 | if ($.inArray(servers[i].server_id + '', this.pub_data.pub_servers) >= 0) { 107 | _html.push('' + servers[i].server_name) 108 | } else { 109 | _html.push('' + servers[i].server_name) 110 | } 111 | } 112 | 113 | $('#servers_op').html(_html.join('')); 114 | 115 | if (1 == _html.length) { 116 | $('#servers_all').attr('disabled', 'disabled'); 117 | } else { 118 | if (this.pub_data.pub_status == 'zip' 119 | || this.pub_data.pub_status == 'zip_success') { 120 | $('#servers_all').click(function () { 121 | var checkboxes = $('#servers_op').find(':checkbox'); 122 | if ($(this).is(':checked')) { 123 | checkboxes.prop('checked', true); 124 | } else { 125 | checkboxes.prop('checked', false); 126 | } 127 | }); 128 | } else { 129 | $('#servers_op').find(':checkbox').attr('disabled', 'disabled'); 130 | } 131 | } 132 | } 133 | 134 | App.socketSend = function(message) { 135 | if (false == this.connected) { 136 | this.debug('not connected server'); 137 | return; 138 | } 139 | 140 | if (typeof message != 'object') { 141 | this.debug('error send message type') 142 | return; 143 | } 144 | 145 | this.ws.send(JSON.stringify(message)) 146 | } 147 | 148 | App.zipBind = function() { 149 | if (this.pub_data.pub_id > 0 && this.pub_data.pub_status != 'zip') { 150 | $('#btn-zip').off('click'); 151 | $('#btn-zip').attr('disabled', 'disabled'); 152 | $('#btn-zip').removeClass('btn-info'); 153 | $('#btn-zip').text('Finished zip'); 154 | $('#servers').show(); 155 | 156 | $('#zip_config_version').attr('disabled', 'disabled'); 157 | $('#zip_game_version').attr('disabled', 'disabled'); 158 | $('#zip_desc').attr('disabled', 'disabled'); 159 | 160 | $('#pub_result').append('
· zip finished (pub id=' + this.pub_data.pub_id + ')
'); 161 | 162 | this.sycBind(); 163 | 164 | return; 165 | } 166 | 167 | var _this = this; 168 | $('#btn-zip').click(function(){ 169 | var _zip_config_version = $('#zip_config_version').val(); 170 | var _zip_game_version = $('#zip_game_version').val(); 171 | var _zip_desc = $('#zip_desc').val(); 172 | 173 | if (0 == _zip_config_version.length) { 174 | $('#zip_config_version').css({"border":"2px solid red"}); 175 | $('#zip_config_version').attr('placeholder', 'Please type config version'); 176 | return; 177 | } else { 178 | $('#zip_config_version').css({"border":""}); 179 | } 180 | 181 | if (0 == _zip_game_version.length) { 182 | $('#zip_game_version').css({"border":"2px solid red"}); 183 | $('#zip_game_version').attr('placeholder', 'Please type game version'); 184 | return; 185 | } else { 186 | $('#zip_game_version').css({"border":""}); 187 | } 188 | 189 | if (0 == _zip_desc.length) { 190 | $('#zip_desc').css({"border":"2px solid red"}); 191 | $('#zip_desc').attr('placeholder', 'Please type a description for your publish'); 192 | return; 193 | } else { 194 | $('#zip_desc').css({"border":""}); 195 | } 196 | 197 | $('#zip_config_version').attr('disabled', 'disabled'); 198 | $('#zip_game_version').attr('disabled', 'disabled'); 199 | $('#zip_desc').attr('disabled', 'disabled'); 200 | 201 | $(this).off('click'); 202 | $(this).attr('disabled', 'disabled'); 203 | $(this).removeClass('btn-info'); 204 | $(this).text('Waiting ...'); 205 | 206 | _this.pub_data.pub_config_version = _zip_config_version; 207 | _this.pub_data.pub_game_version = _zip_game_version; 208 | _this.pub_data.pub_desc = _zip_desc; 209 | _this.pub_data.pub_id = _this.pub_data.pub_id; 210 | 211 | _this.zipExecute(); 212 | }); 213 | } 214 | 215 | App.sycBind = function() { 216 | if (this.pub_data.pub_id > 0 217 | && (this.pub_data.pub_status == 'syc_success' 218 | || this.pub_data.pub_status == 'pub' 219 | || this.pub_data.pub_status == 'pub_success')) { 220 | $('#btn-syc').off('click'); 221 | $('#btn-syc').attr('disabled', 'disabled'); 222 | $('#btn-syc').removeClass('btn-info'); 223 | $('#btn-syc').text('Finished syc'); 224 | 225 | $('#pub_result').append('
· sync all finished
'); 226 | 227 | this.pubBind(); 228 | 229 | return; 230 | } 231 | 232 | $('#btn-syc').removeAttr('disabled'); 233 | $('#btn-syc').addClass('btn-info'); 234 | 235 | var _this = this; 236 | $('#btn-syc').click(function(){ 237 | if (_this.pub_data.pub_id <= 0) { 238 | _this.debug('Unknown pub_id'); 239 | return 240 | } 241 | 242 | _this.pub_data.pub_servers.length = [] 243 | $("input[type='checkbox']:checked").each(function() { 244 | if ($(this).val()) { 245 | _this.pub_data.pub_servers.push($(this).val()); 246 | } 247 | }); 248 | 249 | _this.debug(_this.pub_data.pub_servers); 250 | 251 | if (0 == _this.pub_data.pub_servers.length) { 252 | $('#servers_op').css({"border":"2px solid red"}); 253 | return; 254 | } else { 255 | $('#servers_op').css({"border":""}); 256 | } 257 | 258 | $('#servers_op').find(':checkbox').attr('disabled', 'disabled'); 259 | 260 | $(this).off('click'); 261 | $(this).attr('disabled', 'disabled'); 262 | $(this).removeClass('btn-info'); 263 | $(this).text('Waiting ...'); 264 | 265 | _this.sycExecute(); 266 | }); 267 | } 268 | 269 | App.pubBind = function() { 270 | $('#btn-pub').removeAttr('disabled'); 271 | $('#btn-pub').addClass('btn-info'); 272 | 273 | var _this = this; 274 | 275 | $('#btn-pub').click(function(){ 276 | if (_this.pub_data.pub_id <= 0) { 277 | _this.debug('Unknown pub_id'); 278 | return 279 | } 280 | 281 | $(this).off('click'); 282 | $(this).attr('disabled', 'disabled'); 283 | $(this).removeClass('btn-info'); 284 | $(this).text('Waiting ...'); 285 | 286 | _this.pubExecute(); 287 | }); 288 | } 289 | 290 | App.zipExecute = function() { 291 | this.socketSend({ 292 | 'action' : 'to_zip', 293 | 'params' : { 294 | 'config_version' : this.pub_data.pub_config_version, 295 | 'game_version' : this.pub_data.pub_game_version, 296 | 'desc' : this.pub_data.pub_desc, 297 | 'pub_id' : this.pub_data.pub_id 298 | }, 299 | 'callback' : 'executeResponse' 300 | }); 301 | } 302 | 303 | App.sycExecute = function () { 304 | this.socketSend({ 305 | 'action' : 'to_syc', 306 | 'params' : { 307 | 'pub_id' : this.pub_data.pub_id, 308 | 'servers' : this.pub_data.pub_servers.sort().join(',') 309 | }, 310 | 'callback' : 'executeResponse' 311 | }); 312 | } 313 | 314 | App.pubExecute = function () { 315 | this.socketSend({ 316 | 'action' : 'to_pub', 317 | 'params' : { 318 | 'pub_id' : this.pub_data.pub_id, 319 | 'servers' : this.pub_data.pub_servers.sort().join(',') 320 | }, 321 | 'callback' : 'executeResponse' 322 | }); 323 | } 324 | 325 | App.executeResponse = function(response) { 326 | this.debug(response) 327 | 328 | if (response.code != 'ok') { 329 | return; 330 | } 331 | 332 | switch (response.status) { 333 | case 'zip': 334 | $('#pub_result').append('
· start zip (pub id=' + response.pub_id + ')
'); 335 | break; 336 | case 'zip_failed': 337 | $('#pub_result').append('
· zip failed
'); 338 | break; 339 | case 'zip_success': 340 | $('#pub_result').append('
· zip finished
'); 341 | $('#btn-zip').text('Finished zip'); 342 | $('#servers').show(); 343 | this.pub_data.pub_id = response.pub_id; 344 | this.sycBind(); 345 | break; 346 | case 'syc': 347 | $('#pub_result').append('
· start sync
'); 348 | break; 349 | case 'syc_failed': 350 | $('#pub_result').append('
· server ' + response.server + ' sync failed
'); 351 | break; 352 | case 'syc_process': 353 | $('#pub_result').append('
· server ' + response.server + ' sync finished
'); 354 | break; 355 | case 'syc_success': 356 | $('#btn-syc').text('Finished syc'); 357 | $('#pub_result').append('
· sync all finished
'); 358 | this.pubBind(); 359 | break; 360 | case 'pub': 361 | $('#pub_result').append('
· start pub
'); 362 | break; 363 | case 'pub_failed': 364 | $('#pub_result').append('
· server ' + response.server + ' pub failed
'); 365 | break; 366 | case 'pub_process': 367 | $('#pub_result').append('
· server ' + response.server + ' pub finished
'); 368 | break; 369 | case 'pub_success': 370 | $('#pub_result').append('
· pub all finished
'); 371 | $('#btn-pub').text('Finished pub'); 372 | break; 373 | } 374 | } -------------------------------------------------------------------------------- /src/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('