diff --git a/.idea/FristProject.iml b/.idea/FristProject.iml index 719bacc..a35650a 100644 --- a/.idea/FristProject.iml +++ b/.idea/FristProject.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 55756c3..7269ac0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/DB_table.xlsx b/DB_table.xlsx index b7086fd..b05ea9f 100644 Binary files a/DB_table.xlsx and b/DB_table.xlsx differ diff --git a/config.yaml b/config.yaml index 219e12b..959b2ce 100644 --- a/config.yaml +++ b/config.yaml @@ -27,15 +27,19 @@ ALLOWED_EXTENSIONS : {'zip'} #RTSP RTSP_Check_Time : 600 #10分钟 -- 2024-7-8 取消使用 +#max_channel_num +max_channel_num : 4 #最大视频通道数量 + #model -model_platform : acl #acl gpu cpu +model_platform : cpu #acl gpu cpu device_id : 0 #单设备配置 weight_path: /model/weights yolov5_path: D:/Project/FristProject/model/base_model/yolov5 #使用绝对路径,不同的部署环境需要修改! -cap_sleep_time: 120 #5分钟 +cap_sleep_time: 120 #单位秒 -- 5分钟 buffer_len: 100 #分析后画面缓冲区帧数 -- 可以与验证帧率结合确定缓冲区大小 RESET_INTERVAL : 100000 #帧数重置上限 frame_rate : 20 #帧率参考值 -- 后续作用主要基于verify_rate进行帧率控制 -verify_rate : 10 #验证帧率--- 也就是视频输出的帧率 +verify_rate : 8 #验证帧率--- 也就是视频输出的帧率 warn_video_path: /mnt/zfbox/model/warn/ warn_interval: 120 #报警间隔--单位秒 +video_error_count: 3 #单位秒 ---根据验证帧率,判断3秒内都是空帧的话,视频源链接有问题。 diff --git a/core/ChannelManager.py b/core/ChannelManager.py index e92cb59..e39710b 100644 --- a/core/ChannelManager.py +++ b/core/ChannelManager.py @@ -7,6 +7,7 @@ import queue class ChannelData: def __init__(self, str_url, int_type, bool_run, deque_length,icount_max): + self.cap = None self.str_url = str_url #视频源地址 self.int_type = int_type #视频源类型,0-usb,1-rtsp,2-hksdk self.bool_run = bool_run #线程运行标识 @@ -80,7 +81,9 @@ class ChannelManager: if channel_id in self.channels: #若已经有数据,先删除后再增加 self.channels[channel_id].clear() # 手动清理资源 del self.channels[channel_id] - self.channels[channel_id] = ChannelData(str_url, int_type, bool_run, deque_length,icount_max) + ch_data = ChannelData(str_url, int_type, bool_run, deque_length, icount_max) + self.channels[channel_id] = ch_data + return ch_data #删除节点 def delete_channel(self, channel_id): @@ -94,15 +97,17 @@ class ChannelManager: with self.lock: return self.channels.get(channel_id) - #停止工作线程 + #停止工作线程---要把视频采集线程停止掉 def stop_channel(self,channel_id): with self.lock: if channel_id == 0: for clannel_id,clannel_data in self.channels.items(): - clannel_data.clear() + clannel_data.cap.running = False + clannel_data.clear() #clear 里面已经停止了通道的工作线程 del self.channels else: if channel_id in self.channels: + self.channels[channel_id].cap.running = False self.channels[channel_id].clear() # 手动清理资源 del self.channels[channel_id] diff --git a/core/DBManager.py b/core/DBManager.py index da16817..0418c4c 100644 --- a/core/DBManager.py +++ b/core/DBManager.py @@ -104,8 +104,13 @@ class DBManager(): return bok #---------------------特定数据库操作函数--------------------- - #根据通道ID或者模型ID删除通道和模型间的关联数据 1-通道ID,2-模型ID def delC2M(self,ID,itype): + ''' + #根据通道ID或者模型ID删除通道和模型间的关联数据 1-通道ID,2-模型ID ,注意会有删除没有数据的情况 + :param ID: + :param itype: + :return: + ''' #channel2model if itype ==1: strsql = f"select ID from channel2model where channel_id={ID};" @@ -113,16 +118,12 @@ class DBManager(): strsql = f"delete from channel2model where channel_id={ID};" ret = self.do_sql(strsql) - if ret == False: - return False elif itype ==2: strsql = f"select ID from channel2model where model_id={ID};" datas = self.do_select(strsql) strsql = f"delete from channel2model where model_id={ID};" ret = self.do_sql(strsql) - if ret == False: - return False else: return False #schedule @@ -130,13 +131,11 @@ class DBManager(): c2m_id = data[0] strsql = f"delete from schedule where channel2model_id={c2m_id};" ret = self.do_sql(strsql) - if ret == False: - return False return True #删除通道,需要关联删除布防时间,通道和算法的关联表 def delchannel(self,ID): - ret = self.delC2M(ID) + ret = self.delC2M(ID,1) if ret == False: return False #channel diff --git a/core/ModelManager.py b/core/ModelManager.py index 3ca55c2..fc814c1 100644 --- a/core/ModelManager.py +++ b/core/ModelManager.py @@ -8,6 +8,7 @@ import threading import importlib.util import datetime import math +import copy import queue from collections import deque from core.DBManager import mDBM,DBManager @@ -16,6 +17,8 @@ from myutils.ConfigManager import myCongif from model.plugins.ModelBase import ModelBase from core.ChannelManager import ChannelManager from core.ACLModelManager import ACLModeManger +from core.WarnManager import WarnManager,WarnData + from PIL import Image @@ -25,7 +28,21 @@ class VideoCaptureWithFPS: self.source = source self.width = None self.height = None - self.cap = cv2.VideoCapture(self.source) + # GStreamer + #rtsp_stream = f"rtspsrc location={self.source} ! decodebin ! videoconvert ! appsink" + pipeline = ( + f"rtspsrc location={self.source} latency=0 ! " + "rtph264depay ! " + "h264parse ! " + "avdec_h264 ! " # 使用 avdec_h264 代替其他解码器 + "videoscale ! " + "video/x-raw,width=640,height=480,framerate=10/1 ! " # 降低分辨率和帧率 + "videoconvert ! " + "appsink" + ) + self.cap = cv2.VideoCapture(pipeline, cv2.CAP_GSTREAMER) + # opencv + # self.cap = cv2.VideoCapture(self.source) if self.cap.isOpened(): #若没有打开成功,在读取画面的时候,已有判断和处理 -- 这里也要检查下内存的释放情况 self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) @@ -52,12 +69,15 @@ class VideoCaptureWithFPS: if self.cap.isOpened(): self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + print(self.width,self.height) # self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压 self.fps = math.ceil( self.cap.get(cv2.CAP_PROP_FPS) / float(myCongif.get_data("verify_rate"))) # 向上取整。 icount = 0 else: - time.sleep(1) + sleep_time = myCongif.get_data("cap_sleep_time") + print(f"{self.source}视频流,将于{sleep_time}秒后重连!") + time.sleep(sleep_time) continue #resized_frame = cv2.resize(frame, (int(self.width / 2), int(self.height / 2))) with self.read_lock: @@ -88,7 +108,6 @@ class VideoCaptureWithFPS: self.thread.join() self.cap.release() - class ModelManager: def __init__(self): self.verify_list = ChannelManager() #模型的主要数据 -- 2024-7-5修改为类管理通道数据 @@ -109,6 +128,9 @@ class ModelManager: # acl初始化 -- 一个线程一个 -- 需要验证 if self.model_platform == "acl": ACLModeManger.init_acl(self.device_id) #acl -- 全程序初始化 + self.model_dic = {} # model_id model + # 报警处理线程-全进程独立一个线程处理 + self.warnM = None def __del__(self): self.logger.debug("释放资源") @@ -151,6 +173,7 @@ class ModelManager: else: self.logger.error("{}文件不存在".format(model_path)) return None + self.logger.debug(f"{model_path} 加载成功!!!!") return md def getschedule(self,c2m_id,myDBM): @@ -199,43 +222,33 @@ class ModelManager: def set_last_img(self,): pass - def verify(self,frame,myModle_list,myModle_data,channel_id,schedule_list,result_list,isdraw=1): - '''验证执行主函数,实现遍历通道关联的模型,调用对应模型执行验证,模型文件遍历执行''' - img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - #img = np.ascontiguousarray(img, dtype=np.float32) / 255.0 # 转换为内存连续存储的数组 --该函数可以待定下是不是所有模型都可以做 - # img = frame.to_ndarray(format="bgr24") - #img = frame - # 使用 模型 进行目标检测 - i_warn_count = 0 #报警标签 - #isverify = False - for i in range(len(myModle_list)): # 遍历通道关联的算法进行检测,若不控制模型数量,有可能需要考虑多线程执行。 - model = myModle_list[i] - data = myModle_data[i] - schedule = schedule_list[i] - result = result_list[i] - #验证检测计划,是否在布防时间内 - now = datetime.datetime.now() # 获取当前日期和时间 - weekday = now.weekday() # 获取星期几,星期一是0,星期天是6 - hour = now.hour - result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组 - if schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片 - # 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- ********* - #isverify = True - detections, bwarn, warntext = model.verify(img, data,isdraw) #****************重要 - # 对识别结果要部要进行处理 - if bwarn: # 整个识别有产生报警 - #根据模型设定的时间和占比判断是否 - # 绘制报警文本 - cv2.putText(img, 'Intruder detected!', (50, (i_warn_count + 1) * 50), - cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) - i_warn_count += 1 - result.append(1) #要验证数组修改,是地址修改吗? - else: #没有产生报警也需要记录,统一计算占比 - result.append(0) - else: + def verify(self,frame,model,model_data,channel_id,schedule,result,isdraw=1): + '''验证执行主函数,实现遍历通道关联的模型,调用对应模型执行验证''' + #img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + img = frame + #验证检测计划,是否在布防时间内 + now = datetime.datetime.now() # 获取当前日期和时间 + weekday = now.weekday() # 获取星期几,星期一是0,星期天是6 + hour = now.hour + result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组 + detections = None + bwarn = False + warntext = "" + if model and schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片 + # 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- ********* + #isverify = True + detections, bwarn, warntext = model.verify(img, model_data,isdraw) #****************重要 + # 对识别结果要部要进行处理 + if bwarn: + # 绘制报警文本 + cv2.putText(img, warntext, (50, 50), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) + result.append(1) #要验证数组修改,是地址修改吗? + else: #没有产生报警也需要记录,统一计算占比 + warntext = "" result.append(0) - # if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。 --2024-7-5 取消休眠,帧率控制在dowork_thread完成 - # time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样 + else: + result.append(0) # 将检测结果图像转换为帧--暂时用不到AVFrame--2024-7-5 # new_frame_rgb_avframe = av.VideoFrame.from_ndarray(img, format="rgb24") # AVFrame @@ -243,15 +256,14 @@ class ModelManager: # if isinstance(img, np.ndarray): -- 留个纪念 #处理完的图片后返回-bgr模式 - img_bgr_ndarray = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + #img_bgr_ndarray = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) # 将检查结果转换为WebP格式图片 --在线程里面完成应该可以减少网页端处理时间 - ret,frame_bgr_webp=cv2.imencode('.jpg', img_bgr_ndarray) + ret,frame_bgr_webp=cv2.imencode('.jpg', img) if not ret: buffer_bgr_webp = None else: buffer_bgr_webp = frame_bgr_webp.tobytes() - return buffer_bgr_webp,img_bgr_ndarray - + return buffer_bgr_webp,img,warntext def dowork_thread(self,channel_id): '''一个通道一个线程,关联的模型在一个线程检测,局部变量都是一个通道独有''' @@ -267,38 +279,24 @@ class ModelManager: f"t2.model_name,t1.conf_threshold " f"from channel2model t1 left join model t2 on t1.model_id = t2.ID where t1.channel_id ={channel_id};") #print(strsql) - myModels = myDBM.do_select(strsql) - #加载模型 --- 是不是要做个限制,一个视频通道关联算法模块的上限 --- 关联多了一个线程执行耗时较多,造成帧率太低,或者再多线程并发 #? - - myModle_list = [] #存放模型对象List 一个模型一个 - myModle_data = [] #存放检测参数 一个模型一个 - schedule_list = [] #布防策略 -一个模型一个 - result_list = [] #检测结果记录 -一个模型一个 - warn_last_time =[] #最新的报警时间记录 -一个模型一个 - proportion_list = []#占比设定 -一个模型一个 - warn_save_count = []#没个模型触发报警后,保存录像的最新帧序号 -一个模型一个 - - #获取视频通道的模型相关数据-list - for model in myModels: - #基于基类实例化模块类 - m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载 - #m = None - if m: - myModle_list.append(m) #没有成功加载的模型原画输出 - myModle_data.append(model) - #model[6] -- c2m_id --布防计划 0-周一,6-周日 - schedule_list.append(self.getschedule(model[6],myDBM)) - result = [0 for _ in range(model[3] * myCongif.get_data("verify_rate"))] #初始化时间*验证帧率数量的结果list - result_list.append(result) - warn_last_time.append(time.time()) - proportion_list.append(model[4]) #判断是否报警的占比 - warn_save_count.append(0) #保存录像的最新帧初始化为0 + model = myDBM.do_select(strsql,1) #2024-7-12调整规则,一个通道只关联一个模型,表结构暂时不动 + if len(model) ==0: + print(f"{channel_id}视频通道没有关联模型,结束线程!") + return + + #基于基类实例化模块类 + m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py + schedule = self.getschedule(model[6], myDBM) + result = [0 for _ in range(model[3] * myCongif.get_data("verify_rate"))] # 初始化时间*验证帧率数量的结果list + + #model[6] -- c2m_id --布防计划 0-周一,6-周日 + warn_last_time = time.time() + proportion = model[4] #判断是否报警的占比 + warn_save_count = 0 #保存录像的最新帧初始化为0 #开始拉取画面循环检测 - cap = None - #iread_count =0 #失败读取的次数 + cap = channel_data.cap last_frame_time = time.time() #初始化个读帧时间 - cap_sleep_time = myCongif.get_data("cap_sleep_time") #可以释放数据库资源 del myDBM warn_interval = myCongif.get_data("warn_interval") @@ -310,53 +308,40 @@ class ModelManager: time.sleep(self.frame_interval - elapsed_time) #若小于间隔时间则休眠 last_frame_time = time.time() #*********取画面************* - if not cap: #第一次需要打开视频流 - try: - cap = self._open_view(channel_data.str_url,channel_data.int_type) #创建子线程读画面 - except: - self.logger.error("打开视频参数错误,终止线程!") - return ret,frame = cap.read() #除了第一帧,其它应该都是有画面的 if not ret: - # if iread_count > 30: #2024-7-8 重连接机制放VideoCaptureWithFPS - # self.logger.warning(f"通道-{channel_id}:view disconnected. Reconnecting...") - # cap.release() - # cap = None - # time.sleep(cap_sleep_time) - # else: - # iread_count += 1 continue #没读到画面继续 - #执行图片推理 -- 如何没有模型或不在工作时间,返回的是原画,要不要控制下帧率? -- 在verify中做了sleep - buffer_bgr_webp,img_bgr_ndarray = self.verify(frame,myModle_list,myModle_data,channel_id,schedule_list,result_list) - + #执行图片推理 + buffer_bgr_webp,img_bgr_ndarray,warn_text = self.verify(frame,m,model,channel_id,schedule,result) #分析图片放入内存中 channel_data.add_deque(img_bgr_ndarray) # 缓冲区大小由maxlen控制 超上限后,删除最前的数据 - channel_data.increment_counter() #帧序列加一 + #channel_data.increment_counter() #帧序列加一 # 一直更新最新帧,提供网页端显示 channel_data.update_last_frame(buffer_bgr_webp) #print(f"{channel_id}--Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) - #验证result_list -是否触发报警要求 --遍历每个模型执行的result - for i in range(len(result_list)): - result = result_list[i] - proportion = proportion_list[i] + if warn_text: + #验证result -是否触发报警要求 --遍历每个模型执行的result count_one = float(sum(result)) #1,0 把1累加的和就是1的数量 ratio_of_ones = count_one / len(result) #self.logger.debug(result) if ratio_of_ones >= proportion: #触发报警 # 基于时间间隔判断 current_time = time.time() - elapsed_time = current_time - warn_last_time[i] + elapsed_time = current_time - warn_last_time if elapsed_time < warn_interval: continue - warn_last_time[i] = current_time - model_name = myModle_data[i][7] - w_s_count = warn_save_count[i] - buffer_count = channel_data.get_counter() - self.save_warn(model_name,w_s_count,buffer_count,channel_data.copy_deque(), - cap.width,cap.height,channel_id,None,self.FPS,self.fourcc) - self.send_warn() - #更新帧序列号 - warn_save_count[i] = buffer_count + warn_last_time = current_time + # 处理报警 + warn_data = WarnData() + warn_data.model_name = model[7] + warn_data.warn_text = warn_text + warn_data.img_buffer = channel_data.copy_deque() # 深度复制缓冲区 + warn_data.width = cap.width + warn_data.height = cap.height + warn_data.channel_id = channel_id + self.warnM.add_warn_data(warn_data) + # #更新帧序列号 #加了报警间隔 --buffer的长度小于间隔 + # warn_save_count = buffer_count #结果记录要清空 for i in range(len(result)): result[i] = 0 @@ -369,72 +354,14 @@ class ModelManager: # if cv2.waitKey(1) & 0xFF == ord('q'): # break #结束线程 - cap.release() #视频采集线程结束 if context:#ACL线程中反初始化内容 -- 若线程异常退出,这些资源就不能正常释放了 #先释放每个模型资源 - for model in myModle_list: - del model + del model #再释放context ACLModeManger.th_del_acl(context) #cv2.destroyAllWindows() - def save_warn(self,model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,myDBM,FPS,fourcc): - ''' - 保存报警信息 --- 涉及到I/O操作可以通过线程取执行 -- 避免主线程阻塞 --还未验证-2024-7-6 - :param model_name: 模型名称,如人员入侵 - :param w_s_count: 报警已存储的最新帧序列 - :param buffer_count: 当前视频缓冲区的最新帧序列 - :param buffer: 视频缓存区 - :param width: 视频画面的width - :param height: 视频画面的height - :param channnel_id: 视频通道ID - :return: ret 数据库操作记录 - ''' - return - - def save_warn_th(model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,myDBM,FPS,fourcc): - now = datetime.datetime.now() # 获取当前日期和时间 - current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S") - filename = f"{channnel_id}_{current_time_str}" - save_path = myCongif.get_data("warn_video_path") - #保存视频 - video_writer = cv2.VideoWriter(f"{save_path}{filename}.mp4", fourcc, FPS, (width, height)) - if not video_writer.isOpened(): - print(f"Failed to open video writer for model/warn/{filename}.mp4") - return False - ilen = len(buffer) - istart = 0; - iend = ilen - if buffer_count < w_s_count or (buffer_count-w_s_count) > ilen: #buffer_count重置过 - #buffer区,都保存为视频 - istart = 0 - else:#只取差异的缓冲区大小 - istart = ilen - (buffer_count-w_s_count) - for i in range(istart,iend): - video_writer.write(buffer[i]) - video_writer.release() - #保存图片 - ret = cv2.imwrite(f"model/warn/{filename}.png",buffer[-1]) - #buffer使用完后删除 - del buffer - if not ret: - print("保存图片失败") - return False - #保存数据库 - myDBM = DBManager() - myDBM.connect() - strsql = (f"INSERT INTO warn (model_name ,video_path ,img_path ,creat_time,channel_id ) " - f"Values ('{model_name}','model/warn/{filename}.mp4','model/warn/{filename}.png'," - f"'{current_time_str}','{channnel_id}');") - ret = myDBM.do_sql(strsql) - del myDBM #释放数据库连接资源 - return ret - - th_chn = threading.Thread(target=save_warn_th, - args=(model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,None,FPS,fourcc,)) # 一个视频通道一个线程,线程句柄暂时部保留 - th_chn.start() - def send_warn(self): '''发送报警信息''' pass @@ -454,18 +381,34 @@ class ModelManager: strsql = f"select id,ulr,type from channel where is_work = 1 and id = {channel_id};" #单通道启动检测线程 datas = mDBM.do_select(strsql) for data in datas: - # img_buffer = deque(maxlen=myCongif.get_data("buffer_len")) #创建个定长的视频buffer - # img = None - # icout = 0 #跟img_buffer对应,记录进入缓冲区的帧序列号 - # run_data = [data[1],data[2],True,img_buffer,img,icout] - # self.verify_list[data[0]] = run_data #需要验证重复情况#? channel_id, str_url, int_type, bool_run, deque_length - self.verify_list.add_channel(data[0],data[1],data[2],True,myCongif.get_data("buffer_len"),myCongif.get_data("RESET_INTERVAL")) + # channel_id, str_url, int_type, bool_run, deque_length + c_data = self.verify_list.add_channel(data[0],data[1],data[2],True, + myCongif.get_data("buffer_len"),myCongif.get_data("RESET_INTERVAL")) + + # 启动该通道的视频捕获线程 --把视频捕获线程,放主线程创建 + c_data.cap = self._open_view(c_data.str_url, c_data.int_type) # 创建子线程读画面-把cap给模型就行-- + th_chn = threading.Thread(target=self.dowork_thread, args=(data[0],)) #一个视频通道一个线程,线程句柄暂时部保留 th_chn.start() + # 启动告警线程 + if self.warnM is None: + self.warnM = WarnManager() + self.warnM.start_warnmanager_th() def stop_work(self,channel_id=0): '''停止工作线程,0-停止所有,非0停止对应通道ID的线程''' self.verify_list.stop_channel(channel_id) + if channel_id == 0: + #停止告警线程 + self.warnM.brun = False + + def restartC2M(self,channel_id): + ''' + 修改通道管理的算法模型后需要对该通道算法执行部分重新加载执行 + :param channel_id: + :return: + ''' + pass #print(f"Current working directory (ModelManager.py): {os.getcwd()}") diff --git a/core/WarnManager.py b/core/WarnManager.py new file mode 100644 index 0000000..b1af851 --- /dev/null +++ b/core/WarnManager.py @@ -0,0 +1,102 @@ +import threading +import queue +import datetime +import cv2 +from core.DBManager import DBManager +from myutils.ConfigManager import myCongif + +class WarnData: + def __init__(self): + self.width = None #视频画面的width + self.height = None #视频画面的height + self.channel_id = None + self.model_name = None #模型名称,如人员入侵 + self.img_buffer = None #视频缓冲区 赋值时要拷贝一个备份 + + self.warn_text = None + self.channel_name = None + + + +class WarnManager: + def __init__(self): + self.warn_q = queue.Queue() #线程安全 + self.brun = True + # 保存视频相关内容 + self.FPS = myCongif.get_data("verify_rate") # 视频帧率--是否能实现动态帧率 + self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 使用 mp4 编码 + + def __del__(self): + pass + + def add_warn_data(self,warn_data): + self.warn_q.put(warn_data) + + def th_warnmanager(self): + myDBM = DBManager() + myDBM.connect() + while self.brun: + warn_data = self.warn_q.get() + self.save_warn(warn_data.model_name,warn_data.img_buffer,warn_data.width,warn_data.height, + warn_data.channel_id,self.FPS,self.fourcc,myDBM) + self.send_warn() + del warn_data.img_buffer + del warn_data + + + def start_warnmanager_th(self): + th_warn = threading.Thread(target=self.th_warnmanager) # 一个视频通道一个线程,线程句柄暂时部保留 + th_warn.start() + + def stop_warnmanager_th(self): + self.brun = False + del self.warn_q + + def send_warn(self): + '''发送报警信息''' + pass + + def save_warn(self,model_name,buffer,width,height,channnel_id,FPS,fourcc,myDBM): + ''' + 保存报警信息 --- 涉及到I/O操作可以通过线程取执行 -- 避免主线程阻塞 --还未验证-2024-7-6 + :param model_name: 模型名称,如人员入侵 + :param w_s_count: 报警已存储的最新帧序列 + :param buffer_count: 当前视频缓冲区的最新帧序列 + :param buffer: 视频缓存区 + :param width: 视频画面的width + :param height: 视频画面的height + :param channnel_id: 视频通道ID + :return: ret 数据库操作记录 + ''' + now = datetime.datetime.now() # 获取当前日期和时间 + current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S") + filename = f"{channnel_id}_{current_time_str}" + save_path = myCongif.get_data("warn_video_path") + # 保存视频 + video_writer = cv2.VideoWriter(f"{save_path}{filename}.mp4", fourcc, FPS, (width, height)) + if not video_writer.isOpened(): + print(f"Failed to open video writer for model/warn/{filename}.mp4") + return False + ilen = len(buffer) + istart = 0; + iend = ilen + + for i in range(len(buffer)): + video_writer.write(buffer[i]) + video_writer.release() + # 保存图片 + ret = cv2.imwrite(f"model/warn/{filename}.png", buffer[-1]) + # buffer使用完后删除 + del buffer + if not ret: + print("保存图片失败") + return False + # 保存数据库 + + strsql = (f"INSERT INTO warn (model_name ,video_path ,img_path ,creat_time,channel_id ) " + f"Values ('{model_name}','model/warn/{filename}.mp4','model/warn/{filename}.png'," + f"'{current_time_str}','{channnel_id}');") + ret = myDBM.do_sql(strsql) + del myDBM # 释放数据库连接资源 + return ret + diff --git a/model/plugins/Peo_ACL/yolov5s_bs1.om b/model/plugins/Peo_ACL/yolov5s_bs1.om new file mode 100644 index 0000000..3334c7b Binary files /dev/null and b/model/plugins/Peo_ACL/yolov5s_bs1.om differ diff --git a/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py b/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py index 93b28ab..106d193 100644 --- a/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py +++ b/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py @@ -51,7 +51,7 @@ class Model(ModelBase): # 模型推理, 得到模型输出 outputs = None - #outputs = self.execute([img,])#创建input,执行模型,返回结果 --失败返回None + outputs = self.execute([img,])#创建input,执行模型,返回结果 --失败返回None filtered_pred_all = None bwarn = False @@ -85,7 +85,7 @@ class Model(ModelBase): continue #没产生报警-继续 #产生报警 -- 有一个符合即可 bwarn = True - warn_text = "Intruder detected!" + warn_text = "People Intruder detected!" img_dw = draw_bbox(filtered_pred_all, image, (0, 255, 0), 2, labels_dict) # 画出检测框、类别、概率 #cv2.imwrite('img_res.png', img_dw) return filtered_pred_all, bwarn, warn_text diff --git a/run.py b/run.py index 02be082..97c7068 100644 --- a/run.py +++ b/run.py @@ -1,13 +1,29 @@ -from core.ViewManager import mVManager from web import create_app from core.ModelManager import mMM import os import platform import shutil - +import asyncio +from hypercorn.asyncio import serve +from hypercorn.config import Config +from core.DBManager import mDBM print(f"Current working directory (run.py): {os.getcwd()}") web = create_app() +async def run_quart_app(): + config = Config() + config.bind = ["0.0.0.0:5001"] + await serve(web, config) + +def test(): + area_id = 1 + c_name = "55" + strsql = f"select ID from channel where area_id={area_id} and channel_name={c_name};" + data = mDBM.do_select(strsql, 1) + if data: + print("有值") + else: + print("无值") if __name__ == '__main__': system = platform.system() @@ -22,5 +38,7 @@ if __name__ == '__main__': print(free/(1024*1024)) mMM.start_work() # 启动所有通道的处理 #mVManager.start_check_rtsp() #线程更新视频在线情况 - web.run(debug=True,port=5001,host="0.0.0.0") + asyncio.run(run_quart_app()) + + diff --git a/web/API/channel.py b/web/API/channel.py index 83729b9..ce14fed 100644 --- a/web/API/channel.py +++ b/web/API/channel.py @@ -4,6 +4,9 @@ from . import api from web.common.utils import login_required from core.DBManager import mDBM from myutils.ReManager import mReM +from core.ModelManager import mMM +import cv2 +import base64 @api.route('/channel/tree',methods=['GET']) @login_required @@ -15,11 +18,15 @@ async def channel_tree(): #获取通道树 @api.route('/channel/list',methods=['GET']) async def channel_list(): #获取通道列表 --分页查询,支持区域和通道名称关键字查询 - strsql = ("select t2.area_name ,t1.ID ,t1.channel_name ,t1.ulr ,t1.'type' ,t1.status ,t1.element_id" - " from channel t1 left join area t2 on t1.area_id = t2.id where t1.is_work=1;") + strsql = ("select t2.area_name,t1.ID,t1.channel_name,t1.ulr,t1.'type',t1.status,t1.element_id,t4.model_name " + "from channel t1 left join area t2 on t1.area_id = t2.id " + "left JOIN channel2model t3 on t1.ID = t3.channel_id " + "left JOIN model t4 on t3.model_id = t4.ID " + "where t1.is_work=1 order by area_name desc;") data = mDBM.do_select(strsql) channel_list = [{"area_name": channel[0], "ID": channel[1], "channel_name": channel[2], "ulr": channel[3], - "type": channel[4], "status": channel[5], "element_id": channel[6]} for channel in data] + "type": channel[4], "status": channel[5], + "element_id": channel[6],"model_name":channel[7]} for channel in data] return jsonify(channel_list) @api.route('/channel/info',methods=['GET']) @@ -29,34 +36,85 @@ async def channel_info(): #获取通道信息 ---- list已获取详情 @api.route('/channel/add',methods=['POST']) @login_required -async def channel_add(): #新增通道 - area_id = (await request.form)['area_id'] - channel_name = (await request.form)['channel_name'] - url = (await request.form)['url'] - if mReM.is_valid_rtsp_url(url) is not True: +async def channel_add(): #新增通道 -- 2024-8-1修改为与修改通道用一个接口 + json_data = await request.get_json() + area = json_data.get('area') + cName = json_data.get('cName') + Rtsp = json_data.get('Rtsp') + cid = int(json_data.get('cid')) + + if mReM.is_valid_rtsp_url(Rtsp) is not True: reStatus = 0 reMsg = 'rtsp地址不合法' return jsonify({'status': reStatus, 'msg': reMsg}) - strsql = f"select area_name from area where id = {area_id};" + strsql = f"select id from area where area_name = '{area}';" ret = mDBM.do_select(strsql,1) if ret: - strsql = (f"INSERT INTO channel (area_id,channel_name,ulr,'type',status) values " - f"({area_id},'{channel_name}','{url}',1,0);") - ret = mDBM.do_sql(strsql) - if ret == True: - reStatus = 1 - reMsg = '添加通道成功' - else: + strsql = f"select ID from channel where area_id={ret[0]} and channel_name={cName};" + data = mDBM.do_select(strsql, 1) + if data: #有值--代表重复了 reStatus = 0 - reMsg = '添加通道失败,请联系技术支持!' + reMsg = "同一区域内的通道名称不能相同!" + else: + if cid == -1: + strsql = (f"INSERT INTO channel (area_id,channel_name,ulr,'type',status) values " + f"({ret[0]},'{cName}','{Rtsp}',1,0);") + else: + strsql = (f"UPDATE channel SET area_id={ret[0]},channel_name='{cName}'" + f",ulr='{Rtsp}' where ID={cid};") + ret = mDBM.do_sql(strsql) + if ret == True: + reStatus = 1 + if cid == -1: + reMsg = '添加通道成功' + # 对新增的视频通道进行视频采集和 + strsql = f"select ID from channel where area_id={ret[0]} and channel_name={cName};" + data = mDBM.do_select(strsql, 1) + if data: + cid = data[0] + mMM.start_work(cid) + else: + print("这里不应该没有值!!!!") + else: + reMsg = '修改通道成功' + #需要先停再启动---不区分有没有修改URL了,就当有修改使用 + mMM.stop_work(cid) + mMM.start_work(cid) + else: + reStatus = 0 + if cid == -1: + reMsg = '添加通道失败,请联系技术支持!' + else: + reMsg = '修改通道信息失败,请联系技术支持!' else: reStatus = 0 - reMsg = '错误的通道ID,请修改' + reMsg = '错误的区域ID,请修改' return jsonify({'status':reStatus,'msg':reMsg}) +@api.route('/channel/img',methods=['POST']) +@login_required +async def channel_img(): + json_data = await request.get_json() + cid = int(json_data.get('cid')) + channel_data = mMM.verify_list.get_channel(cid) + if channel_data: + # 执行视频传输 + ret,frame = channel_data.cap.read() + if ret: + # 将帧转换为JPEG格式 + _, buffer = cv2.imencode('.jpg', frame) + # 将图像数据编码为Base64 + img_base64 = base64.b64encode(buffer).decode('utf-8') + # 返回JSON响应 + return jsonify({"image": img_base64}) + else: + return jsonify({"error": "Failed to capture frame"}), 404 + else: + return jsonify({"error": "Channel not found"}), 500 + @api.route('/channel/change',methods=['POST']) @login_required -async def channel_change(): #修改通道信息 +async def channel_change(): #修改通道信息 -- 已弃用 area_id = (await request.form)['area_id'] channel_id = (await request.form)['channel_id'] channel_name = (await request.form)['channel_name'] @@ -84,9 +142,11 @@ async def channel_change(): #修改通道信息 @api.route('/channel/del',methods=['POST']) @login_required async def channel_del(): #删除通道 - channel_id = (await request.form)['channel_id'] + json_data = await request.get_json() + cid = int(json_data.get('cid')) + mMM.stop_work(cid) #删除该通道和算法的关联信息:布防时间,算法模型数据----使用外键级联删除会方便很多,只要一个删除就可以 - ret = mDBM.delchannel(channel_id) + ret = mDBM.delchannel(cid) if ret == True: reStatus = 1 reMsg = '删除通道信息成' @@ -97,7 +157,7 @@ async def channel_del(): #删除通道 @api.route('/channel/check',methods=['GET']) @login_required -async def channel_check(): #检查通道视频的在线情况--可以获取全部 ID-0,全部,1*具体通道ID--10分钟会更新一次在线状态 +async def channel_check(): #检查通道视频的在线情况--可以获取全部 ID-0,全部,1*具体通道ID--10分钟会更新一次在线状态-- 要验证 ID = request.args.get('ID') if ID: ID = int(ID) @@ -118,6 +178,40 @@ async def channel_check(): #检查通道视频的在线情况--可以获取全 reMsg = "参数错误" return jsonify({'status': reStatus, 'msg': reMsg}) +@api.route('/channel/C2M',methods=['POST']) +@login_required +async def channel_to_model(): + '''获取通道关联模型的相关数据''' + json_data = await request.get_json() + cid = int(json_data.get('cid')) + #获取算法数据 + strsql = "select ID,model_name from model;" + model_datas = mDBM.do_select(strsql) + m_datas = [] + if model_datas: + m_datas = [{'ID':row[0],'model_name':row[1]} for row in model_datas] + #获取c2m数据 + strsql = f"select ID,model_id,check_area,polygon,conf_thres,iou_thres from channel2model where channel_id = {cid};" + c2m_data = mDBM.do_select(strsql,1) + #schedule数据 + schedule = [] + if c2m_data: + c2m_data_json = [{'model_id':c2m_data[1],'check_area':c2m_data[2],'polygon':c2m_data[3], + 'conf_thres':c2m_data[4],'iou_thres':c2m_data[5]}] + c2m_id = c2m_data[0] + strsql = f"select day,hour,status from schedule where channel2model_id ={c2m_id} order by hour asc,day asc;" + schedule_datas = mDBM.do_select(strsql) + if schedule_datas: + schedule = [{'day': row[0], 'hour': row[1], 'status': row[2]} for row in schedule_datas] + else: + schedule = [] + else: + schedule = [] + c2m_data_json = [] + #返回数据 + redata = {"m_datas":m_datas,"c2m_data":c2m_data_json,"schedule":schedule} + return jsonify(redata) + @api.route('/channel/area/list',methods=['GET']) @login_required async def channel_area_list(): #获取区域列表 @@ -175,29 +269,29 @@ async def channel_area_add(): #添加区域 reMsg = '新增区域失败,请联系技术支持!' return jsonify({'status': reStatus, 'msg': reMsg}) -@api.route('/channel/model/linklist',methods=['GET']) +@api.route('/channel/model/list',methods=['GET']) @login_required -async def channel_model_linklist(): #获取算法列表 --关联算法时展示 - ID = request.args.get('ID') #通道ID - strsql = (f"select t1.ID,t2.model_name from channel2model t1 left join model t2 " - f"on t1.model_id=t2.ID where channel_id={ID};") +async def channel_model_list(): #获取算法列表 + strsql = "select ID,model_name,version from model;" datas = mDBM.do_select(strsql) reMsg = {} if datas: - reMsg = [{'ID': data[0], 'model_name': data[1]} for data in datas] + reMsg = [{'ID': data[0], 'model_name': data[1],'version':data[2]} for data in datas] return jsonify(reMsg) -@api.route('/channel/model/list',methods=['GET']) +@api.route('/channel/model/linklist',methods=['GET']) @login_required -async def channel_model_list(): #获取算法列表 - strsql = "select ID,model_name,version from model;" +async def channel_model_linklist(): #获取算法列表 --关联算法时展示 --没调用。。 + ID = request.args.get('ID') #通道ID + strsql = (f"select t1.ID,t2.model_name from channel2model t1 left join model t2 " + f"on t1.model_id=t2.ID where channel_id={ID};") datas = mDBM.do_select(strsql) reMsg = {} if datas: - reMsg = [{'ID': data[0], 'model_name': data[1],'version':data[2]} for data in datas] + reMsg = [{'ID': data[0], 'model_name': data[1]} for data in datas] return jsonify(reMsg) -@api.route('/channel/model/linkmodel',methods=['POST']) +@api.route('/channel/model/linkmodel',methods=['POST']) #--没调用。。 @login_required async def channel_model_linkmodel(): #获取算法列表 --关联算法时展示 #?关联算法时需要初始化布防计划,同样删除的需要删除 channel_id = (await request.form)['channel_id'] @@ -234,7 +328,7 @@ async def channel_model_getarea(): #获取算法区域的备注信息 --- 同时 @api.route('/channel/model/changearea',methods=['POST']) @login_required -async def channel_model_changearea(): #修改算法区域信息 +async def channel_model_changearea(): #修改算法检测区域信息 ID = (await request.form)['ID'] check_area = (await request.form)['check_area'] check_x = (await request.form)['polygon'] @@ -243,6 +337,8 @@ async def channel_model_changearea(): #修改算法区域信息 if ret == True: reStatus = 1 reMsg = '修改算法检测区域成功' + #需要重启视频通道的执行程序 --需要cid + #? else: reStatus = 0 reMsg = '新修改算法检测区域失败,请联系技术支持!' @@ -250,7 +346,7 @@ async def channel_model_changearea(): #修改算法区域信息 @api.route('/channel/model/changethreshold',methods=['POST']) @login_required -async def channel_model_changthreshold(): #修改算法区域信息 +async def channel_model_changthreshold(): #修改算法阈值 ID = (await request.form)['ID'] conf_threshold = (await request.form)['conf_threshold'] @@ -258,10 +354,10 @@ async def channel_model_changthreshold(): #修改算法区域信息 ret = mDBM.do_sql(strsql) if ret == True: reStatus = 1 - reMsg = '修改算法检测区域成功' + reMsg = '修改算法的阈值成功' else: reStatus = 0 - reMsg = '新修改算法检测区域失败,请联系技术支持!' + reMsg = '新修改算法的阈值失败,请联系技术支持!' return jsonify({'status': reStatus, 'msg': reMsg}) @api.route('/channel/model/getschedule',methods=['GET']) diff --git a/web/API/user.py b/web/API/user.py index 94ae229..0d53778 100644 --- a/web/API/user.py +++ b/web/API/user.py @@ -1,5 +1,5 @@ import os -from quart import Quart, render_template, request, session, redirect, url_for,jsonify,send_file +from quart import Quart, render_template, request, session, redirect, url_for,jsonify,send_file,flash from quart_sqlalchemy import SQLAlchemy from quart_session import Session from web.common.utils import generate_captcha,login_required @@ -19,13 +19,23 @@ async def user_get_code(): #获取验证码 @api.route('/user/login',methods=['POST']) async def user_login(): #用户登录 - username = (await request.form)['username'] - password = (await request.form)['password'] - captcha = (await request.form)['captcha'] + try: + form = await request.form + username = form['username'] + password = form['password'] + captcha = form['captcha'] + except Exception as e: + await flash('请求数据格式错误', 'error') + return redirect(url_for('main.login')) + #return jsonify({'error': '请求数据格式错误'}), 400 + if captcha != session.get('captcha'): - #验证码验证过后,需要失效 - print(session.get('captcha')) - return '验证码错误', 400 + # 验证码验证过后,需要失效 + session.pop('captcha', None) + await flash('验证码错误', 'error') + return redirect(url_for('main.login')) + #return jsonify({'error': '验证码错误'}), 400 + #return 'captcha error!', 400 #比对用户名和密码 strsql = f"select password from user where username = '{username}'" db_password = mDBM.do_select(strsql,1) @@ -33,8 +43,9 @@ async def user_login(): #用户登录 if db_password[0] == password: #后续需要对密码进行MD5加默 print("登录成功") session['user'] = username - return redirect(url_for('main.get_html', html='实时预览.html')) - return '用户名或密码错误', 400 + return redirect(url_for('main.get_html', html='view_main.html')) + await flash('用户名或密码错误', 'error') + return redirect(url_for('main.login')) @api.route('/user/userinfo',methods=['GET']) @login_required diff --git a/web/API/viedo.py b/web/API/viedo.py index 6e045bc..821da64 100644 --- a/web/API/viedo.py +++ b/web/API/viedo.py @@ -5,6 +5,7 @@ from core.ModelManager import mMM from core.DBManager import mDBM from myutils.ConfigManager import myCongif import logging +import time # 配置日志 logging.basicConfig(level=logging.INFO) @@ -143,18 +144,44 @@ async def get_stats(peer_connection): @api.websocket('/ws/video_feed/') async def ws_video_feed(channel_id): + print(f"New connection for channel: {channel_id}") channel_data = mMM.verify_list.get_channel(channel_id) - frame_rate = myCongif.get_data("frame_rate") - while channel_data.bool_run: #这里的多线程并发,还需要验证检查 - frame = channel_data.get_last_frame() - if frame is not None: - #img = frame.to_ndarray(format="bgr24") - # ret, buffer = cv2.imencode('.jpg', frame) - # if not ret: - # continue - # frame = buffer.tobytes() - await websocket.send(frame) - await asyncio.sleep(1.0 / frame_rate) # Adjust based on frame rate + if channel_data: + verify_rate = int(myCongif.get_data("verify_rate")) + frame_interval = 1.0 / verify_rate #用于帧率控制 + error_max_count = verify_rate * int(myCongif.get_data("video_error_count")) #视频帧捕获失败触发提示的上限 + sleep_time = int(myCongif.get_data("cap_sleep_time")) + last_frame_time = time.time() # 初始化个读帧时间 + icount = 0 + while channel_data.bool_run: #这里的多线程并发,还需要验证检查 + # 帧率控制帧率 + current_time = time.time() + elapsed_time = current_time - last_frame_time + if elapsed_time < frame_interval: + await asyncio.sleep(frame_interval - elapsed_time) # 若小于间隔时间则休眠 + last_frame_time = time.time() + #执行视频传输 + frame = channel_data.get_last_frame() + if frame is not None: + #img = frame.to_ndarray(format="bgr24") + # ret, buffer = cv2.imencode('.jpg', frame) + # if not ret: + # continue + # frame = buffer.tobytes() + icount = 0 + await websocket.send(frame) + else: + icount += 1 + if icount > error_max_count: + icount = 0 + error_message = b"video_error" + await websocket.send(error_message) + await asyncio.sleep(sleep_time*1000) #等待视频重连时间 + else: + print("没有通道数据!") + error_message = b"client_error" + await websocket.send(error_message) + await asyncio.sleep(0.1) #等0.1秒前端处理时间 @api.route('/shutdown', methods=['POST']) async def shutdown():#这是全关 --需要修改 @@ -163,24 +190,59 @@ async def shutdown():#这是全关 --需要修改 pcs.clear() return 'Server shutting down...' +@api.route('/viewlist', methods=['GET']) +async def viewlist():#视频列表 + count = request.args.get('count') + channel_list = [] + element_list = [] + name_list = [] + if count: + strsql = (f"select t1.ID,t1.element_id,t1.channel_name,t2.area_name from channel t1 left join " + f"area t2 on t1.area_id =t2.id where element_id between 0 and {count};") + datas = mDBM.do_select(strsql) + for data in datas: + channel_list.append(data[0]) + element_list.append(data[1]) + name_list.append(f"{data[3]}--{data[2]}") + return jsonify({'clist': channel_list, 'elist': element_list,'nlist':name_list}) + +@api.route('/start_stream', methods=['POST']) +async def start_stream(): #开启视频通道,把视频通道编号和元素编号进行关联 + json_data = await request.get_json() + channel_id = json_data.get('channel_id') + element_id = json_data.get('element_id') + reStatus = 0 + reMsg = "" + strsql = f"select element_id from channel where ID = {channel_id};" + data = mDBM.do_select(strsql,1) + if data: + if data[0] == '': + strsql = f"update channel set element_id = '{element_id}' where ID={channel_id};" + ret = mDBM.do_sql(strsql) + if ret == True: + reStatus = 1 + reMsg = '播放视频配置成功!' + else: + reMsg = '播放视频配置失败,请联系技术支持!' + else: + index = int(data[0]) +1 + reMsg = f"该视频通道已在:Video Stream {index}播放,请先关闭" + return jsonify({'status': reStatus, 'msg': reMsg}) + + @api.route('/close_stream', methods=['POST']) -async def close_stream(): # 需要修改 - channel_id = (await request.form)['channel_id'] +async def close_stream(): #关闭视频通道 + json_data = await request.get_json() + element_id = json_data.get('element_id') reStatus = 0 reMsg ="" - if channel_id in pcs: - await pcs[channel_id].close() - pcs.pop(channel_id, None) - print(f'Stream {channel_id} closed.') - #数据库中该通道的关联关系要清除 - strsql = f"update channel set element_id = NULL where ID={channel_id};" - ret = mDBM.do_sql(strsql) - if ret == True: - reStatus = 1 - reMsg = '关闭画面成功!' - else: - reStatus = 0 - reMsg = '删除通道与组件关联关系失败,请联系技术支持!' + #?关闭视频播放--业务逻辑-待确认后端是否需要执行 + #数据库中该通道的关联关系要清除 + strsql = f"update channel set element_id = '' where element_id={element_id};" + ret = mDBM.do_sql(strsql) + if ret == True: + reStatus = 1 + reMsg = '关闭画面成功!' else: - reMsg = "通道编号不在系统内,请检查!" + reMsg = '删除通道与组件关联关系失败,请联系技术支持!' return jsonify({'status': reStatus, 'msg': reMsg}) diff --git a/web/__init__.py b/web/__init__.py index 8c10270..f4b3cd1 100644 --- a/web/__init__.py +++ b/web/__init__.py @@ -1,5 +1,6 @@ from quart import Quart,session,redirect, url_for from quart_session import Session +from quart_cors import cors from pymemcache.client import base from .main import main from .API import api @@ -31,6 +32,7 @@ class MemcachedSessionInterface: #只是能用,不明所以 def create_app(): app = Quart(__name__) + #app = cors(app, allow_credentials=True) #allow_origin:指定允许跨域访问的来源 #相关配置--设置各种配置选项,这些选项会在整个应用程序中被访问和使用。 # app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' # app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False diff --git a/web/main/__init__.py b/web/main/__init__.py index 3d5df65..bab07fb 100644 --- a/web/main/__init__.py +++ b/web/main/__init__.py @@ -1,5 +1,5 @@ from quart import Blueprint -main = Blueprint('main', __name__,template_folder='templates') +main = Blueprint('main', __name__,static_folder='static/resources',template_folder='templates') from . import routes diff --git a/web/main/routes.py b/web/main/routes.py index a0aa799..42f0fb5 100644 --- a/web/main/routes.py +++ b/web/main/routes.py @@ -14,7 +14,6 @@ from werkzeug.utils import secure_filename def login_required(f): @wraps(f) async def decorated_function(*args, **kwargs): - print("decorated_function3") if 'user' not in session: return redirect(url_for('main.index',error='未登录,请重新登录')) return await f(*args, **kwargs) @@ -22,12 +21,27 @@ def login_required(f): @main.route('/') async def index(): - print("index") - #error = request.args.get('error') - return await render_template('实时预览.html') - #return await render_template('登录.html',error=error) + #return await render_template('实时预览.html') + return await render_template('login.html') #return await render_template('index_webrtc.html') +@main.route('/login', methods=['GET', 'POST']) +async def login(): + if request.method == 'POST': + form = await request.form + username = form.get('username') + password = form.get('password') + # Add your login logic here + if username == 'admin' and password == 'password': + return redirect(url_for('main.dashboard')) # Assuming you have a dashboard route + else: + return "Invalid credentials", 401 + return await render_template('login.html') + +@main.route('/dashboard') +async def dashboard(): + return "Welcome to the dashboard!" + # @main.route('/', methods=['GET', 'POST']) # async def upload_file(): # if request.method == 'POST': diff --git a/web/main/static/favicon.ico b/web/main/static/favicon.ico index b8d74a1..37d0d9e 100644 Binary files a/web/main/static/favicon.ico and b/web/main/static/favicon.ico differ diff --git a/web/main/static/images/登录/u12.png b/web/main/static/images/登录/u12.png deleted file mode 100644 index 89338f0..0000000 Binary files a/web/main/static/images/登录/u12.png and /dev/null differ diff --git a/web/main/static/images/登录/u4.svg b/web/main/static/images/登录/u4.svg deleted file mode 100644 index 6142a4c..0000000 --- a/web/main/static/images/登录/u4.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/web/main/static/images/登录/zf.png b/web/main/static/images/登录/zf.png new file mode 100644 index 0000000..edeaf03 Binary files /dev/null and b/web/main/static/images/登录/zf.png differ diff --git a/web/main/static/images/登录/zf.svg b/web/main/static/images/登录/zf.svg new file mode 100644 index 0000000..193a974 --- /dev/null +++ b/web/main/static/images/登录/zf.svg @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/web/main/static/resources/css/headers.css b/web/main/static/resources/css/headers.css new file mode 100644 index 0000000..8230c9a --- /dev/null +++ b/web/main/static/resources/css/headers.css @@ -0,0 +1,15 @@ +.form-control-dark { + border-color: var(--bs-gray); +} +.form-control-dark:focus { + border-color: #fff; + box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .25); +} + +.text-small { + font-size: 85%; +} + +.dropdown-toggle { + outline: 0; +} diff --git a/web/main/static/resources/css/sign-in.css b/web/main/static/resources/css/sign-in.css new file mode 100644 index 0000000..67e301e --- /dev/null +++ b/web/main/static/resources/css/sign-in.css @@ -0,0 +1,33 @@ +html, +body { + height: 100%; +} + +body { + display: flex; + align-items: center; + padding-top: 40px; + padding-bottom: 40px; + background-color: #f5f5f5; +} + +.form-signin { + max-width: 400px; + padding: 15px; +} + +.form-signin .form-floating:focus-within { + z-index: 2; +} + +.form-signin input[type="text"] { + margin-bottom: 5px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} diff --git a/web/main/static/resources/images/zf.png b/web/main/static/resources/images/zf.png new file mode 100644 index 0000000..edeaf03 Binary files /dev/null and b/web/main/static/resources/images/zf.png differ diff --git a/web/main/static/resources/images/zf.svg b/web/main/static/resources/images/zf.svg new file mode 100644 index 0000000..193a974 --- /dev/null +++ b/web/main/static/resources/images/zf.svg @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/web/main/static/resources/scripts/aiortc-client-new.js b/web/main/static/resources/scripts/aiortc-client-new.js index 624b0df..720c566 100644 --- a/web/main/static/resources/scripts/aiortc-client-new.js +++ b/web/main/static/resources/scripts/aiortc-client-new.js @@ -1,9 +1,12 @@ -var pc_list = {}; +let video_list = {}; //element_id -- socket +let run_list = {}; //element_id -- runtag(替换berror) var channel_list = null; +const fourViewButton = document.getElementById('fourView'); +const nineViewButton = document.getElementById('nineView'); document.addEventListener('DOMContentLoaded', async function() { console.log('DOM fully loaded and parsed'); - // 发送请求获取额外数据 + // 发送请求获取额外数据 --- 这个接口功能有点大了---暂时只是更新通道树2024-7-29 try { let response = await fetch('/api/channel/list'); if (!response.ok) { @@ -11,50 +14,310 @@ document.addEventListener('DOMContentLoaded', async function() { } channel_list = await response.json(); // 遍历输出每个元素的信息 + let area_name = "" + let html = '
    '; channel_list.forEach(channel => { - if(channel.element_id){ //""空为false,非空为true - console.log(`Area Name: ${channel.area_name}`); - console.log(`ID: ${channel.ID}`); - console.log(`Channel Name: ${channel.channel_name}`); - console.log(`URL: ${channel.url}`); - console.log(`Type: ${channel.type}`); - console.log(`Status: ${channel.status}`); - console.log(`Element ID: ${channel.element_id}`); - connectToStream(channel.element_id,channel.ID,channel.area_name,channel.channel_name) +// console.log(`Area Name: ${channel.area_name}`); +// console.log(`ID: ${channel.ID}`); +// console.log(`Channel Name: ${channel.channel_name}`); +// console.log(`URL: ${channel.url}`); +// console.log(`Type: ${channel.type}`); +// console.log(`Status: ${channel.status}`); +// console.log(`Element ID: ${channel.element_id}`); + if(area_name !== `${channel.area_name}`){ + if(area_name !== ""){ + html += '
'; + html += ''; + } + area_name = `${channel.area_name}`; + html += `
  • ${area_name}`; + html += '
      '; } + //html += `
    • ${channel.channel_name}
    • `; + html += `
    • + + ${channel.channel_name} +
    • `; }); + if(area_name !== ""){ + html += '
    '; + html += '
  • '; + } + html += ''; + const treeView = document.getElementById('treeView'); + treeView.innerHTML = html + generateVideoNodes(4); } catch (error) { console.error('Failed to fetch data:', error); } }); -function connectToStream(element_id,channel_id,area_name,channel_name) { - const imgElement = document.getElementById(element_id); - imgElement.alt = `Stream ${area_name}--${channel_name}`; - const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`; - console.log(streamUrl); - function connect() { - const socket = new WebSocket(streamUrl); - - socket.onmessage = function(event) { - //const blob = new Blob([event.data], { type: 'image/jpeg' }); - //imgElement.src = URL.createObjectURL(blob); - // 释放旧的对象URL - if (imgElement.src) { - URL.revokeObjectURL(imgElement.src); - } - imgElement.src = URL.createObjectURL(event.data); - }; +//视频窗口 +document.getElementById('fourView').addEventListener('click', function() { + if (fourViewButton.classList.contains('btn-primary')) { + return; // 如果按钮已经是选中状态,直接返回 + } + const videoGrid = document.getElementById('videoGrid'); + videoGrid.classList.remove('nine'); + videoGrid.classList.add('four'); + generateVideoNodes(4); + //更新按钮点击状态 + fourViewButton.classList.remove('btn-secondary'); + fourViewButton.classList.add('btn-primary'); + nineViewButton.classList.remove('btn-primary'); + nineViewButton.classList.add('btn-secondary'); +}); + +document.getElementById('nineView').addEventListener('click', function() { + if (nineViewButton.classList.contains('btn-primary')) { + return; // 如果按钮已经是选中状态,直接返回 + } + const videoGrid = document.getElementById('videoGrid'); + videoGrid.classList.remove('four'); + videoGrid.classList.add('nine'); + generateVideoNodes(9); + //更新按钮点击状态 + fourViewButton.classList.remove('btn-primary'); + fourViewButton.classList.add('btn-secondary'); + nineViewButton.classList.remove('btn-secondary'); + nineViewButton.classList.add('btn-primary'); +}); + +function generateVideoNodes(count) { //在这里显示视频-初始化 + //结束在播放的socket + for(let key in video_list){ + delete run_list[key]; + video_list[key].close(); + delete video_list[key]; + } + //切换窗口布局 + const videoGrid = document.getElementById('videoGrid'); + let html = ''; + for (let i = 0; i < count; i++) { + let frameWidth = count === 4 ? 'calc(50% - 10px)' : 'calc(33.33% - 10px)'; + html += ` +
    +
    +
    Video Stream ${i+1}
    +
    + + +
    +
    +
    Video Stream
    +
    `; + } + videoGrid.innerHTML = html; + //获取视频接口 + const url = `/api/viewlist?count=${count}`; + fetch(url) + .then(response => response.json()) + .then(data => { + console.log('Success:', data); + clist = data.clist; + elist = data.elist; + nlist = data.nlist; + for(let i=0;i { + console.error('Error:', error); + }); +} + +function toggleFullScreen(id) { + console.log('toggleFullScreen'); + const videoFrame = document.querySelector(`[data-frame-id="${id}"]`); + if (!document.fullscreenElement) { + videoFrame.requestFullscreen().catch(err => { + alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`); + }); + } else { + document.exitFullscreen(); + }; +} + +function closeVideo(id) { + const titleElement = document.querySelector(`[data-frame-id="${id}"] .video-title`); + if (titleElement.textContent === `Video Stream ${Number(id)+1}`) { + showModal('当前视频窗口未播放视频。'); + return; + }; + console.log('closeVideo'); + //发送视频链接接口 + const url = '/api/close_stream'; + const data = {"element_id":id}; + // 发送 POST 请求 + fetch(url, { + method: 'POST', // 指定请求方法为 POST + headers: { + 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON + }, + body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 + }) + .then(response => response.json()) // 将响应解析为 JSON + .then(data => { + console.log('Success:', data); + const istatus = data.status; + if(istatus === 0){ + showModal(data.msg); // 使用 Modal 显示消息 + return; + } + else{ + const videoFrame = document.querySelector(`[data-frame-id="${id}"] .video-area img`); + const titleElement = document.querySelector(`[data-frame-id="${id}"] .video-title`); + run_list[id] = false; + video_list[id].close(); + + videoFrame.src = ''; // 清空画面 + videoFrame.style.display = 'none'; // 停止播放时隐藏 img 元素 + + titleElement.textContent = `Video Stream ${id+1}`; + removeErrorMessage(videoFrame) + } + }) + .catch((error) => { + showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息 + return; + }); +} + +function allowDrop(event) { + event.preventDefault(); +} - socket.onclose = function() { - setTimeout(connect, 1000); // 尝试在1秒后重新连接 - }; +function drag(event) { + event.dataTransfer.setData("text", event.target.dataset.nodeId); + event.dataTransfer.setData("name", event.target.dataset.nodeName); +} - socket.onerror = function() { - console.log(`WebSocket错误,尝试重新连接... Channel ID: ${channel_id}`); - socket.close(); - }; +function drop(event) { + event.preventDefault(); + const nodeId = event.dataTransfer.getData("text"); + const nodeName = event.dataTransfer.getData("name"); + const frameId = event.currentTarget.dataset.frameId; + + //需要判断下当前窗口是否已经在播放视频 + const imgElement = document.getElementById(`video-${frameId}`); + const titleElement = document.querySelector(`[data-frame-id="${frameId}"] .video-title`); + + if (titleElement.textContent !== `Video Stream ${Number(frameId)+1}`) { + showModal('请先关闭当前窗口视频,然后再播放新的视频。'); + return; + }; + //发送视频链接接口 + const url = '/api/start_stream'; + const data = {"channel_id":nodeId,"element_id":frameId}; + // 发送 POST 请求 + fetch(url, { + method: 'POST', // 指定请求方法为 POST + headers: { + 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON + }, + body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 + }) + .then(response => response.json()) // 将响应解析为 JSON + .then(data => { + const istatus = data.status; + if(istatus === 0){ + showModal(data.msg); // 使用 Modal 显示消息 + return; + } + else{ + //获取视频流 + connectToStream(frameId,nodeId,nodeName); + } + }) + .catch((error) => { + showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息 + return; + }); + //console.log('retrun 只是把fetch结束,这里的代码还是会执行'); +} + +function connectToStream(element_id,channel_id,channel_name) { + console.log("开始连接视频",channel_id); + // 设置视频区域的标题 + const titleElement = document.querySelector(`[data-frame-id="${element_id}"] .video-title`); + titleElement.textContent = channel_name; + //获取视频 + const imgElement = document.getElementById(`video-${element_id}`); + imgElement.alt = `Stream ${channel_name}`; + const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`; + let berror = false; + + function connect() { + const socket = new WebSocket(streamUrl); + video_list[element_id] = socket; + run_list[element_id] = true; + // 处理连接打开事件 + socket.onopen = () => { + console.log('WebSocket connection established'); + }; + + socket.onmessage = function(event) { + // 释放旧的对象URL---需要吗? +// if (imgElement.src) { +// URL.revokeObjectURL(imgElement.src); +// } + const reader = new FileReader(); + reader.onload = () => { + const arrayBuffer = reader.result; + const decoder = new TextDecoder("utf-8"); + const decodedData = decoder.decode(arrayBuffer); + + if (decodedData === "video_error") { //video_error + displayErrorMessage(imgElement, "该视频源未获取到画面,请检查,默认5分钟后重新链接视频源。"); + } else if(decodedData === "client_error"){ //client_error + run_list[element_id] = false; + displayErrorMessage(imgElement, "该通道节点数据存在问题,请重启或联系技术支持!"); + socket.close(); // 停止连接 + } + else { + const blob = new Blob([arrayBuffer], { type: 'image/jpeg' }); + const imageUrl = URL.createObjectURL(blob); + imgElement.src = imageUrl; + imgElement.style.display = 'block'; +// imgElement.src = URL.createObjectURL(result); +// imgElement.style.display = 'block'; + } + }; + reader.readAsArrayBuffer(event.data); + }; + + socket.onclose = function() { + if(run_list[element_id]){ + console.log(`尝试重新连接... Channel ID: ${channel_id}`); + setTimeout(connect, 1000*10); // 尝试在10秒后重新连接 } + }; + + socket.onerror = function() { + console.log(`WebSocket错误,Channel ID: ${channel_id}`); + socket.close(); + }; + }; + connect(); +} + +function displayErrorMessage(imgElement, message) { + removeErrorMessage(imgElement) + imgElement.style.display = 'none'; // 隐藏图片 + const errorElement = document.createElement('div'); + errorElement.textContent = message; + errorElement.classList.add('error-message'); + imgElement.parentNode.appendChild(errorElement); +} + +function removeErrorMessage(imgElement) { + const errorElement = imgElement.parentNode.querySelector('.error-message'); + if (errorElement) { + imgElement.parentNode.removeChild(errorElement); + } +} - connect(); - } \ No newline at end of file diff --git a/web/main/static/resources/scripts/base.js b/web/main/static/resources/scripts/base.js new file mode 100644 index 0000000..c906600 --- /dev/null +++ b/web/main/static/resources/scripts/base.js @@ -0,0 +1,47 @@ + + document.addEventListener('DOMContentLoaded', function() { + const links = document.querySelectorAll('.nav-pills .nav-link'); + // Get the current page path + const currentPath = window.location.pathname; + links.forEach(link => { + // Compare the link's href with the current path + if (link.href.endsWith(currentPath)) { + link.classList.add('active'); + } else { + link.classList.remove('active'); + } + }); + }); + +function showModal(message) { + // 设置 Modal 中的消息 + document.getElementById('modalMessage').innerText = message; + + // 显示 Modal + const responseModal = new bootstrap.Modal(document.getElementById('responseModal')); + responseModal.show(); +} + +//动态填充select控件 +function set_select_data(select_ele_id,datas){ + const select_Ele = document.getElementById(select_ele_id); + //清空老数据 + select_Ele.innerHTML = ''; + //添加列表 + datas.forEach(option => { + const optionElement = document.createElement('option'); + optionElement.textContent = option; + select_Ele.appendChild(optionElement); + }); +} + +//设定选项选中状态 +function set_select_selct(select_ele_id,option_str){ + const select_Ele = document.getElementById(select_ele_id); + for(let i=0;i< select_Ele.options.length;i++){ + if(select_Ele.options[i].value === option_str){ + select_Ele.options[i].selected = true; + break; + } + } +} \ No newline at end of file diff --git a/web/main/static/resources/scripts/channel_manager.js b/web/main/static/resources/scripts/channel_manager.js new file mode 100644 index 0000000..ee5f572 --- /dev/null +++ b/web/main/static/resources/scripts/channel_manager.js @@ -0,0 +1,608 @@ +const apiEndpoint = '/api/channel/list'; +const rowsPerPage = 10; +//算法配置窗口部分控件 +const searchEndpoint = '/api/channel/select'; +const canvas = document.getElementById('myCanvas'); +const ctx = canvas.getContext('2d'); +const backgroundCanvas = document.getElementById('backgroundCanvas'); +const backgroundCtx = backgroundCanvas.getContext('2d'); +const img = new Image(); +const tbody = document.getElementById('schedule-body');//布防计划 + +let currentPage = 1; +let channelData = []; +let channelData_bak = []; +let areaData = ["请选择"]; +let currentEditingRow = null; +let cid_schedule = "-1"; +let m_polygon = ""; +let check_area = 0; +let draw_status = false; //是否是绘制状态,处于绘制状态才能开始绘制 +let b_img = false; //有没有加载图片成功,如果没有初始化的时候就不绘制线条了。 +let points = []; + + + +document.addEventListener('DOMContentLoaded', function () { + fetchChannelData(); //初始化页面元素数据 + + document.getElementById('searchButton').addEventListener('click', function () { + performSearch(); + }); + //新增通道 + document.getElementById('saveButton').addEventListener('click', function () { + addChannel(1); + }); + //修改通道 + document.getElementById('saveButton_cc').addEventListener('click', function () { + addChannel(2); + }); + //算法配置 + document.getElementById('cancelButton_mx').addEventListener('click', function () { + close_mx_model(); + }); + //保存算法配置 + document.getElementById('saveButton_mx').addEventListener('click', function () { + save_mx_model(); + }); + //开始绘制区域按钮 + document.getElementById('but_hzqy').addEventListener('click', function () { + startDraw(); + }); +}); + +//添加和修改通道 1--新增,2--修改 +function addChannel(itype) { + let area; + let cName; + let Rtsp; + let cid; + if(itype ==1){ + const saveButton = document.getElementById('saveButton'); + const CNameInput = document.getElementById('CNameInput'); + const RTSPInput = document.getElementById('RTSPInput'); + area = document.getElementById('areaSelect_M').value; + cName = CNameInput.value.trim(); + Rtsp = RTSPInput.value.trim(); + cid = -1 + } + else if(itype ==2){ + const saveButton = document.getElementById('saveButton_cc'); + const CNameInput = document.getElementById('CNameInput_cc'); + const RTSPInput = document.getElementById('RTSPInput_cc'); + area = document.getElementById('areaSelect_CC').value; + cName = CNameInput.value.trim(); + Rtsp = RTSPInput.value.trim(); + cid = currentEditingRow.cells[0].innerText; + } + + if(area === "请选择"){ + alert('请选择所属区域'); + } + else{ + if (cName && Rtsp) { + saveButton.disabled = true; + //发送视频链接接口 + const url = '/api/channel/add'; + const data = {"area":area,"cName":cName,"Rtsp":Rtsp,"cid":cid}; + // 发送 POST 请求 + fetch(url, { + method: 'POST', // 指定请求方法为 POST + headers: { + 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON + }, + body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 + }) + .then(response => response.json()) // 将响应解析为 JSON + .then(data => { + const istatus = data.status; + if(istatus === 0){ + alert(data.msg); // 使用 Modal 显示消息 + // 启用保存按钮 + saveButton.disabled = false; + return; + } + else{ + // 启用保存按钮 + saveButton.disabled = false; + //刷新列表 + fetchChannelData(); + if(itype ==1){ + //添加通道成功 + $('#channelModal').modal('hide'); + alert("添加通道成功!"); // 使用 Modal 显示消息 + } + else if(itype==2){ + //修改通道成功 + currentEditingRow = null; + $('#ChangeC').modal('hide'); + alert("修改通道成功!"); // 使用 Modal 显示消息 + } + } + }) + .catch((error) => { + alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息 + // 启用保存按钮 + saveButton.disabled = false; + return; + }); + } else { + alert('通道名称和RTSP地址不能为空'); + } + } +} + +async function fetchChannelData() { //刷新通道数据 + try { + const response = await fetch(apiEndpoint); + channelData = await response.json(); + channelData_bak = channelData; + renderTable(); //读取通道list接口,刷新表格 + renderPagination(); //刷新分页元素 + renderAreaOptions(); //所属区域下来框 + } catch (error) { + console.error('Error fetching channel data:', error); + } +} + +//关键字查询数据 +async function performSearch() { + try { + const area = document.getElementById('areaSelect').value; + const channelName = document.getElementById('channelNameInput').value; + if(area === "请选择" && channelName===""){ + channelData = channelData_bak; + } + else if(area === "请选择"){ + channelData = []; + channelData_bak.forEach((channel) => { + if(channelName === channel.channel_name){ + channelData.push(channel); + } + }); + } + else if(channelName === ""){ + channelData = []; + channelData_bak.forEach((channel) => { + if(area === channel.area_name){ + channelData.push(channel); + } + }); + } + else{ + channelData = []; + channelData_bak.forEach((channel) => { + if(area === channel.area_name && channelName === channel.channel_name){ + channelData.push(channel); + } + }); + } + // 渲染表格和分页控件 + currentPage = 1; // 重置当前页为第一页 + renderTable(); + renderPagination(); + } catch (error) { + console.error('Error performing search:', error); + } +} + +//刷新表单页面数据 +function renderTable() { + const tableBody = document.getElementById('table-body'); + tableBody.innerHTML = ''; + + const start = (currentPage - 1) * rowsPerPage; + const end = start + rowsPerPage; + const pageData = channelData.slice(start, end); + const surplus_count = rowsPerPage - pageData.length; + let area_name = ""; + pageData.forEach((channel) => { + if(area_name!==channel.area_name){ //这里要求区域名称一样的要在一起 + area_name = channel.area_name; + areaData.push(area_name); + } + const row = document.createElement('tr'); + row.innerHTML = ` + ${channel.ID} + ${channel.area_name} + ${channel.channel_name} + ${channel.ulr} + ${channel.model_name} + + + + + + `; + tableBody.appendChild(row); + row.querySelector('.modify-btn').addEventListener('click', () => modifyChannel(row)); + row.querySelector('.algorithm-btn').addEventListener('click', () => configureAlgorithm(row)); + row.querySelector('.delete-btn').addEventListener('click', () => deleteChannel(row)); + }); +} + +//修改通道信息 +function modifyChannel(row) { +// const cid = row.cells[0].innerText; + const areaName = row.cells[1].innerText; + const channelName = row.cells[2].innerText; + const url = row.cells[3].innerText; + + const area = document.getElementById('areaSelect_CC'); + const CName = document.getElementById('CNameInput_cc'); + const RTSP = document.getElementById('RTSPInput_cc'); + + for(let i=0;i< area.options.length;i++){ + if(area.options[i].value === areaName){ + area.options[i].selected = true; + break; + } + } + CName.value = channelName; + RTSP.value = url; + + currentEditingRow = row; + $('#ChangeC').modal('show'); +} + +//算法配置 -- 点击算法按钮 +function configureAlgorithm(row) { + //获取当前行信息 + currentEditingRow = row; + const cid = row.cells[0].innerText; + //清除数据,若需要的话 + ctx.clearRect(0, 0, canvas.width, canvas.height); //清除左侧绘画和画线信息 + tbody.innerHTML = ''; //清空布防控件数据 + points = []; //清空绘制检测区域 + draw_status = false; + b_img = false; + document.getElementById('but_hzqy').textContent = "绘制区域"; + //开始初始化算法管理模块 + show_channel_img(cid); //显示一帧图片 -- 获取不到图片就是黑画面 + show_channel_model_schedule(cid); //显示结构化数据 + //显示窗口 + $('#MX_M').modal('show'); +} + +//获取一帧图片 +function show_channel_img(cid){ + const data = {"cid":cid}; + fetch('/api/channel/img', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(data => { + if (data.image) { + b_img = true; + img.src = 'data:image/jpeg;base64,' + data.image; + } else { + console.error('Error:', data.error); + } + }) + .catch(error => console.error('Error:', error)); +} + +//图片加载事项 +img.onload = () => { //清除、画图和画线应该分开 + // 设置画布宽高 + backgroundCanvas.width = canvas.width = img.width; + backgroundCanvas.height = canvas.height = img.height; + // 将图片绘制到背景画布上 + backgroundCtx.drawImage(img, 0, 0, img.width, img.height); + // 将背景画布的内容复制到前台画布上 + ctx.drawImage(backgroundCanvas, 0, 0, canvas.width, canvas.height); //绘制画面 +}; + +//开始和重新绘制 +function startDraw(){ + if(!document.getElementById('zdjc').checked){ + alert("请先选择指定区域!"); + return; + } + let but = document.getElementById('but_hzqy'); + if(!draw_status){//开始绘制 + if(points.length >0){ + if (confirm('开始绘制将清除未提交保存的绘制数据,是否继续?')) { + draw_status = true; + points = []; + //按钮文字调整为结束绘制 + but.textContent = '结 束 绘 制'; + // 清除前台画布 + ctx.clearRect(0, 0, canvas.width, canvas.height); + // 将背景画布的内容复制到前台画布上 + ctx.drawImage(backgroundCanvas, 0, 0, canvas.width, canvas.height); + } + } + else{ + draw_status = true; + but.textContent = '结束绘制'; + } + } + else{//结束绘制 + draw_status = false; + but.textContent = '绘制区域'; + } +} + +//单选按钮点击事件处理 +function handleRadioClick(event) { + const selectedRadio = event.target; + console.log('Selected Radio:', selectedRadio.id); + // 根据选中的单选按钮执行相应操作 + if (selectedRadio.id === 'qjjc') { + console.log("points.length",points.length); + // 处理全画面生效的逻辑 + if(points.length>0){ + if (!confirm('已经绘制了检测区域,确认要切换到全画面生效吗?')) { + document.getElementById('zdjc').checked = true; + } + } + console.log('全画面生效'); + } else if (selectedRadio.id === 'zdjc') { + // 处理指定区域的逻辑 + console.log('指定区域'); + } +} + +// 鼠标点击事件处理--动态绘图 +canvas.addEventListener('click', (event) => { + if(draw_status){ + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + + // 获取鼠标相对于canvas的位置 + const x = (event.clientX - rect.left) * scaleX; + const y = (event.clientY - rect.top) * scaleY; + + points.push({ x, y }); + //绘制线条 + drawLines(); + } +}); + +//初始化读取该视频通道与算法配置的相关信息 --- 这里用GET会更加贴切一些 +function show_channel_model_schedule(cid){ + //发送视频链接接口 + const url = '/api/channel/C2M'; + const data = {"cid":cid}; + // 发送 POST 请求 + fetch(url, { + method: 'POST', // 指定请求方法为 POST + headers: { + 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON + }, + body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 + }) + .then(response => response.json()) // 将响应解析为 JSON + .then(data => { + const m_datas = data.m_datas; + const c2m_data = data.c2m_data; + const schedule = data.schedule; + //console.log("m_datas--",m_datas); + //console.log("c2m_data--",c2m_data); + //console.log("schedule--",schedule); + //配置算法下拉清单 + select_datas = ["请选择"]; + m_datas.forEach(option => { + select_datas.push(option.model_name); + }); + set_select_data("model_select",select_datas); + select_str = currentEditingRow.cells[4].innerText; + //检测区域 + if(c2m_data.length >0){ + model_id = c2m_data[0].model_id; + model_name = currentEditingRow.cells[4].innerText; + set_select_selct("model_select",model_name); + check_area = c2m_data[0].check_area + if( check_area == 0){ //全画面生效 + document.getElementById('qjjc').checked = true; + m_polygon = ""; + } + else{//指定区域 + document.getElementById('zdjc').checked = true; + m_polygon = c2m_data[0].polygon; + if(m_polygon !== ""){ //指定区域了,一般是会有数据的。 + const coords = parseCoordStr(m_polygon); + points = coords; + drawLines(); + } + } + console.log("m_polygon",m_polygon); + //阈值 + document.getElementById('zxyz').value = c2m_data[0].conf_thres + document.getElementById('iouyz').value = c2m_data[0].iou_thres + } + + + const days = ['一', '二', '三', '四', '五', '六','日']; + const num_days=['0','1','2','3','4','5','6'] + days.forEach((day, dayIndex) => { + const row = document.createElement('tr'); + const dayCell = document.createElement('th'); + dayCell.textContent = day; + row.appendChild(dayCell); + num_day = num_days[dayIndex] + for (let hour = 0; hour < 24; hour++) { + const cell = document.createElement('td'); + if(schedule.length >0){ + const status = schedule.find(item => item.day === num_day && item.hour === hour); + if (status && status.status === 1) { + cell.classList.add('blocked'); + } else { + cell.classList.add('allowed'); + } + } + else{ + cell.classList.add('blocked'); + } + row.appendChild(cell); + + cell.addEventListener('click', () => { + if (cell.classList.contains('blocked')) { + cell.classList.remove('blocked'); + cell.classList.add('allowed'); + // Update status in the database + //updateStatus(day, hour, 0); + } else { + cell.classList.remove('allowed'); + cell.classList.add('blocked'); + // Update status in the database + //updateStatus(day, hour, 1); + } + }); + } + tbody.appendChild(row); + }); + }) + .catch((error) => { + alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息 + return; + }); +} + +// 将字符串转换为数组 +function parseCoordStr(str) { + return str.match(/\(([^)]+)\)/g).map(pair => { + const [x, y] = pair.replace(/[()]/g, '').split(',').map(Number); + return { x, y }; + }); +} + +// 绘制区域,各点连接 +function drawLines() { + if(b_img){ + // 清除前台画布 + ctx.clearRect(0, 0, canvas.width, canvas.height); + // 将背景画布的内容复制到前台画布上 + ctx.drawImage(backgroundCanvas, 0, 0, canvas.width, canvas.height); + + // 绘制点和线 + ctx.strokeStyle = 'red'; + ctx.lineWidth = 2; + + if (points.length > 0) { + ctx.beginPath(); + ctx.moveTo(points[0].x, points[0].y); + + for (let i = 1; i < points.length; i++) { + ctx.lineTo(points[i].x, points[i].y); + } + + // 连接最后一个点到起点 + ctx.lineTo(points[0].x, points[0].y); + ctx.stroke(); + } + + points.forEach(point => { + ctx.beginPath(); + ctx.arc(point.x, point.y, 5, 0, Math.PI * 2); + ctx.fillStyle = 'red'; + ctx.fill(); + }); + } +} + +//关闭算法配置窗口 +function close_mx_model(){ + if (confirm('确定退出窗口吗?未保存的修改将丢失!')) { + $('#MX_M').modal('hide'); + } +} + +//保存算法配置窗口数据 +function save_mx_model(){ + //? + currentEditingRow =null; +} + +//删除通道 +function deleteChannel(row) { + if (confirm('确定删除此区域吗?')) { + cid = row.cells[0].innerText; + //发送视频链接接口 + const url = '/api/channel/del'; + const data = {"cid":cid}; + // 发送 POST 请求 + fetch(url, { + method: 'POST', // 指定请求方法为 POST + headers: { + 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON + }, + body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 + }) + .then(response => response.json()) // 将响应解析为 JSON + .then(data => { + const istatus = data.status; + if(istatus === 0){ + alert(data.msg); // 使用 Modal 显示消息 + // 启用保存按钮 + saveButton.disabled = false; + return; + } + else{ + // 启用保存按钮 + saveButton.disabled = false; + //刷新列表 + row.remove(); + alert("删除通道成功!"); + } + }) + .catch((error) => { + alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息 + // 启用保存按钮 + saveButton.disabled = false; + return; + }); + + } +} + +//刷新分页标签 +function renderPagination() { + const pagination = document.getElementById('pagination'); + pagination.innerHTML = ''; + + const totalPages = Math.ceil(channelData.length / rowsPerPage); + for (let i = 1; i <= totalPages; i++) { + const pageItem = document.createElement('li'); + pageItem.className = 'page-item' + (i === currentPage ? ' active' : ''); + pageItem.innerHTML = `${i}`; + pageItem.addEventListener('click', (event) => { + event.preventDefault(); + currentPage = i; + renderTable(); + renderPagination(); + }); + pagination.appendChild(pageItem); + } +} + +//刷新区域下拉控件 +function renderAreaOptions() { + const areaSelect = document.getElementById('areaSelect'); + const areaSelect_M = document.getElementById('areaSelect_M') + const areaSelect_CC = document.getElementById('areaSelect_CC') + areaData.forEach(option => { + const optionElement = document.createElement('option'); + optionElement.textContent = option; + areaSelect.appendChild(optionElement); + + const optionElement_m = document.createElement('option'); + optionElement_m.textContent = option; + areaSelect_M.appendChild(optionElement_m); + + const optionElement_cc = document.createElement('option'); + optionElement_cc.textContent = option; + areaSelect_CC.appendChild(optionElement_cc); + }); +} + + + + + diff --git a/web/main/static/resources/scripts/jquery-1.7.1.min.js b/web/main/static/resources/scripts/jquery-1.7.1.min.js deleted file mode 100644 index 198b3ff..0000000 --- a/web/main/static/resources/scripts/jquery-1.7.1.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.7.1 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
    a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
    "+""+"
    ",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
    t
    ",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
    ",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; -f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

    ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
    ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() -{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/web/main/static/resources/scripts/jquery-3.2.1.min.js b/web/main/static/resources/scripts/jquery-3.2.1.min.js deleted file mode 100644 index 1f824ba..0000000 --- a/web/main/static/resources/scripts/jquery-3.2.1.min.js +++ /dev/null @@ -1,14 +0,0 @@ -/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), -a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"
    ","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), -null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + {% block script %}{% endblock %} + + + diff --git a/web/main/templates/channel_manager.html b/web/main/templates/channel_manager.html new file mode 100644 index 0000000..3a53261 --- /dev/null +++ b/web/main/templates/channel_manager.html @@ -0,0 +1,291 @@ +{% extends 'base.html' %} + +{% block title %}ZFBOX{% endblock %} + +{% block style %} +.pagination { + justify-content: flex-end; /* 右对齐 */ + } + .page-item .page-link { + padding: 0.25rem 0.5rem; /* 缩小按钮 */ + font-size: 0.875rem; /* 调整字体大小 */ + } + .btn-group-sm > .btn, .btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + } + .btn-group-sm .btn { + margin-right: 5px; /* 按钮之间的间距 */ + } + .form-group-right h5 { + text-align: right; + margin-bottom: 0; + } + .table-container { + min-height: 400px; /* 设置最小高度,可以根据需要调整 */ + } + + .video-area { + width: 100%; + position: relative; + background-color: #000; + border: 1px solid #ddd; + } + + .video-area::before { + content: ""; + display: block; + padding-top: 75%; /* 4:3 ratio */ + } + + .video-area canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: contain; + } + .schedule-table { + width: 100%; + table-layout: fixed; + } + .schedule-table th, .schedule-table td { + text-align: center; + vertical-align: middle; + cursor: pointer; + border: 1px solid #dee2e6; + padding: 8px; + } + .schedule-table td.allowed { + background-color: white; + } + .schedule-table td.blocked { + background-color: blue; + } + /* 缩小表格行高 */ + .table-sm th, + .table-sm td { + padding: 0.2rem; /* 调整这里的值来改变行高 */ + } + canvas { + border: 1px solid black; + } +{% endblock %} + +{% block content %} +
    +
    +
    所属区域:
    +
    +
    +
    通道名称:
    +
    +
    +
    +
    +
    + + +
    + + + + + + + + + +
    + + + + + + + + + + + + + + +
    ID所属区域通道名称RTSP地址配置算法操作
    + +
    +
    +{% endblock %} + +{% block script %} + + + +{% endblock %} \ No newline at end of file diff --git a/web/main/templates/footer.html b/web/main/templates/footer.html new file mode 100644 index 0000000..52b985e --- /dev/null +++ b/web/main/templates/footer.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + +
    +
    +
    + zfai + © ZFKJ All Rights Reserved +
    + + +
    +
    \ No newline at end of file diff --git a/web/main/templates/header.html b/web/main/templates/header.html new file mode 100644 index 0000000..cf849ef --- /dev/null +++ b/web/main/templates/header.html @@ -0,0 +1,35 @@ +
    + +
    \ No newline at end of file diff --git a/web/main/templates/index_webrtc.html b/web/main/templates/index_webrtc.html index 671d43f..472a586 100644 --- a/web/main/templates/index_webrtc.html +++ b/web/main/templates/index_webrtc.html @@ -1,45 +1,176 @@ - + - WebRTC Video Stream + + + ZFBOX + + + -

    WebRTC Video Stream

    - +
    + +
    +
    +
    +
      +
    • 一区 +
        +
      • 北门通道一
      • +
      • 南门通道二
      • +
      • 通道三
      • +
      +
    • +
    • 二区域 +
        +
      • 通道一
      • +
      +
    • +
    +
    +
    +
    + + +
    +
    +
    Video Stream
    +
    Video Stream
    +
    Video Stream
    +
    Video Stream
    +
    +
    +
    +
    + © 2024 ZFKJ All Rights Reserved +
    + + diff --git a/web/main/templates/login.html b/web/main/templates/login.html new file mode 100644 index 0000000..513e4a8 --- /dev/null +++ b/web/main/templates/login.html @@ -0,0 +1,122 @@ + + + + + + ZFBOX + + + + + + + + + +
    + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + + {% endif %} + {% endwith %} +
    + +

    ZF-AI

    + +
    + + +
    +
    + + +
    + +
    +
    + + +
    + Captcha +
    + + +

    © 2024–2025 ZFKJ All Rights Reserved

    +
    +
    + + + diff --git a/web/main/templates/schedule.html b/web/main/templates/schedule.html new file mode 100644 index 0000000..4981582 --- /dev/null +++ b/web/main/templates/schedule.html @@ -0,0 +1,156 @@ + + + + + + 布防时间计划 + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    小时000102030405060708091011121314151617181920212223
    星期一
    星期二
    星期三
    星期四
    星期五
    星期六
    星期日
    +
    +
    +
    + 允许 +
    +
    +
    + 阻止 +
    +
    +
    + + + diff --git a/web/main/templates/view_main.html b/web/main/templates/view_main.html new file mode 100644 index 0000000..7300e65 --- /dev/null +++ b/web/main/templates/view_main.html @@ -0,0 +1,146 @@ +{% extends 'base.html' %} + +{% block title %}ZFBOX{% endblock %} + +{% block style %} + .nav-scroller { + position: relative; + z-index: 2; + height: 2.75rem; + overflow-y: hidden; + } + + .nav-scroller .nav { + display: flex; + flex-wrap: nowrap; + padding-bottom: 1rem; + margin-top: -1px; + overflow-x: auto; + text-align: center; + white-space: nowrap; + -webkit-overflow-scrolling: touch; + } + + .blue-svg { + color: blue; /* 这将影响所有使用currentColor的fill属性 */ + } + + main { + display: flex; + flex: 1; + overflow: hidden; + } + + .tree-view { + border-right: 1px solid #ddd; + padding: 10px; + overflow-y: auto; + } + + .video-frame { + position: relative; + width: calc(50% - 10px); /* 默认4画面布局,每行2个视频框架 */ + margin: 5px; + float: left; + background-color: #ccc; /* 默认灰色填充 */ + border: 1px solid #000; /* 边框 */ + } + .video-header { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #2c3e50; + color: #fff; + padding: 5px; + } + .video-area { + width: 100%; + padding-bottom: 75%; /* 4:3 比例 */ + background-color: #000; /* 默认灰色填充 */ + position: relative; + border: 1px solid #ddd; /* 视频区域边框 */ + } + + .video-area img { + display: none; /* 初始隐藏 */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + + .video-buttons { + display: flex; + gap: 10px; + } + + .video-buttons button { + background: none; + border: none; + color: white; + cursor: pointer; + } + + .video-buttons button:hover { + color: #f39c12; + } + + .dropdown-menu { + min-width: 100px; + } + + .video-frame img { + width: 100%; + height: auto; + } + #videoGrid.four .video-frame { + width: calc(50% - 10px); /* 每行4个视频框架 */ + } + #videoGrid.eight .video-frame { + width: calc(12.5% - 10px); /* 每行8个视频框架 */ + } + #videoGrid.nine .video-frame { + width: calc(33.33% - 10px); /* 每行3个视频框架,9画面布局 */ + } + .toggle-buttons { + margin-bottom: 5px; + } + .btn-small { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; + } + .error-message { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: red; + background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */ + padding: 10px; + border-radius: 5px; + } +{% endblock %} + +{% block content %} +
    +
    + +
    + +
    +
    + +
    +
    + + +
    +
    +
    +{% endblock %} + +{% block script %} + +{% endblock %} diff --git a/web/main/templates/登录.html b/web/main/templates/登录.html deleted file mode 100644 index ea4ac77..0000000 --- a/web/main/templates/登录.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - 登录 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if error %} -
    {{ error }}
    - {% endif %} -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    -
    -

    智凡BOX

    -
    -
    - - -
    -
    -
    -

    @2024 ZFKJ All Rights Reserved

    -
    -
    - - -
    - - -
    - - -
    -
    -
    -

    用户名:

    -
    -
    - - -
    -
    - -
    - - -
    -
    -
    -

    密码:

    -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    -
    -

    验证码:

    -
    -
    - - -
    -
    -
    -

    登录

    -
    -
    - - -
    - - -
    - - -
    -
    -
    -

    忘记密码

    -
    -
    - - -
    -
    -
    -

    注:通过手机验证码修改密码。

    -
    -
    -
    - - - diff --git a/zfbox.db b/zfbox.db index 920cb4b..bc33ca8 100644 Binary files a/zfbox.db and b/zfbox.db differ