From e743ae3dbec94aa9e7d599e627915cdbdb8de8a2 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 16:36:55 +0800 Subject: [PATCH 01/27] model --- jlog/models.py | 12 ++++++ jlog/views.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/jlog/models.py b/jlog/models.py index c8ffd77a2..dfb418651 100644 --- a/jlog/models.py +++ b/jlog/models.py @@ -1,4 +1,6 @@ from django.db import models +from juser.models import User +import time class Log(models.Model): @@ -47,3 +49,13 @@ class FileLog(models.Model): datetime = models.DateTimeField(auto_now=True) +class TermLog(models.Model): + user = models.ManyToManyField(User) + logPath = models.TextField() + filename = models.CharField(max_length=40) + logPWD = models.TextField() # log zip file's + nick = models.TextField(null=True) # log's nick name + log = models.TextField(null=True) + history = models.TextField(null=True) + timestamp = models.IntegerField(default=int(time.time())) + datetimestamp = models.DateTimeField(auto_now_add=True) diff --git a/jlog/views.py b/jlog/views.py index df726c1a7..1357cb872 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -8,7 +8,10 @@ from jperm.perm_api import user_have_perm from django.http import HttpResponseNotFound from jlog.log_api import renderTemplate -from jlog.models import Log, ExecLog, FileLog +from jlog.models import Log, ExecLog, FileLog, TermLog +from jumpserver.settings import LOG_DIR +import zipfile +import json @require_role('admin') @@ -32,12 +35,13 @@ def log_list(request, offset): posts = ExecLog.objects.all().order_by('-id') keyword = request.GET.get('keyword', '') if keyword: - posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(cmd__icontains=keyword)) + posts = posts.filter(Q(user__icontains=keyword) | Q(host__icontains=keyword) | Q(cmd__icontains=keyword)) elif offset == 'file': posts = FileLog.objects.all().order_by('-id') keyword = request.GET.get('keyword', '') if keyword: - posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(filename__icontains=keyword)) + posts = posts.filter( + Q(user__icontains=keyword) | Q(host__icontains=keyword) | Q(filename__icontains=keyword)) else: posts = Log.objects.filter(is_finished=True).order_by('-start_time') username_all = set([log.user for log in Log.objects.all()]) @@ -144,3 +148,101 @@ def log_detail(request, offset): except (SyntaxError, NameError): result = {} return my_render('jlog/file_detail.html', locals(), request) + + +import pyte + + +class TermLogRecorder(object): + def __init__(self, user): + self.log = {} + self.user = user + self.recoderStartTime = time.time() + self.__init_screen_stream() + self.recoder = True + self._commands = [] + self.vim_pattern = re.compile(r'\W?vi[m]?\s.* | \W?fg\s.*', re.X) + self._in_vim = False + self.CMD = {} + + def __init_screen_stream(self): + """ + 初始化虚拟屏幕和字符流 + """ + self._stream = pyte.ByteStream() + self._screen = pyte.Screen(80, 24) + self._stream.attach(self._screen) + + def _command(self): + self._commands = [] + for i in self._screen.display: + if i.strip().__len__() > 0: + self._commands.append(i.strip()) + self._screen.reset() + if not self._commands[-1] == '': + self.CMD[str(time.time())] = self._commands[-1] + + def write(self, msg): + if self.recoder and (not self._in_vim): + if self._commands.__len__() == 0: + self._stream.feed(msg) + elif not self.vim_pattern.search(self._commands[-1]): + self._stream.feed(msg) + else: + self._in_vim = True + self._command() + else: + if self._in_vim: + if re.compile(r'\[\?1049', re.X).search(msg.decode('utf-8', 'replace')): + self._in_vim = False + self._commands.append('') + self._screen.reset() + else: + self._command() + self.log[str(time.time() - self.recoderStartTime)] = msg.decode('utf-8', 'replace') + + def show(self): + return self._screen.display + + def save(self, path=LOG_DIR): + date = datetime.datetime.now().strftime('%Y%m%d') + filename = str(uuid.uuid4()) + filepath = os.path.join(path, 'tty', date, filename + '.zip') + while os.path.isfile(filepath): + filename = str(uuid.uuid4()) + filepath = os.path.join(path, 'tty', date, filename + '.zip') + password = str(uuid.uuid4()) + try: + zf = zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED) + zf.setpassword(password) + zf.writestr(filename, json.dumps(self.log)) + zf.close() + record = TermLog.objects.create(logPath=filepath, logPWD=password, filename=filename, + history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) + record.user.add(self.user) + except: + record = TermLog.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log), + filename=filename, history=json.dumps(self.CMD), + timestamp=int(self.recoderStartTime)) + record.user.add(self.user) + + def list(self): + return TermLog.objects.filter(user=self.user.id) + + def load(self, filename): + self.file = TermLog.objects.get(user=self.user.id, filename=filename) + if self.file.logPath == 'locale': + return self.file.log + else: + try: + zf = zipfile.ZipFile(self.file.logPath, 'r', zipfile.ZIP_DEFLATED) + zf.setpassword(self.file.logPWD) + self.data = zf.read(zf.namelist()[0]) + return self.data + except KeyError: + return 'ERROR: Did not find %s file' % filename + +# @require_role('admin') +# def test(request): +# tr = TermLogRecorder(request.user) +# return HttpResponse(tr.load(tr.list().all()[0].filename)) From 52d8825d957ed70893130ddd86ee48150aee4677 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 17:05:02 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dtmux=E7=AD=89=E4=BC=9A?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=87=BA=E7=8E=B0=E5=91=BD=E4=BB=A4=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jlog/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jlog/views.py b/jlog/views.py index 1357cb872..15dc22c52 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -174,13 +174,12 @@ class TermLogRecorder(object): self._stream.attach(self._screen) def _command(self): - self._commands = [] for i in self._screen.display: if i.strip().__len__() > 0: self._commands.append(i.strip()) + if not i.strip() == '': + self.CMD[str(time.time())] = self._commands[-1] self._screen.reset() - if not self._commands[-1] == '': - self.CMD[str(time.time())] = self._commands[-1] def write(self, msg): if self.recoder and (not self._in_vim): @@ -199,6 +198,10 @@ class TermLogRecorder(object): self._screen.reset() else: self._command() + print "<<<<<<<<<<<<<<<<" + print self._commands + print self.CMD + print ">>>>>>>>>>>>>>>>" self.log[str(time.time() - self.recoderStartTime)] = msg.decode('utf-8', 'replace') def show(self): From fa1f2404d98b5046f5c7f7f14c846a5bfd4e6eb5 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 17:05:35 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run_server.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/run_server.py b/run_server.py index 0823ebd8c..b1df25e39 100755 --- a/run_server.py +++ b/run_server.py @@ -37,7 +37,7 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' from jumpserver.settings import IP, PORT define("port", default=PORT, help="run on the given port", type=int) define("host", default=IP, help="run port on given host", type=str) - +from jlog.views import TermLogRecorder def django_request_support(func): @functools.wraps(func) @@ -63,6 +63,7 @@ def require_auth(role='user'): logger.debug('Websocket: session: %s' % session) if session and datetime.datetime.now() < session.expire_date: user_id = session.get_decoded().get('_auth_user_id') + request.user_id = user_id user = get_object(User, id=user_id) if user: logger.debug('Websocket: user [ %s ] request websocket' % user.username) @@ -311,6 +312,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): role_name = self.get_argument('role', 'sb') asset_id = self.get_argument('id', 9999) asset = get_object(Asset, id=asset_id) + self.termlog = TermLogRecorder(User.objects.get(id=self.user_id)) if asset: roles = user_have_perm(self.user, asset) logger.debug(roles) @@ -361,6 +363,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): jsondata.get('data').get('resize').get('rows', 24) ) elif jsondata.get('data'): + self.termlog.recoder = True self.term.input_mode = True if str(jsondata['data']) in ['\r', '\n', '\r\n']: if self.term.vim_flag: @@ -383,6 +386,8 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): def on_close(self): logger.debug('Websocket: Close request') + print self.termlog.CMD + self.termlog.save() if self in WebTerminalHandler.clients: WebTerminalHandler.clients.remove(self) try: @@ -413,6 +418,8 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.term.vim_data += recv try: self.write_message(data.decode('utf-8', 'replace')) + self.termlog.write(data) + self.termlog.recoder = False now_timestamp = time.time() self.log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) self.log_file_f.write(data) From 388ebe3beefe574610566aae8599f9c07108598a Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 17:07:57 +0800 Subject: [PATCH 04/27] update --- jlog/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jlog/views.py b/jlog/views.py index 15dc22c52..ee7e0a1da 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -198,10 +198,10 @@ class TermLogRecorder(object): self._screen.reset() else: self._command() - print "<<<<<<<<<<<<<<<<" - print self._commands - print self.CMD - print ">>>>>>>>>>>>>>>>" + # print "<<<<<<<<<<<<<<<<" + # print self._commands + # print self.CMD + # print ">>>>>>>>>>>>>>>>" self.log[str(time.time() - self.recoderStartTime)] = msg.decode('utf-8', 'replace') def show(self): From 365a5ccd46af0395dcc2f28d117de71ce796a026 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 18:25:18 +0800 Subject: [PATCH 05/27] update_TermLogRecorder --- jlog/views.py | 71 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/jlog/views.py b/jlog/views.py index ee7e0a1da..dfe2a8ac8 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -12,6 +12,7 @@ from jlog.models import Log, ExecLog, FileLog, TermLog from jumpserver.settings import LOG_DIR import zipfile import json +import pyte @require_role('admin') @@ -150,17 +151,35 @@ def log_detail(request, offset): return my_render('jlog/file_detail.html', locals(), request) -import pyte - - class TermLogRecorder(object): + """ + TermLogRecorder + --- + Author: liuzheng + This class is use for record the terminal output log. + self.commands is pure commands list, it will have empty item '' because in vi/vim model , I made it log noting. + self.CMD is the command with timestamp, like this {'1458723794.88': u'ls', '1458723799.82': u'tree'}. + self.log is the all output with delta time log. + self.vim_pattern is the regexp for check vi/vim/fg model. + Usage: + recorder = TermLogRecorder(User) + recoder.write(messages) + recoder.save() # save all log into database + list = recoder.list() # will give a object about this user's all log info + recoder.load_full_log(filemane) # will get full log + recoder.load_history(filename) # will only get the command history list + """ + def __init__(self, user): self.log = {} self.user = user self.recoderStartTime = time.time() self.__init_screen_stream() self.recoder = True - self._commands = [] + self.commands = [] + self._lists = None + self.file = None + self._data = None self.vim_pattern = re.compile(r'\W?vi[m]?\s.* | \W?fg\s.*', re.X) self._in_vim = False self.CMD = {} @@ -176,16 +195,16 @@ class TermLogRecorder(object): def _command(self): for i in self._screen.display: if i.strip().__len__() > 0: - self._commands.append(i.strip()) + self.commands.append(i.strip()) if not i.strip() == '': - self.CMD[str(time.time())] = self._commands[-1] + self.CMD[str(time.time())] = self.commands[-1] self._screen.reset() def write(self, msg): if self.recoder and (not self._in_vim): - if self._commands.__len__() == 0: + if self.commands.__len__() == 0: self._stream.feed(msg) - elif not self.vim_pattern.search(self._commands[-1]): + elif not self.vim_pattern.search(self.commands[-1]): self._stream.feed(msg) else: self._in_vim = True @@ -194,19 +213,16 @@ class TermLogRecorder(object): if self._in_vim: if re.compile(r'\[\?1049', re.X).search(msg.decode('utf-8', 'replace')): self._in_vim = False - self._commands.append('') + self.commands.append('') self._screen.reset() else: self._command() # print "<<<<<<<<<<<<<<<<" - # print self._commands + # print self.commands # print self.CMD # print ">>>>>>>>>>>>>>>>" self.log[str(time.time() - self.recoderStartTime)] = msg.decode('utf-8', 'replace') - def show(self): - return self._screen.display - def save(self, path=LOG_DIR): date = datetime.datetime.now().strftime('%Y%m%d') filename = str(uuid.uuid4()) @@ -230,22 +246,33 @@ class TermLogRecorder(object): record.user.add(self.user) def list(self): - return TermLog.objects.filter(user=self.user.id) + tmp = [] + self._lists = TermLog.objects.filter(user=self.user.id) + for i in self._lists.all(): + tmp.append( + {'filename': i.filename, 'locale': i.logPath == 'locale', 'nick': i.nick, 'timestamp': i.timestamp, + 'date': i.datetimestamp}) + return tmp - def load(self, filename): - self.file = TermLog.objects.get(user=self.user.id, filename=filename) + def load_full_log(self, filename): + if self._lists: + self.file = self._lists.get(filename=filename) + else: + self.file = TermLog.objects.get(user=self.user.id, filename=filename) if self.file.logPath == 'locale': return self.file.log else: try: zf = zipfile.ZipFile(self.file.logPath, 'r', zipfile.ZIP_DEFLATED) zf.setpassword(self.file.logPWD) - self.data = zf.read(zf.namelist()[0]) - return self.data + self._data = zf.read(zf.namelist()[0]) + return self._data except KeyError: return 'ERROR: Did not find %s file' % filename -# @require_role('admin') -# def test(request): -# tr = TermLogRecorder(request.user) -# return HttpResponse(tr.load(tr.list().all()[0].filename)) + def load_history(self, filename): + if self._lists: + self.file = self._lists.get(filename=filename) + else: + self.file = TermLog.objects.get(user=self.user.id, filename=filename) + return self.file.history From 17a7470a2a9056ccc9ba61b86e045034b18c8eea Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 18:26:23 +0800 Subject: [PATCH 06/27] update_TermLogRecorder --- jlog/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jlog/views.py b/jlog/views.py index dfe2a8ac8..2f1cb283c 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -186,7 +186,7 @@ class TermLogRecorder(object): def __init_screen_stream(self): """ - 初始化虚拟屏幕和字符流 + Initializing the virtual screen and the character stream """ self._stream = pyte.ByteStream() self._screen = pyte.Screen(80, 24) From b3f83c3362e9033904f8718027d60f5b8fe7f11b Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Wed, 23 Mar 2016 22:38:02 +0800 Subject: [PATCH 07/27] update --- jlog/views.py | 119 +++++++++++++++++++++++++++++++++++++------------- run_server.py | 14 +++--- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/jlog/views.py b/jlog/views.py index 2f1cb283c..41da9fe83 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -162,17 +162,25 @@ class TermLogRecorder(object): self.log is the all output with delta time log. self.vim_pattern is the regexp for check vi/vim/fg model. Usage: - recorder = TermLogRecorder(User) + recorder = TermLogRecorder(user=UserObject) # or recorder = TermLogRecorder(uid=UserID) recoder.write(messages) recoder.save() # save all log into database + # The following methods all have `user`,`uid`,args. Same as __init__ list = recoder.list() # will give a object about this user's all log info recoder.load_full_log(filemane) # will get full log recoder.load_history(filename) # will only get the command history list + recoder.share_to(filename,user=UserObject) # or recoder.share_to(filename,uid=UserID). will share this commands to someone + recoder.unshare_to(filename,user=UserObject) # or recoder.unshare_to(filename,uid=UserID). will unshare this commands to someone """ - def __init__(self, user): + def __init__(self, user=None, uid=None): self.log = {} - self.user = user + if isinstance(user, User): + self.user = user + elif uid: + self.user = User.objects.get(id=uid) + else: + self.user = None self.recoderStartTime = time.time() self.__init_screen_stream() self.recoder = True @@ -227,6 +235,8 @@ class TermLogRecorder(object): date = datetime.datetime.now().strftime('%Y%m%d') filename = str(uuid.uuid4()) filepath = os.path.join(path, 'tty', date, filename + '.zip') + if not os.path.isdir(os.path.join(path, 'tty', date)): + os.makedirs(os.path.join(path, 'tty', date), mode=0777) while os.path.isfile(filepath): filename = str(uuid.uuid4()) filepath = os.path.join(path, 'tty', date, filename + '.zip') @@ -238,41 +248,90 @@ class TermLogRecorder(object): zf.close() record = TermLog.objects.create(logPath=filepath, logPWD=password, filename=filename, history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) - record.user.add(self.user) + if self.user: + record.user.add(self.user) except: record = TermLog.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log), filename=filename, history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) - record.user.add(self.user) + if self.user: + record.user.add(self.user) - def list(self): + def list(self, user=None, uid=None): tmp = [] - self._lists = TermLog.objects.filter(user=self.user.id) - for i in self._lists.all(): - tmp.append( - {'filename': i.filename, 'locale': i.logPath == 'locale', 'nick': i.nick, 'timestamp': i.timestamp, - 'date': i.datetimestamp}) + if isinstance(user, User): + user = user + elif uid: + user = User.objects.get(id=uid) + else: + user = self.user + if user: + self._lists = TermLog.objects.filter(user=user.id) + for i in self._lists.all(): + tmp.append( + {'filename': i.filename, 'locale': i.logPath == 'locale', 'nick': i.nick, 'timestamp': i.timestamp, + 'date': i.datetimestamp}) return tmp - def load_full_log(self, filename): - if self._lists: - self.file = self._lists.get(filename=filename) + def load_full_log(self, filename, user=None, uid=None): + if isinstance(user, User): + user = user + elif uid: + user = User.objects.get(id=uid) else: - self.file = TermLog.objects.get(user=self.user.id, filename=filename) - if self.file.logPath == 'locale': - return self.file.log - else: - try: - zf = zipfile.ZipFile(self.file.logPath, 'r', zipfile.ZIP_DEFLATED) - zf.setpassword(self.file.logPWD) - self._data = zf.read(zf.namelist()[0]) - return self._data - except KeyError: - return 'ERROR: Did not find %s file' % filename + user = self.user + if user: + if self._lists: + self.file = self._lists.get(filename=filename) + else: + self.file = TermLog.objects.get(user=user.id, filename=filename) + if self.file.logPath == 'locale': + return self.file.log + else: + try: + zf = zipfile.ZipFile(self.file.logPath, 'r', zipfile.ZIP_DEFLATED) + zf.setpassword(self.file.logPWD) + self._data = zf.read(zf.namelist()[0]) + return self._data + except KeyError: + return 'ERROR: Did not find %s file' % filename + return 'ERROR User(None)' - def load_history(self, filename): - if self._lists: - self.file = self._lists.get(filename=filename) + def load_history(self, filename, user=None, uid=None): + if isinstance(user, User): + user = user + elif uid: + user = User.objects.get(id=uid) else: - self.file = TermLog.objects.get(user=self.user.id, filename=filename) - return self.file.history + user = self.user + if user: + if self._lists: + self.file = self._lists.get(filename=filename) + else: + self.file = TermLog.objects.get(user=user.id, filename=filename) + return self.file.history + return 'ERROR User(None)' + + def share_to(self, filename, user=None, uid=None): + if isinstance(user, User): + user = user + elif uid: + user = User.objects.get(id=uid) + else: + pass + if user: + TermLog.objects.get(filename=filename).user.add(user) + return True + return False + + def unshare_to(self, filename, user=None, uid=None): + if isinstance(user, User): + user = user + elif uid: + user = User.objects.get(id=uid) + else: + pass + if user: + TermLog.objects.get(filename=filename).user.remove(user) + return True + return False diff --git a/run_server.py b/run_server.py index b1df25e39..1f2a191da 100755 --- a/run_server.py +++ b/run_server.py @@ -22,7 +22,7 @@ import tornado.httpclient from tornado.websocket import WebSocketClosedError from tornado.options import define, options -from pyinotify import WatchManager, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier +# from pyinotify import WatchManager, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier import select from connect import Tty, User, Asset, PermRole, logger, get_object, gen_resource @@ -97,12 +97,12 @@ class MyThread(threading.Thread): pass -class EventHandler(ProcessEvent): - def __init__(self, client=None): - self.client = client - - def process_IN_MODIFY(self, event): - self.client.write_message(f.read()) +# class EventHandler(ProcessEvent): +# def __init__(self, client=None): +# self.client = client +# +# def process_IN_MODIFY(self, event): +# self.client.write_message(f.read()) def file_monitor(path='.', client=None): From 3ab0c94496d3d52f563d470a4180abac6dc0f14a Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Thu, 24 Mar 2016 12:49:06 +0800 Subject: [PATCH 08/27] update settings for myself, use sqlite --- jumpserver/settings.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/jumpserver/settings.py b/jumpserver/settings.py index d01dcc96a..1ab24fac5 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -99,23 +99,23 @@ WSGI_APPLICATION = 'jumpserver.wsgi.application' # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': DB_DATABASE, - 'USER': DB_USER, - 'PASSWORD': DB_PASSWORD, - 'HOST': DB_HOST, - 'PORT': DB_PORT, - } -} - # DATABASES = { # 'default': { -# 'ENGINE': 'django.db.backends.sqlite3', -# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), +# 'ENGINE': 'django.db.backends.mysql', +# 'NAME': DB_DATABASE, +# 'USER': DB_USER, +# 'PASSWORD': DB_PASSWORD, +# 'HOST': DB_HOST, +# 'PORT': DB_PORT, # } # } + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', From 357aea169338d114652a00145dd076d6612fc75d Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Thu, 24 Mar 2016 12:49:39 +0800 Subject: [PATCH 09/27] update jumpserver.conf only for myself --- jumpserver.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jumpserver.conf b/jumpserver.conf index 05919d351..a2b4132d1 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -1,8 +1,8 @@ [base] -url = http://192.168.10.165 +url = http://127.0.0.1 key = 941enj9neshd1wes ip = 0.0.0.0 -port = 80 +port = 8000 log = debug [db] From ba5e90abc3c9a3fcd577310aa0214a4379549023 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Thu, 24 Mar 2016 17:17:47 +0800 Subject: [PATCH 10/27] update --- jlog/views.py | 2 +- run_server.py | 8 +++++++- static/js/webterminal.js | 16 ++++++++-------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/jlog/views.py b/jlog/views.py index 41da9fe83..69716bda2 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -183,7 +183,7 @@ class TermLogRecorder(object): self.user = None self.recoderStartTime = time.time() self.__init_screen_stream() - self.recoder = True + self.recoder = False self.commands = [] self._lists = None self.file = None diff --git a/run_server.py b/run_server.py index 1f2a191da..e19bc1df2 100755 --- a/run_server.py +++ b/run_server.py @@ -35,10 +35,12 @@ except ImportError: os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' from jumpserver.settings import IP, PORT + define("port", default=PORT, help="run on the given port", type=int) define("host", default=IP, help="run port on given host", type=str) from jlog.views import TermLogRecorder + def django_request_support(func): @functools.wraps(func) def _deco(*args, **kwargs): @@ -46,6 +48,7 @@ def django_request_support(func): response = func(*args, **kwargs) request_finished.send_robust(func) return response + return _deco @@ -83,6 +86,7 @@ def require_auth(role='user'): logger.warning('Websocket: Request auth failed.') return _deco2 + return _deco @@ -340,6 +344,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.term.remote_ip = self.request.remote_ip self.ssh = self.term.get_connection() self.channel = self.ssh.invoke_shell(term='xterm') + self.channel.resize_pty(80, 24) WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) WebTerminalHandler.clients.append(self) @@ -421,7 +426,7 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.termlog.write(data) self.termlog.recoder = False now_timestamp = time.time() - self.log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(data))) + self.log_time_f.write('%s %s\n' % (round(now_timestamp - pre_timestamp, 4), len(data))) self.log_file_f.write(data) pre_timestamp = now_timestamp self.log_file_f.flush() @@ -481,6 +486,7 @@ def main(): tornado.ioloop.IOLoop.instance().start() + if __name__ == '__main__': # tornado.options.parse_command_line() # app = Application() diff --git a/static/js/webterminal.js b/static/js/webterminal.js index e5ea997f2..50e21276f 100644 --- a/static/js/webterminal.js +++ b/static/js/webterminal.js @@ -104,13 +104,13 @@ $(document).ready(function () { $('#ssh').show(); var term_client = openTerminal(options); console.log(rowHeight); - - window.onresize = function () { - var geom = resize(); - console.log(geom); - term_client.term.resize(geom.cols, geom.rows); - term_client.client.send({'resize': {'rows': geom.rows, 'cols': geom.cols}}); - $('#ssh').show(); - } + // by liuzheng712 because it will bring record bug + //window.onresize = function () { + // var geom = resize(); + // console.log(geom); + // term_client.term.resize(geom.cols, geom.rows); + // term_client.client.send({'resize': {'rows': geom.rows, 'cols': geom.cols}}); + // $('#ssh').show(); + //} }); \ No newline at end of file From 3fcd9589a444b90017f917db9d2384fe08008b1b Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Thu, 24 Mar 2016 17:29:50 +0800 Subject: [PATCH 11/27] update --- run_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/run_server.py b/run_server.py index e19bc1df2..a123d3ebf 100755 --- a/run_server.py +++ b/run_server.py @@ -344,7 +344,6 @@ class WebTerminalHandler(tornado.websocket.WebSocketHandler): self.term.remote_ip = self.request.remote_ip self.ssh = self.term.get_connection() self.channel = self.ssh.invoke_shell(term='xterm') - self.channel.resize_pty(80, 24) WebTerminalHandler.tasks.append(MyThread(target=self.forward_outbound)) WebTerminalHandler.clients.append(self) From 8e8d8c9d6a32b7550750793d361b4af17f151c29 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Fri, 25 Mar 2016 22:19:31 +0800 Subject: [PATCH 12/27] =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=9B=9E=E6=94=BEOK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jlog/views.py | 41 ++++++++++++++++++++++++++++------------- jumpserver/settings.py | 26 +++++++++++++------------- run_server.py | 14 +++++++------- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/jlog/views.py b/jlog/views.py index 69716bda2..085a9307e 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -1,8 +1,7 @@ # coding:utf-8 from django.db.models import Q from django.template import RequestContext -from django.shortcuts import render_to_response - +from django.shortcuts import render_to_response, render from jumpserver.api import * from jperm.perm_api import user_have_perm from django.http import HttpResponseNotFound @@ -114,20 +113,36 @@ def log_history(request): return HttpResponse('无日志记录!') +# @require_role('admin') +# def log_record(request): +# log_id = request.GET.get('id', 0) +# log = Log.objects.filter(id=int(log_id)) +# if log: +# log = log[0] +# log_file = log.log_path + '.log' +# log_time = log.log_path + '.time' +# if os.path.isfile(log_file) and os.path.isfile(log_time): +# content = renderTemplate(log_file, log_time) +# return HttpResponse(content) +# else: +# return HttpResponse('无日志记录!') @require_role('admin') def log_record(request): - log_id = request.GET.get('id', 0) - log = Log.objects.filter(id=int(log_id)) - if log: - log = log[0] - log_file = log.log_path + '.log' - log_time = log.log_path + '.time' - if os.path.isfile(log_file) and os.path.isfile(log_time): - content = renderTemplate(log_file, log_time) - return HttpResponse(content) + """ + Author: liuzheng712@gmail.com + """ + if request.method == "GET": + return render(request, 'jlog/record.html') + elif request.method == "POST": + log_id = request.REQUEST.get('id', None) + if log_id: + logs = TermLogRecorder(request.user) + log = TermLog.objects.get(id=int(log_id)) + return HttpResponse(logs.load_full_log(log.filename)) else: - return HttpResponse('无日志记录!') - + return HttpResponse("ERROR") + else: + return HttpResponse("ERROR METHOD!") @require_role('admin') def log_detail(request, offset): diff --git a/jumpserver/settings.py b/jumpserver/settings.py index 1ab24fac5..d01dcc96a 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -99,23 +99,23 @@ WSGI_APPLICATION = 'jumpserver.wsgi.application' # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.mysql', -# 'NAME': DB_DATABASE, -# 'USER': DB_USER, -# 'PASSWORD': DB_PASSWORD, -# 'HOST': DB_HOST, -# 'PORT': DB_PORT, -# } -# } - DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'ENGINE': 'django.db.backends.mysql', + 'NAME': DB_DATABASE, + 'USER': DB_USER, + 'PASSWORD': DB_PASSWORD, + 'HOST': DB_HOST, + 'PORT': DB_PORT, } } + +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), +# } +# } TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', diff --git a/run_server.py b/run_server.py index 95c7c1448..71ddf0ad2 100755 --- a/run_server.py +++ b/run_server.py @@ -22,7 +22,7 @@ import tornado.httpclient from tornado.websocket import WebSocketClosedError from tornado.options import define, options -# from pyinotify import WatchManager, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier +from pyinotify import WatchManager, ProcessEvent, IN_DELETE, IN_CREATE, IN_MODIFY, AsyncNotifier import select from connect import Tty, User, Asset, PermRole, logger, get_object, gen_resource @@ -101,12 +101,12 @@ class MyThread(threading.Thread): pass -# class EventHandler(ProcessEvent): -# def __init__(self, client=None): -# self.client = client -# -# def process_IN_MODIFY(self, event): -# self.client.write_message(f.read()) +class EventHandler(ProcessEvent): + def __init__(self, client=None): + self.client = client + + def process_IN_MODIFY(self, event): + self.client.write_message(f.read()) def file_monitor(path='.', client=None): From 2d1e001ddfb46cefa43f014509508627d1865da6 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Fri, 25 Mar 2016 22:20:50 +0800 Subject: [PATCH 13/27] =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=9B=9E=E6=94=BEok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/angular-route.min.js | 13 ++ static/js/angular.min.js | 250 +++++++++++++++++++++++++++++++++ static/js/record.js | 157 +++++++++++++++++++++ templates/jlog/record.html | 26 ++++ 4 files changed, 446 insertions(+) create mode 100644 static/js/angular-route.min.js create mode 100644 static/js/angular.min.js create mode 100644 static/js/record.js create mode 100644 templates/jlog/record.html diff --git a/static/js/angular-route.min.js b/static/js/angular-route.min.js new file mode 100644 index 000000000..e764c4d4e --- /dev/null +++ b/static/js/angular-route.min.js @@ -0,0 +1,13 @@ +/* + AngularJS v1.2.5 + (c) 2010-2014 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(b&&b.$template){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";a.$on("$routeChangeSuccess", +v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,e){var b=e.caseInsensitiveMatch, +f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";k[b]=e.extend({redirectTo:a}, +q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)? +c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,{cache:v}).then(function(a){return a.data}))); +e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g").append(b).html();try{return b[0].nodeType===pb?z(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+z(b)})}catch(d){return z(c)}}function rc(b){try{return decodeURIComponent(b)}catch(a){}} +function sc(b){var a={},c,d;r((b||"").split("&"),function(b){b&&(c=b.replace(/\+/g,"%20").split("="),d=rc(c[0]),y(d)&&(b=y(c[1])?rc(c[1]):!0,tc.call(a,d)?H(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Pb(b){var a=[];r(b,function(b,d){H(b)?r(b,function(b){a.push(Ea(d,!0)+(!0===b?"":"="+Ea(b,!0)))}):a.push(Ea(d,!0)+(!0===b?"":"="+Ea(b,!0)))});return a.length?a.join("&"):""}function qb(b){return Ea(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Ea(b,a){return encodeURIComponent(b).replace(/%40/gi, +"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Id(b,a){var c,d,e=rb.length;b=A(b);for(d=0;d/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=ab(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector", +d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;Q&&e.test(Q.name)&&(c.debugInfoEnabled=!0,Q.name=Q.name.replace(e,""));if(Q&&!f.test(Q.name))return d();Q.name=Q.name.replace(f,"");ca.resumeBootstrap=function(b){r(b,function(b){a.push(b)});return d()};G(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function Kd(){Q.name="NG_ENABLE_DEBUG_INFO!"+Q.name;Q.location.reload()}function Ld(b){b=ca.element(b).injector();if(!b)throw Ja("test");return b.get("$$testability")} +function vc(b,a){a=a||"_";return b.replace(Md,function(b,d){return(d?a:"")+b.toLowerCase()})}function Nd(){var b;wc||((ta=Q.jQuery)&&ta.fn.on?(A=ta,w(ta.fn,{scope:Ka.scope,isolateScope:Ka.isolateScope,controller:Ka.controller,injector:Ka.injector,inheritedData:Ka.inheritedData}),b=ta.cleanData,ta.cleanData=function(a){var c;if(Qb)Qb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=ta._data(e,"events"))&&c.$destroy&&ta(e).triggerHandler("$destroy");b(a)}):A=T,ca.element=A,wc=!0)}function Rb(b,a,c){if(!b)throw Ja("areq", +a||"?",c||"required");return b}function sb(b,a,c){c&&H(b)&&(b=b[b.length-1]);Rb(G(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function La(b,a){if("hasOwnProperty"===b)throw Ja("badname",a);}function xc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g")+d[2];for(d=d[0];d--;)c=c.lastChild;f=Ya(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)}); +return e}function T(b){if(b instanceof T)return b;var a;C(b)&&(b=N(b),a=!0);if(!(this instanceof T)){if(a&&"<"!=b.charAt(0))throw Tb("nosel");return new T(b)}if(a){a=W;var c;b=(c=gf.exec(b))?[a.createElement(c[1])]:(c=Hc(b,a))?c.childNodes:[]}Ic(this,b)}function Ub(b){return b.cloneNode(!0)}function wb(b,a){a||xb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d 4096 bytes)!"));else{if(p.cookie!==y)for(y=p.cookie,d=y.split("; "),fa={},f=0;fk&&this.remove(q.key),b},get:function(a){if(k").parent()[0])});var f=S(a,b,a,c,d,e);D.$$addScopeClass(a); +var g=null;return function(b,c,d){Rb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==va(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?A(Xb(g,A("
").append(a).html())):c?Ka.clone.call(a):a;if(h)for(var k in h)d.data("$"+k+"Controller",h[k].instance);D.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a,b,c,d,e,f){function g(a, +c,d,e){var f,k,l,q,p,s,M;if(m)for(M=Array(c.length),q=0;qK.priority)break;if(V=K.scope)K.templateUrl||(J(V)?(Na("new/isolated scope",P||F,K,w),P=K):Na("new/isolated scope",P,K,w)),F=F||K;da=K.name;!K.templateUrl&&K.controller&&(V=K.controller,S=S||{},Na("'"+da+"' controller",S[da],K,w),S[da]=K);if(V=K.transclude)ka=!0,K.$$tlb||(Na("transclusion",fa,K,w),fa=K),"element"==V?(E=!0,I=K.priority,V=w,w=e.$$element=A(W.createComment(" "+da+": "+ +e[da]+" ")),d=w[0],T(g,Za.call(V,0),d),fb=D(V,f,I,k&&k.name,{nonTlbTranscludeDirective:fa})):(V=A(Ub(d)).contents(),w.empty(),fb=D(V,f));if(K.template)if(x=!0,Na("template",ma,K,w),ma=K,V=G(K.template)?K.template(w,e):K.template,V=Tc(V),K.replace){k=K;V=Sb.test(V)?Uc(Xb(K.templateNamespace,N(V))):[];d=V[0];if(1!=V.length||d.nodeType!==qa)throw la("tplrt",da,"");T(g,w,d);Q={$attr:{}};V=X(d,[],Q);var aa=a.splice(z+1,a.length-(z+1));P&&y(V);a=a.concat(V).concat(aa);R(e,Q);Q=a.length}else w.html(V);if(K.templateUrl)x= +!0,Na("template",ma,K,w),ma=K,K.replace&&(k=K),B=of(a.splice(z,a.length-z),w,e,g,ka&&fb,l,p,{controllerDirectives:S,newIsolateScopeDirective:P,templateDirective:ma,nonTlbTranscludeDirective:fa}),Q=a.length;else if(K.compile)try{za=K.compile(w,e,fb),G(za)?s(null,za,Oa,U):za&&s(za.pre,za.post,Oa,U)}catch(pf){c(pf,wa(w))}K.terminal&&(B.terminal=!0,I=Math.max(I,K.priority))}B.scope=F&&!0===F.scope;B.transcludeOnThisElement=ka;B.elementTranscludeOnThisElement=E;B.templateOnThisElement=x;B.transclude=fb; +m.hasElementTranscludeDirective=E;return B}function y(a){for(var b=0,c=a.length;bq.priority)&&-1!=q.restrict.indexOf(f)&&(k&&(q=Ob(q,{$$start:k,$$end:l})),b.push(q),h=q)}catch(M){c(M)}}return h}function x(b){if(d.hasOwnProperty(b))for(var c=a.get(b+"Directive"),e=0,f=c.length;e"+b+"";return c.childNodes[0].childNodes;default:return b}} +function Q(a,b){if("srcdoc"==b)return Z.HTML;var c=va(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return Z.RESOURCE_URL}function Oa(a,c,d,e,f){var h=Q(a,e);f=g[e]||f;var k=b(d,!0,h,f);if(k){if("multiple"===e&&"select"===va(a))throw la("selmulti",wa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers={});if(l.test(e))throw la("nodomevents");var m=g[e];m!==d&&(k=m&&b(m,!0,h,f),d=m);k&&(g[e]=k(a),(c[e]||(c[e]=[])).$$inter= +!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function T(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=a)return b;for(;a--;)8===b[a].nodeType&&qf.call(b,a,1);return b}function Fe(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,c){La(a, +"controller");J(a)?w(b,a):b[a]=c};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!J(a.$scope))throw R("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h,l,k){var n,p,q;l=!0===l;k&&C(k)&&(q=k);if(C(g)){k=g.match(c);if(!k)throw rf("ctrlfmt",g);p=k[1];q=q||k[3];g=b.hasOwnProperty(p)?b[p]:xc(h.$scope,p,!0)||(a?xc(e,p,!0):t);sb(g,p,!0)}if(l)return l=(H(g)?g[g.length-1]:g).prototype,n=Object.create(l||null),q&&f(h,q,n,p||g.name),w(function(){d.invoke(g, +n,h,p);return n},{instance:n,identifier:q});n=d.instantiate(g,h,p);q&&f(h,q,n,p||g.name);return n}}]}function Ge(){this.$get=["$window",function(b){return A(b.document)}]}function He(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Zb(b,a){if(C(b)){var c=b.replace(sf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(Wc))||(d=(d=c.match(tf))&&uf[d[0]].test(c));d&&(b=qc(c))}}return b}function Xc(b){var a=ia(),c,d,e;if(!b)return a;r(b.split("\n"), +function(b){e=b.indexOf(":");c=z(N(b.substr(0,e)));d=N(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Yc(b){var a=J(b)?b:t;return function(c){a||(a=Xc(b));return c?(c=a[z(c)],void 0===c&&(c=null),c):a}}function Zc(b,a,c,d){if(G(d))return d(b,a,c);r(d,function(d){b=d(b,a,c)});return b}function Ke(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return J(a)&&"[object File]"!==Ca.call(a)&&"[object Blob]"!==Ca.call(a)&&"[object FormData]"!==Ca.call(a)?$a(a): +a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:sa($b),put:sa($b),patch:sa($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},a=!1;this.useApplyAsync=function(b){return y(b)?(a=!!b,this):a};var c=this.interceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(d,e,f,g,h,l){function k(a){function c(a){var b=w({},a);b.data=a.data?Zc(a.data,a.headers,a.status,e.transformResponse):a.data;a=a.status;return 200<=a&&300>a? +b:h.reject(b)}function d(a){var b,c={};r(a,function(a,d){G(a)?(b=a(),null!=b&&(c[d]=b)):c[d]=a});return c}if(!ca.isObject(a))throw R("$http")("badreq",a);var e=w({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse},a);e.headers=function(a){var c=b.headers,e=w({},a.headers),f,g,c=w({},c.common,c[z(a.method)]);a:for(f in c){a=z(f);for(g in e)if(z(g)===a)continue a;e[f]=c[f]}return d(e)}(a);e.method=ub(e.method);var f=[function(a){var d=a.headers,e=Zc(a.data,Yc(d), +t,a.transformRequest);x(e)&&r(d,function(a,b){"content-type"===z(b)&&delete d[b]});x(a.withCredentials)&&!x(b.withCredentials)&&(a.withCredentials=b.withCredentials);return n(a,e).then(c,c)},t],g=h.when(e);for(r(u,function(a){(a.request||a.requestError)&&f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var k=f.shift(),g=g.then(a,k)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,e)});return g};g.error= +function(a){g.then(null,function(b){a(b.data,b.status,b.headers,e)});return g};return g}function n(c,f){function l(b,c,d,e){function f(){m(c,b,d,e)}I&&(200<=b&&300>b?I.put(P,[b,c,Xc(d),e]):I.remove(P));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function m(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?L.resolve:L.reject)({data:a,status:b,headers:Yc(d),config:c,statusText:e})}function n(a){m(a.data,a.status,sa(a.headers()),a.statusText)}function u(){var a=k.pendingRequests.indexOf(c);-1!==a&&k.pendingRequests.splice(a, +1)}var L=h.defer(),B=L.promise,I,D,S=c.headers,P=p(c.url,c.params);k.pendingRequests.push(c);B.then(u,u);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(I=J(c.cache)?c.cache:J(b.cache)?b.cache:q);I&&(D=I.get(P),y(D)?D&&G(D.then)?D.then(n,n):H(D)?m(D[1],D[0],sa(D[2]),D[3]):m(D,200,{},"OK"):I.put(P,B));x(D)&&((D=$c(c.url)?e.cookies()[c.xsrfCookieName||b.xsrfCookieName]:t)&&(S[c.xsrfHeaderName||b.xsrfHeaderName]=D),d(c.method,P,f,l,S,c.timeout,c.withCredentials,c.responseType)); +return B}function p(a,b){if(!b)return a;var c=[];Ed(b,function(a,b){null===a||x(a)||(H(a)||(a=[a]),r(a,function(a){J(a)&&(a=ga(a)?a.toISOString():$a(a));c.push(Ea(b)+"="+Ea(a))}))});0=l&&(s.resolve(q),p(M.$$intervalId),delete f[M.$$intervalId]);u||b.$apply()},h);f[M.$$intervalId]=s;return M}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId), +delete f[b.$$intervalId],!0):!1};return e}]}function Rd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "), +DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a",ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"]},pluralCat:function(b){return 1===b?"one":"other"}}}}function bc(b){b=b.split("/");for(var a=b.length;a--;)b[a]=qb(b[a]); +return b.join("/")}function ad(b,a){var c=Aa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=aa(c.port)||xf[c.protocol]||null}function bd(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Aa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=sc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ya(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ga(b){var a=b.indexOf("#"); +return-1==a?b:b.substr(0,a)}function Fb(b){return b.replace(/(#.+)|#$/,"$1")}function cc(b){return b.substr(0,Ga(b).lastIndexOf("/")+1)}function dc(b,a){this.$$html5=!0;a=a||"";var c=cc(b);ad(b,this);this.$$parse=function(a){var b=ya(c,a);if(!C(b))throw Gb("ipthprfx",a,c);bd(b,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Pb(this.$$search),b=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl= +function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=ya(b,d))!==t?(g=f,g=(f=ya(a,f))!==t?c+(ya("/",f)||f):b+g):(f=ya(c,d))!==t?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function ec(b,a){var c=cc(b);ad(b,this);this.$$parse=function(d){d=ya(b,d)||ya(c,d);var e;"#"===d.charAt(0)?(e=ya(a,d),x(e)&&(e=d)):e=this.$$html5?d:"";bd(e,this);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()}; +this.$$compose=function(){var c=Pb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ga(b)==Ga(a)?(this.$$parse(a),!0):!1}}function cd(b,a){this.$$html5=!0;ec.apply(this,arguments);var c=cc(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ga(d)?f=d:(g=ya(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose= +function(){var c=Pb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function Hb(b){return function(){return this[b]}}function dd(b,a){return function(c){if(x(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Me(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return y(a)?(b=a,this):b};this.html5Mode=function(b){return Wa(b)?(a.enabled=b,this):J(b)?(Wa(b.enabled)&&(a.enabled= +b.enabled),Wa(b.requireBase)&&(a.requireBase=b.requireBase),Wa(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,n;n=d.baseHref();var p=d.url(),q;if(a.enabled){if(!n&&a.requireBase)throw Gb("nobase"); +q=p.substring(0,p.indexOf("/",p.indexOf("//")+2))+(n||"/");n=e.history?dc:cd}else q=Ga(p),n=ec;k=new n(q,"#"+b);k.$$parseLinkUrl(p,p);k.$$state=d.state();var u=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=A(b.target);"a"!==va(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");J(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Aa(h.animVal).href); +u.test(h)||!h||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(h,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});Fb(k.absUrl())!=Fb(p)&&d.url(k.absUrl(),!0);var s=!0;d.onUrlChange(function(a,b){c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d,!1,e)):(s=!1,l(d,e)))});c.$$phase||c.$digest()}); +c.$watch(function(){var a=Fb(d.url()),b=Fb(k.absUrl()),f=d.state(),g=k.$$replace,q=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(s||q)s=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(q&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function Ne(){var b=!0,a=this;this.debugEnabled=function(a){return y(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof +Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||E;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];r(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a, +arguments)}}()}}]}function ua(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw na("isecfld",a);return b}function oa(b,a){if(b){if(b.constructor===b)throw na("isecfn",a);if(b.window===b)throw na("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw na("isecdom",a);if(b===Object)throw na("isecobj",a);}return b}function fc(b){return b.constant}function hb(b,a,c,d,e){oa(b,e);oa(a,e);c=c.split(".");for(var f, +g=0;1h?ed(g[0],g[1],g[2],g[3],g[4],c,d):function(a,b){var e=0,f;do f=ed(g[e++],g[e++],g[e++],g[e++],g[e++],c,d)(a,b),b=t,a=f;while(e=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;fa)for(b in l++,f)e.hasOwnProperty(b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,g,k=1r&&(M=4-r,O[M]||(O[M]=[]),O[M].push({msg:G(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:h}));else if(b=== +d){n=!1;break a}}catch(A){g(A)}if(!(k=t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(k=t.$$nextSibling);)t=t.$parent}while(t=k);if((n||m.length)&&!r--)throw v.$$phase=null,c("infdig",a,O);}while(n||m.length);for(v.$$phase=null;F.length;)try{F.shift()()}catch(x){g(x)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==v){for(var b in this.$$listenerCount)q(this,this.$$listenerCount[b],b);a.$$childHead==this&&(a.$$childHead= +this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}}},$eval:function(a, +b){return h(a)(this,b)},$evalAsync:function(a,b){v.$$phase||m.length||l.defer(function(){m.length&&v.$digest()});m.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){F.push(a)},$apply:function(a){try{return p("$apply"),this.$eval(a)}catch(b){g(b)}finally{v.$$phase=null;try{v.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);M()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]|| +(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,q(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,h={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=Ya([h],arguments,1),l,q;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(q=d.length;lQa)throw Ba("iequirks");var d=sa(pa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs= +d.getTrusted=function(a,b){return b},d.valueOf=ra);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,g=d.trustAs;r(pa,function(a,b){var c=z(b);d[db("parse_as_"+c)]=function(b){return e(a,b)};d[db("get_trusted_"+c)]=function(b){return f(a,b)};d[db("trust_as_"+c)]=function(b){return g(a,b)}});return d}]}function Ue(){this.$get=["$window","$document",function(b,a){var c={},d=aa((/android (\d+)/.exec(z((b.navigator|| +{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,n=!1;if(l){for(var p in l)if(k=h.exec(p)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||g+"Transition"in l);n=!!("animation"in l||g+"Animation"in l);!d||k&&n||(k=C(f.body.style.webkitTransition),n=C(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"=== +a&&11>=Qa)return!1;if(x(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:bb(),vendorPrefix:g,transitions:k,animations:n,android:d}}]}function We(){this.$get=["$templateCache","$http","$q",function(b,a,c){function d(e,f){d.totalPendingRequests++;var g=a.defaults&&a.defaults.transformResponse;H(g)?g=g.filter(function(a){return a!==Zb}):g===Zb&&(g=null);return a.get(e,{cache:b,transformResponse:g})["finally"](function(){d.totalPendingRequests--}).then(function(a){return a.data}, +function(a){if(!f)throw la("tpload",e);return c.reject(a)})}d.totalPendingRequests=0;return d}]}function Xe(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var d=ca.element(a).data("$binding");d&&r(d,function(d){c?(new RegExp("(^|\\s)"+gd(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"], +h=0;hb;b=Math.abs(b);var g=b+"",h="",l=[],k=!1;if(-1!==g.indexOf("e")){var n=g.match(/([\d\.]+)e(-?)(\d+)/);n&&"-"==n[2]&&n[3]>e+1?b=0:(h=g,k=!0)}if(k)0b&&(h=b.toFixed(e),b=parseFloat(h));else{g=(g.split(od)[1]|| +"").length;x(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(od),k=g[0],g=g[1]||"",p=0,q=a.lgSize,u=a.gSize;if(k.length>=q+u)for(p=k.length-q,n=0;nb&&(d="-",b=-b);for(b=""+b;b.length-c)e+=c;0===e&&-12==c&&(e=12);return Ib(e,a,d)}}function Jb(b,a){return function(c,d){var e=c["get"+b](),f=ub(a?"SHORT"+b:b);return d[f][e]}}function pd(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function qd(b){return function(a){var c=pd(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+ +(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Ib(a,b)}}function ic(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function kd(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=aa(b[9]+b[10]),g=aa(b[9]+b[11]));h.call(a,aa(b[1]),aa(b[2])-1,aa(b[3]));f=aa(b[4]||0)-f;g=aa(b[5]||0)-g;h=aa(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; +return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;C(c)&&(c=Kf.test(c)?aa(c):a(c));Y(c)&&(c=new Date(c));if(!ga(c))return c;for(;e;)(k=Lf.exec(e))?(h=Ya(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset()));r(h,function(a){l=Mf[a];g+=l?l(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ff(){return function(b,a){x(a)&&(a=2);return $a(b,a)}}function Gf(){return function(b, +a){Y(b)&&(b=b.toString());return H(b)||C(b)?(a=Infinity===Math.abs(Number(a))?Number(a):aa(a))?0b||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",n)}a.on("change",l);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function Mb(b,a){return function(c,d){var e,f;if(ga(c))return c;if(C(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(Nf.test(c))return new Date(c);b.lastIndex= +0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},r(e,function(b,c){c=r}; +g.$observe("min",function(a){r=q(a);h.$validate()})}if(y(g.max)||g.ngMax){var v;h.$validators.max=function(a){return!p(a)||x(v)||c(a)<=v};g.$observe("max",function(a){v=q(a);h.$validate()})}}}function td(b,a,c,d){(d.$$hasNativeValidators=J(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?t:b})}function ud(b,a,c,d,e){if(y(d)){b=b(d);if(!b.constant)throw R("ngModel")("constexpr",c,d);return b(a)}return e}function kc(b,a){b="ngClass"+b;return["$animate", +function(c){function d(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Sb=/<|&#?\w+;/,ef=/<([\w:]+)/,ff=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja={option:[1,'"],thead:[1,"","
"],col:[2,"", +"
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead;ja.th=ja.td;var Ka=T.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===W.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),T(Q).on("load",a))},toString:function(){var b=[];r(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<= +b?A(this[b]):A(this[this.length+b])},length:0,push:Pf,sort:[].sort,splice:[].splice},Eb={};r("multiple selected checked disabled readOnly required open".split(" "),function(b){Eb[z(b)]=b});var Oc={};r("input select option textarea button form details".split(" "),function(b){Oc[b]=!0});var Pc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};r({data:Vb,removeData:xb},function(b,a){T[a]=b});r({data:Vb,inheritedData:Db,scope:function(b){return A.data(b,"$scope")|| +Db(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return A.data(b,"$isolateScope")||A.data(b,"$isolateScopeNoTemplate")},controller:Kc,injector:function(b){return Db(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ab,css:function(b,a,c){a=db(a);if(y(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=z(a);if(Eb[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||E).specified? +d:t;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?t:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(x(b)){var d=a.nodeType;return d===qa||d===pb?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(x(a)){if(b.multiple&&"select"===va(b)){var c=[];r(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(x(a))return b.innerHTML; +wb(b,!0);b.innerHTML=a},empty:Lc},function(b,a){T.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==Lc&&(2==b.length&&b!==Ab&&b!==Kc?a:d)===t){if(J(a)){for(e=0;e":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)}, +"||":function(a,c,d,e){return d(a,c)||e(a,c)},"!":function(a,c,d){return!d(a,c)},"=":!0,"|":!0}),Zf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},hc=function(a){this.options=a};hc.prototype={constructor:hc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=y(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw na("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index< +this.text.length;){var d=z(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:c,text:a,constant:!0,value:Number(a)})},readIdent:function(){for(var a=this.index;this.indexa){a=this.tokens[a];var g=a.text;if(g===c||g===d||g===e||g=== +f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){if(0===this.tokens.length)throw na("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},unaryFn:function(a,c){var d=nb[a];return w(function(a,f){return d(a,f,c)},{constant:c.constant,inputs:[c]})},binaryFn:function(a,c,d,e){var f=nb[c];return w(function(c,e){return f(c,e,a,d)},{constant:a.constant&& +d.constant,inputs:!e&&[a,d]})},identifier:function(){for(var a=this.consume().text;this.peek(".")&&this.peekAhead(1).identifier&&!this.peekAhead(2,"(");)a+=this.consume().text+this.consume().text;return zf(a,this.options,this.text)},constant:function(){var a=this.consume().value;return w(function(){return a},{constant:!0,literal:!0})},statements:function(){for(var a=[];;)if(0","<=",">=");)a=this.binaryFn(a,c.text, +this.additive());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.text,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.text,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(ib.ZERO,a.text,this.unary()):(a=this.expect("!"))?this.unaryFn(a.text,this.unary()):this.primary()},fieldAccess:function(a){var c= +this.identifier();return w(function(d,e,f){d=f||a(d,e);return null==d?t:c(d)},{assign:function(d,e,f){var g=a(d,f);g||a.assign(d,g={},f);return c.assign(g,e)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return w(function(e,f){var g=a(e,f),h=d(e,f);ua(h,c);return g?oa(g[h],c):t},{assign:function(e,f,g){var h=ua(d(e,g),c),l=oa(a(e,g),c);l||a.assign(e,l={},g);return l[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression()); +while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var l=c?c(g,h):y(c)?t:g,k=a(g,h,l)||E;if(f)for(var n=d.length;n--;)f[n]=oa(d[n](g,h),e);oa(l,e);if(k){if(k.constructor===k)throw na("isecfn",e);if(k===Wf||k===Xf||k===Yf)throw na("isecff",e);}l=k.apply?k.apply(l,f):k(f[0],f[1],f[2],f[3],f[4]);f&&(f.length=0);return oa(l,e)}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(",")) +}this.consume("]");return w(function(c,d){for(var e=[],f=0,g=a.length;fa.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Ib(Math[0=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},Lf=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, +Kf=/^\-?\d+$/;kd.$inject=["$locale"];var Hf=ea(z),If=ea(ub);md.$inject=["$parse"];var Td=ea({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref&&!c.name)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===Ca.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),vb={};r(Eb,function(a,c){if("multiple"!=a){var d=xa("ng-"+c);vb[d]=function(){return{restrict:"A",priority:100,link:function(a,f,g){a.$watch(g[d], +function(a){g.$set(c,!!a)})}}}}});r(Pc,function(a,c){vb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(Of))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});r(["src","srcset","href"],function(a){var c=xa("ng-"+a);vb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===Ca.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href", +g=null);f.$observe(c,function(c){c?(f.$set(h,c),Qa&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var Kb={$addControl:E,$$renameControl:function(a,c){a.$name=c},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E};rd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var yd=function(a){return["$timeout",function(c){return{name:"form",restrict:a?"EAC":"E",controller:rd,compile:function(d,e){d.addClass(Ra).addClass(lb);var f=e.name?"name":a&&e.ngForm?"ngForm": +!1;return{pre:function(a,d,e,k){if(!("action"in e)){var n=function(c){a.$apply(function(){k.$commitViewValue();k.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",n,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",n,!1)},0,!1)})}var p=k.$$parentForm;f&&(hb(a,null,k.$name,k,k.$name),e.$observe(f,function(c){k.$name!==c&&(hb(a,null,k.$name,t,k.$name),p.$$renameControl(k,c),hb(a,null,k.$name,k,k.$name))}));d.on("$destroy",function(){p.$removeControl(k); +f&&hb(a,null,e[f],t,k.$name);w(k,Kb)})}}}}}]},Ud=yd(),ge=yd(!0),Nf=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,$f=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,ag=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,bg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,zd=/^(\d{4})-(\d{2})-(\d{2})$/,Ad=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,lc=/^(\d{4})-W(\d\d)$/,Bd=/^(\d{4})-(\d\d)$/, +Cd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Dd={text:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);jc(e)},date:kb("date",zd,Mb(zd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":kb("datetimelocal",Ad,Mb(Ad,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:kb("time",Cd,Mb(Cd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:kb("week",lc,function(a,c){if(ga(a))return a;if(C(a)){lc.lastIndex=0;var d=lc.exec(a);if(d){var e=+d[1],f=+d[2],g=d=0,h=0,l=0,k=pd(e),f=7*(f-1);c&&(d=c.getHours(),g= +c.getMinutes(),h=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,g,h,l)}}return NaN},"yyyy-Www"),month:kb("month",Bd,Mb(Bd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){td(a,c,d,e);jb(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:bg.test(a)?parseFloat(a):t});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!Y(a))throw Nb("numfmt",a);a=a.toString()}return a});if(y(d.min)||d.ngMin){var h;e.$validators.min=function(a){return e.$isEmpty(a)|| +x(h)||a>=h};d.$observe("min",function(a){y(a)&&!Y(a)&&(a=parseFloat(a,10));h=Y(a)&&!isNaN(a)?a:t;e.$validate()})}if(y(d.max)||d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||x(l)||a<=l};d.$observe("max",function(a){y(a)&&!Y(a)&&(a=parseFloat(a,10));l=Y(a)&&!isNaN(a)?a:t;e.$validate()})}},url:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);jc(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||$f.test(d)}},email:function(a,c,d,e,f,g){jb(a,c,d,e,f,g);jc(e); +e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||ag.test(d)}},radio:function(a,c,d,e){x(d.name)&&c.attr("name",++ob);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=ud(l,a,"ngTrueValue",d.ngTrueValue,!0),n=ud(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&& +a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ha(a,k)});e.$parsers.push(function(a){return a?k:n})},hidden:E,button:E,submit:E,reset:E,file:E},zc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Dd[z(h.type)]||Dd.text)(f,g,h,l[0],c,a,d,e)}}}}],cg=/^(true|false|\d+)$/,ye=function(){return{restrict:"A",priority:100,compile:function(a, +c){return cg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},Zd=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===t?"":a})}}}}],ae=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate)); +c.$$addBindingInfo(f,d.expressions);f=f[0];g.$observe("ngBindTemplate",function(a){f.textContent=a===t?"":a})}}}}],$d=["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],xe=ea({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), +be=kc("",!0),de=kc("Odd",0),ce=kc("Even",1),ee=Ia({compile:function(a,c){c.$set("ngCloak",t);a.removeClass("ng-cloak")}}),fe=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Ec={},dg={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=xa("ng-"+a);Ec[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h= +d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};dg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var ie=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=W.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k= +tb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],je=["$templateRequest","$anchorScroll","$animate","$sce",function(a,c,d,e){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(f,g){var h=g.ngInclude||g.src,l=g.onload||"",k=g.autoscroll;return function(f,g,q,r,s){var t=0,v,m,F,w=function(){m&&(m.remove(),m=null);v&&(v.$destroy(),v=null);F&&(d.leave(F).then(function(){m=null}),m=F,F=null)};f.$watch(e.parseAsResourceUrl(h),function(e){var h= +function(){!y(k)||k&&!f.$eval(k)||c()},m=++t;e?(a(e,!0).then(function(a){if(m===t){var c=f.$new();r.template=a;a=s(c,function(a){w();d.enter(a,null,g).then(h)});v=c;F=a;v.$emit("$includeContentLoaded",e);f.$eval(l)}},function(){m===t&&(w(),f.$emit("$includeContentError",e))}),f.$emit("$includeContentRequested",e)):(w(),r.template=null)})}}}}],Ae=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Hc(f.template, +W).childNodes)(c,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ke=Ia({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),we=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?N(f):f;e.$parsers.push(function(a){if(!x(a)){var c=[];a&&r(a.split(h),function(a){a&&c.push(g?N(a):a)});return c}});e.$formatters.push(function(a){return H(a)? +a.join(f):t});e.$isEmpty=function(a){return!a||!a.length}}}},lb="ng-valid",vd="ng-invalid",Ra="ng-pristine",Lb="ng-dirty",xd="ng-pending",Nb=new R("ngModel"),eg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,n){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=t;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0; +this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=t;this.$name=n(d.name||"",!1)(a);var p=f(d.ngModel),q=p.assign,u=p,s=q,M=null,v,m=this;this.$$setOptions=function(a){if((m.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");u=function(a){var d=p(a);G(d)&&(d=c(a));return d};s=function(a,c){G(p(a))?g(a,{$$$p:m.$modelValue}):q(a,m.$modelValue)}}else if(!p.assign)throw Nb("nonassign",d.ngModel,wa(e)); +};this.$render=E;this.$isEmpty=function(a){return x(a)||""===a||null===a||a!==a};var F=e.inheritedData("$formController")||Kb,w=0;sd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:F,$animate:g});this.$setPristine=function(){m.$dirty=!1;m.$pristine=!0;g.removeClass(e,Lb);g.addClass(e,Ra)};this.$setDirty=function(){m.$dirty=!0;m.$pristine=!1;g.removeClass(e,Ra);g.addClass(e,Lb);F.$setDirty()};this.$setUntouched=function(){m.$touched=!1;m.$untouched=!0;g.setClass(e, +"ng-untouched","ng-touched")};this.$setTouched=function(){m.$touched=!0;m.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(M);m.$viewValue=m.$$lastCommittedViewValue;m.$render()};this.$validate=function(){if(!Y(m.$modelValue)||!isNaN(m.$modelValue)){var a=m.$$rawModelValue,c=m.$valid,d=m.$modelValue,e=m.$options&&m.$options.allowInvalid;m.$$runValidators(a,m.$$lastCommittedViewValue,function(f){e||c===f||(m.$modelValue=f?a:t,m.$modelValue!==d&&m.$$writeModelToScope())})}}; +this.$$runValidators=function(a,c,d){function e(){var d=!0;r(m.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d?!0:(r(m.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;r(m.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!G(k.then))throw Nb("$asyncValidators",k);g(h,t);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},E):h(!0)}function g(a,c){l===w&&m.$setValidity(a,c)}function h(a){l===w&&d(a)}w++;var l= +w;(function(){var a=m.$$parserName||"parse";if(v===t)g(a,null);else return v||(r(m.$validators,function(a,c){g(c,null)}),r(m.$asyncValidators,function(a,c){g(c,null)})),g(a,v),v;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=m.$viewValue;h.cancel(M);if(m.$$lastCommittedViewValue!==a||""===a&&m.$$hasNativeValidators)m.$$lastCommittedViewValue=a,m.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=m.$$lastCommittedViewValue;if(v= +x(c)?t:!0)for(var d=0;dz;)d=t.pop(),n(O,d.label,!1),d.element.remove()}for(;R.length> +x;){l=R.pop();for(z=1;za&&q.removeOption(c)})}var v;if(!(v=s.match(d)))throw gg("iexp",s,wa(f));var C=c(v[2]||v[1]),x=v[4]||v[6],A=/ as /.test(v[0])&&v[1],B=A?c(A):null,G=v[5],J=c(v[3]||""),z=c(v[2]?v[1]:x),L=c(v[7]),I=v[8]?c(v[8]):null,Q={},R=[[{element:f,label:""}]],T={};w&&(a(w)(e),w.removeClass("ng-scope"),w.remove());f.empty();f.on("change",function(){e.$apply(function(){var a=L(e)||[],c;if(u)c=[],r(f.val(), +function(d){d=I?Q[d]:d;c.push("?"===d?t:""===d?null:h(B?B:z,d,a[d]))});else{var d=I?Q[f.val()]:f.val();c="?"===d?t:""===d?null:h(B?B:z,d,a[d])}g.$setViewValue(c);p()})});g.$render=p;e.$watchCollection(L,l);e.$watchCollection(function(){var a=L(e),c;if(a&&H(a)){c=Array(a.length);for(var d=0,f=a.length;df||e.$isEmpty(c)||c.length<=f}}}}},Cc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=aa(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};Q.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(Nd(),Pd(ca),A(W).ready(function(){Jd(W,uc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend(''); diff --git a/static/js/record.js b/static/js/record.js new file mode 100644 index 000000000..2df8fe352 --- /dev/null +++ b/static/js/record.js @@ -0,0 +1,157 @@ +/** + * Created by liuzheng on 3/25/16. + */ +'use strict'; + +var NgApp = angular.module('NgApp', ['ngRoute']); +NgApp.config(['$httpProvider', function ($httpProvider) { + $httpProvider.defaults.transformRequest = function (obj) { + var str = []; + for (var p in obj) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + } + return str.join("&"); + }; + $httpProvider.defaults.xsrfCookieName = 'csrftoken'; + $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; + $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; + $httpProvider.defaults.headers.post = { + 'Content-Type': 'application/x-www-form-urlencoded' + } +}]); +NgApp.controller('TerminalRecordCtrl', function ($scope, $http) { + $http.post(window.location.href).success(function (data) { + var toggle = true; + var totalTime = 0; + var TICK = 33; + var TIMESTEP = 33; + var time = 33; + var timer; + var pos = 0; + + // Thanks http://stackoverflow.com/a/2998822 + function zeroPad(num, size) { + var s = "0" + num; + return s.substr(s.length - size); + } + + $scope.scrub = function () { + var setPercent = document.getElementById('scrubber').value; + time = (setPercent / 100) * totalTime; + $scope.restart(time); + }; + + function buildTimeString(millis) { + var hours = zeroPad(Math.floor(millis / (1000 * 60 * 60)), 2); + millis -= hours * (1000 * 60 * 60); + var minutes = zeroPad(Math.floor(millis / (1000 * 60)), 2); + millis -= minutes * (1000 * 60); + var seconds = zeroPad(Math.floor(millis / 1000), 2); + return hours + ':' + minutes + ':' + seconds; + } + + function advance() { + document.getElementById('scrubber').value = + Math.ceil((time / totalTime) * 100); + document.getElementById("beforeScrubberText").innerHTML = buildTimeString(time); + for (; pos < timelist.length; pos++) { + if (timelist[pos] * 1000 <= time) { + term.write(data[timelist[pos]]); + } else { + break; + } + } + + if (pos >= timelist.length) { + clearInterval(timer); + } + + time += TIMESTEP; + } + + $scope.pause = function (test) { + if (!toggle && test) { + return; + } + if (toggle) { + clearInterval(timer); + toggle = !toggle; + } else { + timer = setInterval(advance, TICK); + toggle = !toggle; + } + }; + + $scope.setSpeed = function () { + var speed = document.getElementById('speed').value; + if (speed == 0) { + TIMESTEP = TICK; + } else if (speed < 0) { + TIMESTEP = TICK / -speed; + } else { + TIMESTEP = TICK * speed; + } + }; + + $scope.restart = function (millis) { + clearInterval(timer); + term.reset(); + time = millis; + pos = 0; + toggle = true; + timer = setInterval(advance, TICK); + }; + + var rc = textSize(); + var term = new Terminal({ + rows: rc.y, + cols: rc.x, + useStyle: true, + screenKeys: true + }); + var timelist = []; + for (var i in data) { + totalTime = totalTime > i ? totalTime : i; + timelist.push(i); + } + timelist = timelist.sort(); + totalTime = totalTime * 1000; + document.getElementById("afterScrubberText").innerHTML = buildTimeString(totalTime); + term.open(document.getElementById('terminal')); + timer = setInterval(advance, TICK); + }) + +}) + +function textSize() { + var charSize = getCharSize(); + var windowSize = getwindowSize(); + var size = { + x: Math.floor(windowSize.width / charSize.width) + , y: Math.floor(windowSize.height / charSize.height) + }; + if (size.x > 150)size.x = 150; + if (size.y > 35)size.y = 35; + size.x = 140; + size.y = 30; + return size; +} +function getCharSize() { + var $span = $("", {text: "qwertyuiopasdfghjklzxcvbnm"}); + $('body').append($span); + var size = { + width: $span.outerWidth() / 30 + , height: $span.outerHeight() * 1.1 + }; + $span.remove(); + return size; +} +function getwindowSize() { + var e = window, + a = 'inner'; + if (!('innerWidth' in window )) { + a = 'client'; + e = document.documentElement || document.body; + } + return {width: e[a + 'Width'] - 300, height: e[a + 'Height'] - 120}; +} diff --git a/templates/jlog/record.html b/templates/jlog/record.html new file mode 100644 index 000000000..c23dc8f5e --- /dev/null +++ b/templates/jlog/record.html @@ -0,0 +1,26 @@ + + + + Jumpserver 录像回放 + + + + + + +{% csrf_token %} +
+ + + + + + -5x +5x +
+
+ + + + \ No newline at end of file From 44b2bcb7594cab588d96a183aa1b740bbc998bdd Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Fri, 25 Mar 2016 22:24:56 +0800 Subject: [PATCH 14/27] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jlog/models.py | 13 +++++++++++++ jlog/views.py | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/jlog/models.py b/jlog/models.py index dfb418651..3a50259e5 100644 --- a/jlog/models.py +++ b/jlog/models.py @@ -13,6 +13,19 @@ class Log(models.Model): pid = models.IntegerField() is_finished = models.BooleanField(default=False) end_time = models.DateTimeField(null=True) + ''' + add by liuzheng + ''' + userMM = models.ManyToManyField(User) + logPath = models.TextField() + filename = models.CharField(max_length=40) + logPWD = models.TextField() # log zip file's + nick = models.TextField(null=True) # log's nick name + log = models.TextField(null=True) + history = models.TextField(null=True) + timestamp = models.IntegerField(default=int(time.time())) + datetimestamp = models.DateTimeField(auto_now_add=True) + def __unicode__(self): return self.log_path diff --git a/jlog/views.py b/jlog/views.py index 085a9307e..d917f0748 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -7,7 +7,7 @@ from jperm.perm_api import user_have_perm from django.http import HttpResponseNotFound from jlog.log_api import renderTemplate -from jlog.models import Log, ExecLog, FileLog, TermLog +from jlog.models import Log, ExecLog, FileLog from jumpserver.settings import LOG_DIR import zipfile import json @@ -137,7 +137,7 @@ def log_record(request): log_id = request.REQUEST.get('id', None) if log_id: logs = TermLogRecorder(request.user) - log = TermLog.objects.get(id=int(log_id)) + log = Log.objects.get(id=int(log_id)) return HttpResponse(logs.load_full_log(log.filename)) else: return HttpResponse("ERROR") @@ -261,16 +261,16 @@ class TermLogRecorder(object): zf.setpassword(password) zf.writestr(filename, json.dumps(self.log)) zf.close() - record = TermLog.objects.create(logPath=filepath, logPWD=password, filename=filename, + record = Log.objects.create(logPath=filepath, logPWD=password, filename=filename, history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) if self.user: - record.user.add(self.user) + record.userMM.add(self.user) except: - record = TermLog.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log), + record = Log.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log), filename=filename, history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) if self.user: - record.user.add(self.user) + record.userMM.add(self.user) def list(self, user=None, uid=None): tmp = [] @@ -281,7 +281,7 @@ class TermLogRecorder(object): else: user = self.user if user: - self._lists = TermLog.objects.filter(user=user.id) + self._lists = Log.objects.filter(user=user.id) for i in self._lists.all(): tmp.append( {'filename': i.filename, 'locale': i.logPath == 'locale', 'nick': i.nick, 'timestamp': i.timestamp, @@ -299,7 +299,7 @@ class TermLogRecorder(object): if self._lists: self.file = self._lists.get(filename=filename) else: - self.file = TermLog.objects.get(user=user.id, filename=filename) + self.file = Log.objects.get(user=user.id, filename=filename) if self.file.logPath == 'locale': return self.file.log else: @@ -323,7 +323,7 @@ class TermLogRecorder(object): if self._lists: self.file = self._lists.get(filename=filename) else: - self.file = TermLog.objects.get(user=user.id, filename=filename) + self.file = Log.objects.get(user=user.id, filename=filename) return self.file.history return 'ERROR User(None)' @@ -335,7 +335,7 @@ class TermLogRecorder(object): else: pass if user: - TermLog.objects.get(filename=filename).user.add(user) + Log.objects.get(filename=filename).userMM.add(user) return True return False @@ -347,6 +347,6 @@ class TermLogRecorder(object): else: pass if user: - TermLog.objects.get(filename=filename).user.remove(user) + Log.objects.get(filename=filename).userMM.remove(user) return True return False From 1084be4712c23d59f49fe9f147048ea826e1b3bb Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Fri, 25 Mar 2016 22:28:49 +0800 Subject: [PATCH 15/27] update --- static/js/record.js | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/static/js/record.js b/static/js/record.js index 2df8fe352..423211edc 100644 --- a/static/js/record.js +++ b/static/js/record.js @@ -102,10 +102,9 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) { timer = setInterval(advance, TICK); }; - var rc = textSize(); var term = new Terminal({ - rows: rc.y, - cols: rc.x, + rows: 80, + cols: 24, useStyle: true, screenKeys: true }); @@ -121,37 +120,4 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) { timer = setInterval(advance, TICK); }) -}) - -function textSize() { - var charSize = getCharSize(); - var windowSize = getwindowSize(); - var size = { - x: Math.floor(windowSize.width / charSize.width) - , y: Math.floor(windowSize.height / charSize.height) - }; - if (size.x > 150)size.x = 150; - if (size.y > 35)size.y = 35; - size.x = 140; - size.y = 30; - return size; -} -function getCharSize() { - var $span = $("", {text: "qwertyuiopasdfghjklzxcvbnm"}); - $('body').append($span); - var size = { - width: $span.outerWidth() / 30 - , height: $span.outerHeight() * 1.1 - }; - $span.remove(); - return size; -} -function getwindowSize() { - var e = window, - a = 'inner'; - if (!('innerWidth' in window )) { - a = 'client'; - e = document.documentElement || document.body; - } - return {width: e[a + 'Width'] - 300, height: e[a + 'Height'] - 120}; -} +}) \ No newline at end of file From 9b43c6c238a27f92805d29693ad873cea4441eb9 Mon Sep 17 00:00:00 2001 From: liuzheng712 Date: Fri, 25 Mar 2016 23:52:48 +0800 Subject: [PATCH 16/27] update --- jlog/models.py | 18 +++++++++--------- jlog/views.py | 26 +++++++++++++------------- static/js/record.js | 8 ++++---- templates/jlog/log_offline.html | 9 +++++++-- templates/jlog/record.html | 2 +- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/jlog/models.py b/jlog/models.py index 3a50259e5..7902ec6ca 100644 --- a/jlog/models.py +++ b/jlog/models.py @@ -16,15 +16,15 @@ class Log(models.Model): ''' add by liuzheng ''' - userMM = models.ManyToManyField(User) - logPath = models.TextField() - filename = models.CharField(max_length=40) - logPWD = models.TextField() # log zip file's - nick = models.TextField(null=True) # log's nick name - log = models.TextField(null=True) - history = models.TextField(null=True) - timestamp = models.IntegerField(default=int(time.time())) - datetimestamp = models.DateTimeField(auto_now_add=True) + # userMM = models.ManyToManyField(User) + # logPath = models.TextField() + # filename = models.CharField(max_length=40) + # logPWD = models.TextField() # log zip file's + # nick = models.TextField(null=True) # log's nick name + # log = models.TextField(null=True) + # history = models.TextField(null=True) + # timestamp = models.IntegerField(default=int(time.time())) + # datetimestamp = models.DateTimeField(auto_now_add=True) def __unicode__(self): diff --git a/jlog/views.py b/jlog/views.py index d917f0748..1386fd21f 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -7,7 +7,7 @@ from jperm.perm_api import user_have_perm from django.http import HttpResponseNotFound from jlog.log_api import renderTemplate -from jlog.models import Log, ExecLog, FileLog +from jlog.models import Log, ExecLog, FileLog,TermLog from jumpserver.settings import LOG_DIR import zipfile import json @@ -136,9 +136,9 @@ def log_record(request): elif request.method == "POST": log_id = request.REQUEST.get('id', None) if log_id: - logs = TermLogRecorder(request.user) - log = Log.objects.get(id=int(log_id)) - return HttpResponse(logs.load_full_log(log.filename)) + TermL = TermLogRecorder(request.user) + log = TermLog.objects.get(id=int(log_id)) + return HttpResponse(TermL.load_full_log(log.filename)) else: return HttpResponse("ERROR") else: @@ -261,16 +261,16 @@ class TermLogRecorder(object): zf.setpassword(password) zf.writestr(filename, json.dumps(self.log)) zf.close() - record = Log.objects.create(logPath=filepath, logPWD=password, filename=filename, + record = TermLog.objects.create(logPath=filepath, logPWD=password, filename=filename, history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) if self.user: - record.userMM.add(self.user) + record.user.add(self.user) except: - record = Log.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log), + record = TermLog.objects.create(logPath='locale', logPWD=password, log=json.dumps(self.log), filename=filename, history=json.dumps(self.CMD), timestamp=int(self.recoderStartTime)) if self.user: - record.userMM.add(self.user) + record.user.add(self.user) def list(self, user=None, uid=None): tmp = [] @@ -281,7 +281,7 @@ class TermLogRecorder(object): else: user = self.user if user: - self._lists = Log.objects.filter(user=user.id) + self._lists = TermLog.objects.filter(user=user.id) for i in self._lists.all(): tmp.append( {'filename': i.filename, 'locale': i.logPath == 'locale', 'nick': i.nick, 'timestamp': i.timestamp, @@ -299,7 +299,7 @@ class TermLogRecorder(object): if self._lists: self.file = self._lists.get(filename=filename) else: - self.file = Log.objects.get(user=user.id, filename=filename) + self.file = TermLog.objects.get(user=user.id, filename=filename) if self.file.logPath == 'locale': return self.file.log else: @@ -323,7 +323,7 @@ class TermLogRecorder(object): if self._lists: self.file = self._lists.get(filename=filename) else: - self.file = Log.objects.get(user=user.id, filename=filename) + self.file = TermLog.objects.get(user=user.id, filename=filename) return self.file.history return 'ERROR User(None)' @@ -335,7 +335,7 @@ class TermLogRecorder(object): else: pass if user: - Log.objects.get(filename=filename).userMM.add(user) + TermLog.objects.get(filename=filename).user.add(user) return True return False @@ -347,6 +347,6 @@ class TermLogRecorder(object): else: pass if user: - Log.objects.get(filename=filename).userMM.remove(user) + TermLog.objects.get(filename=filename).user.remove(user) return True return False diff --git a/static/js/record.js b/static/js/record.js index 423211edc..910ebbc22 100644 --- a/static/js/record.js +++ b/static/js/record.js @@ -103,17 +103,17 @@ NgApp.controller('TerminalRecordCtrl', function ($scope, $http) { }; var term = new Terminal({ - rows: 80, - cols: 24, + rows: 24, + cols: 80, useStyle: true, screenKeys: true }); var timelist = []; for (var i in data) { - totalTime = totalTime > i ? totalTime : i; + totalTime = Math.max(totalTime, i); timelist.push(i); } - timelist = timelist.sort(); + timelist = timelist.sort(function(a, b){return a-b}); totalTime = totalTime * 1000; document.getElementById("afterScrubberText").innerHTML = buildTimeString(totalTime); term.open(document.getElementById('terminal')); diff --git a/templates/jlog/log_offline.html b/templates/jlog/log_offline.html index 62bda764f..4d0fd6367 100644 --- a/templates/jlog/log_offline.html +++ b/templates/jlog/log_offline.html @@ -8,6 +8,11 @@ {% include 'nav_cat_bar.html' %}