diff --git a/Modules/Md5.py b/Modules/Md5.py new file mode 100644 index 00000000..85fd3cfb --- /dev/null +++ b/Modules/Md5.py @@ -0,0 +1,27 @@ +#-*- coding: utf-8 -*- +import hashlib +import io +from Modules import loging +logging = loging.Error() +def Md5_make(string): + try: + m = hashlib.md5() + m.update(str(string).encode('utf-8')) + md5value = m.hexdigest() + except Exception as e: + logging.error(e) + return md5value + +def Md5_file(path): + try: + m = hashlib.md5() + file = io.FileIO(path, 'r') + bytes = file.read(1024) + while (bytes != b''): + m.update(bytes) + bytes = file.read(1024) + file.close() + md5value = m.hexdigest() + except Exception as e: + logging.error(e) + return md5value \ No newline at end of file diff --git a/Modules/MyForm.py b/Modules/MyForm.py new file mode 100644 index 00000000..29c77c93 --- /dev/null +++ b/Modules/MyForm.py @@ -0,0 +1,248 @@ +#-*- coding: utf-8 -*- +from flask_wtf import Form +from wtforms import StringField,BooleanField,SelectMultipleField,TextAreaField,SelectField,SubmitField,FileField,IntegerField +from wtforms.validators import DataRequired,Length +from flask_wtf.csrf import CsrfProtect +import redis +import datetime +from Modules import tools,db_idc,db_op,loging +from sqlalchemy import distinct,and_,desc +from flask_sqlalchemy import SQLAlchemy +from flask import Flask +app = Flask(__name__) +app.config.from_pyfile('../conf/main.conf') +app.config.from_pyfile('../conf/redis.conf') +CsrfProtect(app) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +logging = loging.Error() +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +rc = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +redis_data = app.config.get('REDIS_DATA') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +class MyForm_input(Form): + text = TextAreaField(validators=[DataRequired()]) + text2 = TextAreaField(validators=[DataRequired()]) + input = StringField('Input', validators=[DataRequired()]) + submit = SubmitField('提交',id='btn1') + +class MyForm_server(Form): + select = SelectField(choices=[('hostname', '主机名'),('ip', 'ip地址'),('sn', 'sn号'),('cid', '机柜'),('status', '使用状态'),('buy_date', '购买日期')],id='select') + show_pages = SelectField(choices=[(15,15),(30,30),(50,50),(70,70),(100,100)],id='show_pages') + text = StringField(validators=[DataRequired()],id='input') + submit = SubmitField('服务器查询',id='btn1') + +class MyForm_publish(Form): + package_url = TextAreaField(validators=[DataRequired()],id='package_url') + package_md5 = StringField('md5', validators=[DataRequired()],id='package_md5') + check_url = StringField('url', validators=[DataRequired()],id='check_url') + package_type = SelectField(choices=[('full','整包'),('part','部分')],id='package_type') + publish_type = SelectField(choices=[('batch','批量'),('step','逐台')],id='publish_type') + restart = SelectField(choices=[('True', '是'), ('False', '否')],id='restart') + execute = SelectField(choices=[('publish', '上线'), ('rollback', '回滚')],id='execute') + gray = SelectField(choices=[(0, '否'), (1, '是')], id='gray') + describe = TextAreaField(validators=[DataRequired()],id='describe') + try: + db_publish = db_op.publish_records + projects = [] + versions = [] + vals = db_publish.query.with_entities(distinct(db_publish.project)).all() + if vals: + projects = [(val[0],val[0]) for val in vals if val] + vals = db_publish.query.with_entities(distinct(db_publish.version)).filter( + db_publish.project == projects[0][0]).order_by(desc(db_publish.version)).all() + if vals: + versions = [(val[0],val[0]) for val in vals] + project = SelectField(choices=projects, id='project') + version = SelectField(choices=versions, id='version') + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + submit = SubmitField('提交',id='btn1') + +class MyFrom_resource_pool(Form): + db_project = db_op.project_list + db_server = db_idc.idc_servers + try: + source_type = SelectField(choices=[('self','自有资源'),('third','第三方资源')],id="source_type") + hosts_add = db_project.query.with_entities(db_project.resource,db_project.app_port,db_project.ip,db_project.ssh_port).filter(db_project.status=='未分配').all() + hosts = [] + for infos in hosts_add: + info = [ str(info) for info in infos] + hosts.append(info) + hosts_add = [(':'.join(info),':'.join(info)) for info in hosts] + hosts_add = SelectField(choices=hosts_add,id='hosts_add') + projects = db_project.query.with_entities(distinct(db_project.project)).all() + Project = SelectField(choices=[(str(project[0]),str(project[0])) for project in projects],id="projects") + submit_query = SubmitField('查询', id='submit_query') + submit_add = SubmitField('增加', id='submit_add') + servers = db_server.query.with_entities(db_server.ip,db_server.ssh_port).filter(and_(db_server.status=='未使用')).all() + servers = [('%s:%s' %info,'%s:%s' %info) for info in servers] + servers = SelectField(choices=servers,id="select_server") + resource = [('tomcat','tomcat'),('php','php'),('nginx','nginx'),('python','python')] + resource = SelectField(choices=resource) + app_port = StringField('app_port', validators=[DataRequired(), Length(1, 15)], id="app_port") + pool_pages = SelectField(choices=[(15, 15), (30, 30), (50, 50), (70, 70), (100, 100)], id='pool_pages') + submit_allot = SubmitField('预配', id='submit_allot') + submit_lcok = SubmitField('加锁', id='submit_lock') + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + db_op.DB.session.remove() + +class MyFrom_third_resource(Form): + hosts = TextAreaField(validators=[DataRequired()]) + submit_recucle = SubmitField('回收', id='submit') + +class MyForm_dns_conf(Form): + select_domain = SelectField(choices=[('xxxx.com','xxxx.com'),('service.xxx','service.xxx'),('sql.xxx','sql.xxx')]) + select_type = SelectField(choices=[('A','A'),('CNAME','CNAME'),('MX','MX'),('TEXT','TEXT'),('NS','NS')]) + select_action = SelectField(choices=[ ('query', '查询'),('add', '新增'), ('modify', '修改'), ('del', '删除')]) + select_sys = SelectField(choices=[('cw', '测外'), ('xs', '线上')]) + field = StringField('field', validators=[DataRequired(), Length(1,64)]) + ip = StringField('ip', validators=[DataRequired(), Length(1, 64)]) + submit = SubmitField('提交',id='btn1') + +class MyForm_assets_manage(Form): + db_idc_id = db_idc.idc_id + text = TextAreaField(validators=[DataRequired()]) + try: + db_values = db_idc_id.query.with_entities(distinct(db_idc_id.aid)).all() + rack = StringField('rack', id='rack') + purch = StringField('purch', validators=[DataRequired(), Length(1, 15)],id="purch") + expird = StringField('expird', validators=[DataRequired(), Length(1, 15)],id="expird") + idrac = StringField('idrac',id='idrac_down') + select_aid = SelectField(choices=[(value[0],value[0]) for value in db_values]) + select_action = SelectField(choices=[('add','上架'),('modify','变更'),('down','下架'),('upload','批量')],id='select_action') + select_device = SelectField(choices=[('server', '服务器'), ('network', '网络设备'), ('store', '存储设备')],id='select_device') + device_type = StringField('device_type',id='devicetype') + fault = BooleanField('维护中', default=False) + old_host = StringField('old_host',id='old_host') + submit = SubmitField('提交',id='btn1') + File = FileField('File') + upload = SubmitField('批量录入') + finally: + db_idc.DB.session.remove() + +class MyForm_apply(Form): + try: + db_permission = db_op.permission + vals = db_permission.query.with_entities(db_permission.authid,db_permission.auth).all() + finally: + db_op.DB.session.remove() + select = SelectField(choices= vals) + submit = SubmitField('提交',id='btn1') + +class MyForm_deploy(Form): + try: + db_project = db_op.project_list + db_third = db_idc.third_resource + db_busi = db_op.business + Resources = [] + resource = db_project.query.with_entities(db_project.resource,db_project.ip,db_project.ssh_port,db_project.app_port).filter(db_project.status == '未分配').all() + for source in resource: + Resources.append([str(sour) for sour in source]) + resource = [(':'.join(source),':'.join(source)) for source in Resources] + busis = db_busi.query.with_entities(db_busi.id,db_busi.business).all() + select_busi = SelectField(choices=[busi for busi in busis]) + domain = StringField() + business = StringField(validators=[DataRequired(), Length(1, 64)]) + describe = StringField(validators=[DataRequired(), Length(1,128)]) + person = StringField() + contact = StringField() + project = StringField(validators=[DataRequired(), Length(1, 64)]) + select_dev = SelectField(choices= [('java','java'),('php','php'),('python','python')]) + select_resource = SelectMultipleField(choices=resource) + area_resource = TextAreaField(validators=[DataRequired()]) + submit = SubmitField('提交') + Thirds = [] + third_vals = db_third.query.with_entities(db_third.resource_type, db_third.ip, db_third.ssh_port,db_third.app_port).filter(db_third.status == '未分配').all() + for third in third_vals: + Thirds.append([str(val) for val in third]) + thirds = [(':'.join(val), ':'.join(val)) for val in Thirds] + select_third = SelectMultipleField(choices=thirds) + department = StringField(validators=[DataRequired()]) + person = StringField(validators=[DataRequired(), Length(4, 64)]) + contact = StringField(validators=[DataRequired(), Length(4, 64)]) + submit_third = SubmitField('提交') + finally: + db_op.DB.session.remove() + db_idc.DB.session.remove() + +class Form_business_bigdata(Form): + dms = [] + for i in range(7): + dm = datetime.datetime.now() - datetime.timedelta(days=i) + dm = dm.strftime('%Y-%m-%d') + dms.append((dm,dm)) + select_date = SelectField(choices= dms,id='business_bigdata_select_date') + +class Form_platform_token(Form): + input = StringField('input_platform', validators=[DataRequired()],id='input_platform') + input_date = StringField('input_date', validators=[DataRequired()]) + select_date = SelectField(choices=[(0,'永不'),(3,'3天'),(7,'7天'),(15,'15天'),(30,'30天'),(90,'90天'),(180,'180天')], id='select_date') + +class Form_resource_report(Form): + select = SelectField(choices=[(30,'近一个月'),(90,'近三个月'),(180,'近六个月'),(360,'近一年内')],id='select') + +class Form_resource_modify(Form): + db_business = db_op.business + business = [] + resource = StringField('input_resource', validators=[DataRequired()],id='resource') + hosts = TextAreaField(validators=[DataRequired()],id='hosts') + app_port = StringField('app_port', validators=[DataRequired()],id='app_port') + source_type = SelectField(choices=[('非集群','非集群'),('集群模式','集群模式')], id='source_type') + action = SelectField(choices=[('add', '新增'), ('del', '删除')], id='action') + try: + business = db_business.query.with_entities(db_business.id,db_business.business).all() + business = [(int(busi[0]),busi[1]) for busi in business] + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + business.insert(0, (0, '--可选择--')) + select_busi = SelectField(choices=business, id='select_busi') + submit = SubmitField('提交') + +class Form_k8s_contexts(Form): + _,contexts,_ = tools.k8s_conf() + select = SelectField(choices=[(context,context) for context in contexts],id='contexts') + +class Form_k8s_deploy(Form): + try: + db_project = db_op.project_list + projects = db_project.query.with_entities(distinct(db_project.project)).all() + projects = SelectField(choices=[(project[0],project[0]) for project in projects],id='projects') + object = StringField('object', validators=[DataRequired()],id='object') + version = StringField('version', validators=[DataRequired()], id='version') + container_port = StringField('container_port', validators=[DataRequired()],id='container_port') + ingress_port = IntegerField('ingress_port', id='ingress_port') + replicas = IntegerField('replicas', validators=[DataRequired()],id='replicas') + dm_name = StringField('dm_name', validators=[DataRequired()],id='dm_name') + domain = StringField('domain', id='domain') + request_cpu = IntegerField('request_cpu',id='request_cpu') + request_mem = IntegerField('request_mem',id='request_mem') + limit_cpu = IntegerField('limit_cpu',id='limit_cpu') + limit_mem = IntegerField('limit_mem',id='limit_mem') + submit = SubmitField('提交', id='btn1') + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + +class Form_k8s_update(Form): + try: + db_k8s_deploy = db_op.k8s_deploy + values = db_k8s_deploy.query.with_entities(distinct(db_k8s_deploy.deployment)).all() + deployment = SelectField(choices=[(val[0],val[0]) for val in values],id='deployment') + version = StringField('version', validators=[DataRequired()], id='version') + replicas = IntegerField('replicas',id='replicas') + submit = SubmitField('提交', id='btn1') + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() diff --git a/Modules/Mysql.py b/Modules/Mysql.py new file mode 100644 index 00000000..67d195b6 --- /dev/null +++ b/Modules/Mysql.py @@ -0,0 +1,39 @@ +#-*- coding: utf-8 -*- +import mysql.connector as mysql +from Modules import loging +from flask import Flask +app = Flask(__name__) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/sql.conf') +logging = loging.Error() +mysql_user = app.config.get('MYSQL_USER') +mysql_password = app.config.get('MYSQL_PASSWORD') +mysql_host = app.config.get('MYSQL_HOST') +mysql_port = app.config.get('MYSQL_PORT') +class MYSQL(object): + def __init__(self,user=mysql_user,password=mysql_password,host=mysql_host,port=mysql_port,db='mysql'): + try: + self.__user = user + self.__password = password + self.__host = host + self.__port = port + self.__db = db + self.cnx = mysql.connect(user=self.__user,password=self.__password,host=self.__host,port=self.__port,db=self.__db) + self.cur = self.cnx.cursor(buffered=True) + except Exception as e: + logging.error(e) + def Run(self,cmd): + try: + self.cur.execute(cmd) + self.cnx.commit() + except Exception as e: + logging.error(e) + else: + try: + return [cu for cu in self.cur] + except: + return [] + def Close(self): + if self.cur: + self.cur.close() + self.cnx.close() \ No newline at end of file diff --git a/Modules/SSH.py b/Modules/SSH.py new file mode 100644 index 00000000..fc8ef4e6 --- /dev/null +++ b/Modules/SSH.py @@ -0,0 +1,46 @@ +#-*- coding: utf-8 -*- +import paramiko +from flask import Flask +app = Flask(__name__) +app.config.from_pyfile('../conf/ssh.conf') +username = app.config.get('USER') +password = app.config.get('SSH_PW') +keyfile = app.config.get('KEY_FILE') +ssh_port = app.config.get('SSH_PORT') +key_type = app.config.get('KEY_TYPE') +from scp import SCPClient +class ssh(object): + def __init__(self,username=username,ip=None,ssh_port=ssh_port,keyfile=keyfile,password=password,key_type=key_type): + self.username = username + self.ip = str(ip) + self.ssh_port= int(ssh_port) + self.keyfile = keyfile + self.password = password + self._ssh = paramiko.SSHClient() + self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + if self.keyfile: + if key_type == 'rsa': + self.key = paramiko.RSAKey.from_private_key_file(self.keyfile) + if key_type == 'dsa' or key_type == 'dss': + self.key = paramiko.DSSKey.from_private_key_file(self.keyfile) + self._ssh.connect(self.ip, self.ssh_port, self.username, pkey=self.key, timeout=5) + else: + if self.username == 'root': + self._ssh.connect(hostname=self.ip,port=self.ssh_port,username=self.username,password=self.password,timeout=5) + def Run(self,cmd): + try: + if isinstance(cmd,str): + stdin, stdout, stderr = self._ssh.exec_command(cmd.strip()) + else: + for Cmd in cmd: + stdin, stdout, stderr = self._ssh.exec_command(Cmd.strip()) + except Exception as e: + return({'stderr':e}) + return {'stdout':stdout.readlines(),'stderr':stderr.readlines()} + def Close(self): + self._ssh.close() + def Scp(self,src,dst): + scp = SCPClient(self._ssh.get_transport()) + stderr = scp.put(src,dst,recursive=True) + if stderr: + return stderr \ No newline at end of file diff --git a/Modules/Task.py b/Modules/Task.py new file mode 100644 index 00000000..5ca808bd --- /dev/null +++ b/Modules/Task.py @@ -0,0 +1,2001 @@ +#-*- coding: utf-8 -*- +from elasticsearch import Elasticsearch +from elasticsearch import helpers +import oss2 +from tcpping import tcpping +import redis +from Modules import check +import time +import datetime +import requests +import socket +from influxdb import InfluxDBClient +from multiprocessing.dummy import Pool as ThreadPool +from Modules import loging,db_op,db_idc,SSH,ip_adress,Mysql,tools,Md5 +from sqlalchemy import distinct,and_,func +from collections import defaultdict +from functools import reduce +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/sql.conf') +app.config.from_pyfile('../conf/task.conf') +app.config.from_pyfile('../conf/es.conf') +app.config.from_pyfile('../conf/assets.conf') +app.config.from_pyfile('../conf/oss.conf') +logging = loging.Error() +DB = SQLAlchemy(app) +HOST = socket.gethostbyname(socket.gethostname()) +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +RC = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +redis_data = app.config.get('REDIS_DATA') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +es_hosts = app.config.get('ES_HOSTS') +es = Elasticsearch(hosts=es_hosts,timeout=60) +influxdb_host = app.config.get('INFLUXDB_HOST') +influxdb_port = app.config.get('INFLUXDB_PORT') +influxdb_user = app.config.get('INFLUXDB_USER') +influxdb_pw = app.config.get('INFLUXDB_PASSWORD') +influxdb_db = app.config.get('INFLUXDB_DB') +Influx_cli = InfluxDBClient(influxdb_host,influxdb_port,influxdb_user,influxdb_pw,influxdb_db) +PHYSICAL_TYPES = app.config.get('PHYSICAL_TYPES') +dt = time.strftime('%m%d',time.localtime()) +TASK_SERVERS = app.config.get('TASK_SERVERS') +oss_id = app.config.get('ID') +oss_key = app.config.get('KEY') +def counts_logs(vals): + try: + def share_counts(host_key,uri_key): + try: + # 统计域名 + RC.hincrby(host_key, host, 1) + # 统计uri + RC.hincrby(uri_key, uri, 1) + # 统计地区及ISP运营商 + except Exception as e: + logging.error(e) + remote_addr,status,host,uri,upstream_addr,response_time,time_t = vals + tm = ':'.join(time_t.split(':')[:-1]) + tm = '%s_%s'%(time.strftime('%Y-%m-%d',time.localtime()),tm) + code_key = 'error_logs_status_%s' % tm + host_key = 'error_logs_domain_%s_%s' % (status, tm) + uri_key = 'error_logs_domain_%s_%s_%s' % (status, host, tm) + total_key = 'total_access_%s' % tm + domain_key = 'domain_counts_%s' % tm + domain_status_key = 'domain_counts_status_%s' % tm + counts_status_key = 'counts_%s:%s_%s' % (host, status, tm) + RC.incr(total_key, 1) + if int(status) < 400: + try: + if int(response_time) >= 1: + if int(response_time) >= 1 and int(response_time) <= 3: + r_time = '1000-3000' + else: + r_time = '3000+' + host_key = 'response_time_domain_%s_%s' % (r_time, tm) + uri_key = 'response_time_domain_%s_%s_%s' % (host, r_time, tm) + # 域名下后端tomcat响应时间统计 + counts_key = 'counts_%s:%s_%s' % (host, r_time, tm) + RC.hincrby(domain_key, '%s:%s' % (host, r_time), 1) + RC.hincrby(counts_key, upstream_addr, 1) + share_counts(host_key, uri_key) + RC.expire(counts_key, 86400) + except Exception as e: + logging.error(e) + + else: + try: + share_counts(host_key, uri_key) + # 统计错误状态码 + RC.hincrby(code_key, status, 1) + # 域名下后端tomcat状态码统计 + RC.hincrby(domain_status_key, '%s:%s' % (host, status), 1) + RC.hincrby(counts_status_key, upstream_addr, 1) + for KEY in (code_key, domain_status_key, counts_status_key): + RC.expire(KEY, 86400) + except Exception as e: + logging.error(e) + for KEY in (host_key, uri_key, total_key, domain_key): + RC.expire(KEY, 86400) + except Exception as e: + logging.error(e) + +def count_es_logs(): + try: + dt = time.strftime('%Y-%m-%d',time.localtime()) + now_date = datetime.datetime.now() + lte_date = now_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + gd = now_date - datetime.timedelta(minutes=1) + tm = gd.strftime('%Y-%m-%d_%H:%M') + gte_date = gd.strftime('%Y-%m-%dT%H:%M:%S+08:00') + gd = gd.strftime('%Y-%m-%dT%H:%M:%SZ') + index = 'logstash-nginx-log-*' + Key = 'api_domain_lists_%s' % dt + web_domain_key = 'web_domain_lists_%s' %dt + domain_list_key = 'domain_lists_%s' %tm + try: + # 获取域名 + body = {'size': 0, + "query": {"bool": {"must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}]}}, + "aggs": {"counts": {"terms": {"field": "host.keyword", "size": 100}}}} + res = es.search(index=index, body=body) + except Exception as e: + logging.error(e) + # 获取指定域名列表 + hosts = RC_CLUSTER.smembers(Key) + for val in res['aggregations']['counts']['buckets']: + host = val['key'] + if host in hosts: + RC_CLUSTER.sadd(web_domain_key,host) + RC_CLUSTER.sadd(domain_list_key,host) + RC_CLUSTER.expire(web_domain_key,864000) + RC_CLUSTER.expire(domain_list_key,3600) + for i in range(len(RC_CLUSTER.smembers(domain_list_key))): + try: + host = RC_CLUSTER.spop(domain_list_key) + if host: + # 获取指定域名接口列表 + Key = 'api_uri_lists_%s_%s' % (host, dt) + uris = RC_CLUSTER.smembers(Key) + # 获取域名下接口 + try: + body = {'size': 0, "query": {"bool": {"must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"match_phrase": {"host": {"query": host}}}]}}, + "aggs": {"counts": {"terms": {"field": "uri.keyword","size":20}}}} + res = es.search(index=index, body=body) + except Exception as e: + logging.error(e) + for val in res['aggregations']['counts']['buckets']: + try: + uri = val['key'] + influx_fields = defaultdict() + if uri in uris: + #域名接口列表 + RC_CLUSTER.sadd('domain_api_lists_%s_%s' %(host,dt),uri) + RC_CLUSTER.expire('domain_api_lists_%s_%s' %(host,dt),864000) + #域名接口pv + RC_CLUSTER.set('domain_api_pv_%s_%s_%s' %(host,uri,tm),val['doc_count']) + RC_CLUSTER.expire('domain_api_pv_%s_%s_%s' % (host, uri, tm), 864000) + #域名总pv + RC_CLUSTER.incr('domain_api_pv_%s_%s' % (host, tm), int(val['doc_count'])) + RC_CLUSTER.expire('domain_api_pv_%s_%s' % (host, tm), 864000) + #记录到influxdb + influx_fields['pv'] = float('%.2f' % float(val['doc_count'])) + # 获取域名下接口状态码统计 + upstream = {'status_4xx': (400, 499), 'status_5xx': (500, 599)} + for k in upstream: + try: + body = {'size': 0, "query": {"bool": {"must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"match_phrase": {"host": {"query": host}}}, + {"match_phrase": {"uri": {"query": uri}}}, + {"range": {"status": {"gte": upstream[k][0], "lte": upstream[k][-1]}}}]}}} + res = es.search(index=index, body=body) + except Exception as e: + logging.error(e) + RC_CLUSTER.hset('domain_api_infos_%s_%s_%s'%(host,uri,tm),k,res['hits']['total']) + #域名维度状态码统计 + RC_CLUSTER.hincrby('domain_api_infos_%s_%s' % (host, tm), k,int(float(res['hits']['total'])*100)) + # 记录到influxdb + influx_fields[k]= float('%.2f' %float(res['hits']['total'])) + # 获取域名下接口响应时间 + upstream = {'resp_100': 0.1, 'resp_200': 0.2, 'resp_500': 0.5, 'resp_1000': 1} + for k in upstream: + try: + body = {'size': 0, "query": { + "bool": { + "must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"match_phrase": {"host": {"query": host}}}, + {"match_phrase": {"uri": {"query": uri}}}, + {"range": {"upstream_response_time": {"gte": upstream[k]}}}]}, }} + if k == 'resp_100': + body = {'size': 0, "query": { + "bool": { + "must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"match_phrase": {"host": {"query": host}}}, + {"match_phrase": {"uri": {"query": uri}}}, + {"range": {"upstream_response_time": {"gte": upstream[k]}}}]}, }, + "aggs": { + "avg_resp": { + "avg": {"field": "upstream_response_time"} + } + }} + res = es.search(index=index, body=body) + except Exception as e: + logging.error(e) + # 获取域名接口下平均延时响应时间 + if k == 'resp_100': + avg_val = res['aggregations']['avg_resp']['value'] + if isinstance(avg_val, float): + RC_CLUSTER.hset('domain_api_infos_%s_%s_%s' % (host, uri, tm), 'avg_resp',float('%.2f' % avg_val)) + # 域名维度接口响应时间统计 + RC_CLUSTER.hincrby('domain_api_infos_%s_%s' % (host, tm), 'avg_resp',int(float(avg_val)*100)) + # 记录到influxdb + influx_fields['avg_resp'] = float('%.2f' % float(avg_val)) + #获取接口统计数据 + RC_CLUSTER.hset('domain_api_infos_%s_%s_%s'%(host,uri,tm),k,res['hits']['total']) + # 域名维度接口响应时间统计 + RC_CLUSTER.hincrby('domain_api_infos_%s_%s' % (host, tm), k,int(float(res['hits']['total'])*100)) + # 记录到influxdb + influx_fields[k] = float('%.2f' %float(res['hits']['total'])) + RC_CLUSTER.expire('domain_api_infos_%s_%s_%s' % (host, uri, tm), 864000) + RC_CLUSTER.expire('domain_api_infos_%s_%s' % (host, tm), 864000) + except Exception as e: + logging.error(e) + continue + if influx_fields: + try: + #写入influxdb数据库 + json_body = [{"measurement": "analysis_logs","tags": {"host":host,"uri":uri},"fields": influx_fields,"time":gd}] + Influx_cli.write_points(json_body) + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + +@check.proce_lock +def get_server_info(): + db_store = db_idc.idc_store + db_server = db_idc.idc_servers + db_idc_id = db_idc.idc_id + server_val = db_server.query.with_entities(db_server.ip,db_server.ssh_port).filter(and_(db_server.status !='维护中',db_server.comment !='跳过')).all() + #获取阿里云存储情况 + auth = oss2.Auth(oss_id, oss_key) + service = oss2.Service(auth, 'http://oss.aliyuncs.com') + Buckets = [b.name for b in oss2.BucketIterator(service)] + #获取idc id + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid=='阿里云',db_idc_id.cid=='OSS')).all() + if idc_id: + store_val = db_store.query.with_entities(db_store.type).filter(db_store.idc_id == idc_id[0][0]).all() + if store_val: + store_val = [val[0] for val in store_val] + # bucket信息写入数据库 + add_vals = set(Buckets) - set(store_val) + if add_vals: + for bucket_name in add_vals: + bucket = oss2.Bucket(auth, 'http://oss.aliyuncs.com', bucket_name) + bucket_info = bucket.get_bucket_info() + c = db_store(idc_id=idc_id[0][0],type=bucket_name,ip='http://oss.aliyuncs.com',purch_date=bucket_info.creation_date.split('T')[0],expird_date='',status='使用中',comment='') + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + # 删除数据库中bucket信息 + del_vals = set(store_val)-set(Buckets) + if del_vals: + for bucket_name in del_vals: + c = db_store.query.filter(db_store.type==bucket_name).all() + for v in c: + db_idc.DB.session.delete(v) + db_idc.DB.session.commit() + def get_info(info): + sip,port= info + sip = sip.strip() + if tcpping(host=sip, port=port, timeout=3): + try: + Ssh = SSH.ssh(ip=sip, ssh_port=port) + Ssh.Run('yum -y install dmidecode unzip zip md5sum') + Ssh.Close() + except: + pass + else: + try: + Ssh = SSH.ssh(ip=sip, ssh_port=port) + # 获取系统磁盘容量 + try: + disk_size = '' + disks = [] + values = Ssh.Run("cat /proc/partitions") + for line in values['stdout']: + line = line.strip('\n') + if 'sd' in line or 'vd' in line or 'xvd' in line: + try: + if int(line[-1]) >= 0 and 'xvd' not in line: + continue + except: + disks.append(int(round(float(line.split()[-2]) / 1000 / 1000))) + if 'xvd' in line: + try: + if int(line[-1]) >= 0: + disks.append(int(round(float(line.split()[-2]) / 1000 / 1000))) + except: + continue + disk_count = len(disks) + if disks: + disk_size = '%sG' % disks[0] + if disk_count > 1: + disk_size = '%sG' % reduce(lambda x, y: x + y, disks) + except Exception as e: + logging.error(e) + + # 获取IP信息 + try: + ips = '' + IPS = [] + cmd = "ip a|grep 'inet '" + values = Ssh.Run(cmd) + for line in values['stdout']: + IPS.append(line.strip('\n').split()[1].split('/')[0]) + if '127.0.0.1' in IPS: + IPS.remove('127.0.0.1') + if sip in IPS: + IPS.remove(sip) + if IPS: + if len(IPS) == 1: + ips = '%s;'%IPS[0] + else: + ips ='%s;'%';'.join(IPS[:4]) + except Exception as e: + logging.error(e) + + #获取内存信息 + try: + mem_size = '' + cmd = "free -m" + values = Ssh.Run(cmd) + for line in values['stdout']: + if 'Mem:' in line.strip('\n'): + mem_size ='{0}G'.format(int(round(float(line.split()[1])/1000))) + break + except Exception as e: + logging.error(e) + + # 获取CPU信息 + try: + datas = [] + for cmd in ("cat /proc/cpuinfo|grep 'model name'|sed -n '1p' ", "cat /proc/cpuinfo|grep 'processor'|wc -l"): + values = Ssh.Run(cmd) + if values['stdout']: + values = values['stdout'][0].strip('\n') + if ':' in values: + values = values.split(':')[-1].strip() + datas.append(values) + else: + datas.append('') + db_server.query.filter(and_(db_server.ip==sip,db_server.ssh_port==port)).update({db_server.s_ip:ips,db_server.cpu_info:datas[0],db_server.cpu_core:datas[1], + db_server.mem:mem_size,db_server.disk_count:disk_count,db_server.disk_size:disk_size}) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + + # 获取系统信息 + try: + system = '' + values = Ssh.Run("cat /etc/redhat-release") + if values['stdout']: + system = values['stdout'][0].strip() + db_server.query.filter(and_(db_server.ip==sip,db_server.ssh_port==port)).update({db_server.system:system}) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + + # 获取厂家信息 + try: + infos = [] + for cmd in ('/usr/sbin/dmidecode -s system-serial-number', '/usr/sbin/dmidecode -s system-product-name', '/usr/sbin/dmidecode -s system-manufacturer'): + values = Ssh.Run(cmd) + if values['stdout']: + infos.append(values['stdout'][0].strip('\n')) + else: + infos.append('Not Specified') + if infos[-1] in PHYSICAL_TYPES: + infos.append('physical') + else: + infos.append('vm') + db_server.query.filter(and_(db_server.ip==sip,db_server.ssh_port==port)).update({db_server.sn:infos[0],db_server.productname:infos[1], + db_server.manufacturer:infos[2],db_server.host_type:infos[3]}) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + finally: + Ssh.Close() + try: + loging.write("start get server infos ......") + if server_val: + pool = ThreadPool(5) + pool.map(get_info,server_val) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + finally: + loging.write("get server infos complete!") + db_idc.DB.session.remove() + +@check.proce_lock +def auto_discovery(): + try: + loging.write("start %s ......" % auto_discovery.__name__) + db_ips = db_idc.resource_ip + db_idc_id = db_idc.idc_id + db_third = db_idc.third_resource + db_project = db_op.project_list + db_zabbix = db_idc.zabbix_info + db_server = db_idc.idc_servers + aids = db_ips.query.with_entities(db_ips.aid,db_ips.network).all() + aids = {aid[-1]:aid[0] for aid in aids} + exist_infos = db_server.query.with_entities(db_server.hostname,db_server.ip,db_server.ssh_port).all() + exist_hostname = set([info[0] for info in exist_infos]) + exist_hosts = ["%s:%s"%(info[1],info[2]) for info in exist_infos] + hosts_list = tools.get_server_list() + dt = time.strftime('%Y-%m-%d', time.localtime()) + server_ensure = [] + Key = "op_disconnet_assets_count" + RC.delete(Key) + if RC.exists('server_ensure'): + server_ensure = RC.smembers('server_ensure') + def discovery(info): + ip, ssh_port, hostname,idc = info + aid = aids[idc] + if hostname not in exist_hostname: + if "%s:%s" %(ip,ssh_port) in exist_hosts: + db_server.query.filter(and_(db_server.ip == ip,db_server.ssh_port == ssh_port)).update({db_server.hostname:hostname}) + db_idc.DB.session.commit() + else: + if tcpping(host=ip, port=ssh_port, timeout=15): + try: + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + except: + if '%s:%s' % (ip, ssh_port) not in server_ensure: + RC.hset('ssh_login_fault_%s'%dt, '%s:%s' % (ip, ssh_port), aid) + RC.sadd(Key, hostname) + else: + val = None + Ssh.Run("yum -y install dmidecode iproute") + #自动识别变更新ip地址 + sn_val = Ssh.Run('/usr/sbin/dmidecode -s system-serial-number') + if sn_val['stdout']: + if 'Specified' not in sn_val['stdout']: + sn = sn_val['stdout'] + val = db_server.query.with_entities(db_server.ip,db_server.ssh_port).filter(db_server.sn==sn).all() + if val: + sip,ssh_port = val[0] + #修改资产表 + db_server.query.filter(db_server.sn==sn).update({db_server.ip:ip}) + db_idc.DB.session.commit() + #修改第三方资源表 + db_third.query.filter(and_(db_third.ip == sip,db_third.ssh_port == ssh_port)).update({db_third.ip:ip}) + db_idc.DB.session.commit() + #修改自有服务资源表 + db_project.query.filter(and_(db_project.ip == sip,db_project.ssh_port == ssh_port)).update({db_project.ip:ip}) + db_op.DB.session.commit() + #修改zabbix信息表 + db_zabbix.query.filter(and_(db_zabbix.ip == sip,db_zabbix.ssh_port == ssh_port)).update({db_zabbix.ip:ip}) + db_idc.DB.session.commit() + if not val: + dmi_val = Ssh.Run("/usr/sbin/dmidecode -s system-manufacturer") + if dmi_val['stdout']: + if dmi_val['stdout'][0].strip('\n') in PHYSICAL_TYPES: + v = db_server(idc_id=1053,ip=ip, ssh_port=ssh_port, s_ip='', host_type='physical', hostname=hostname, sn='', + manufacturer='', productname='', + system='', cpu_info='', cpu_core=0, mem='',disk_count=0, disk_size='', idrac='', + purch_date='', + expird_date='', status='新发现', comment='') + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + else: + #判断机房机柜信息 + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid==aid,db_idc_id.cid == 'KVM')).all() + if not idc_id: + c = db_idc_id(aid=aid,cid='KVM') + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid == aid, db_idc_id.cid == 'KVM')).all() + idc_id = int(idc_id[0][0]) + v = db_server(idc_id=idc_id,ip=ip,ssh_port=ssh_port,s_ip='',host_type='vm',hostname=hostname,sn='',manufacturer='',productname='', + system='',cpu_info='',cpu_core=0,mem='',disk_count=0,disk_size='',idrac='',purch_date=dt,expird_date='2999-12-12',status='使用中',comment='') + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + for cmd in ("yum -y install dmidecode","chmod +s /usr/sbin/dmidecode"): + Ssh.Run(cmd) + loging.write("auto discovery new server %s %s" %(ip,hostname)) + RC.hdel('ssh_login_fault_%s'%dt, '%s:%s' % (ip, ssh_port)) + RC.hdel('ssh_port_fault_%s'%dt, '%s:%s' % (ip, ssh_port)) + else: + if '%s:%s' % (ip, ssh_port) not in server_ensure: + RC.hset('ssh_port_fault_%s'%dt, '%s:%s' % (ip, ssh_port),aid) + RC.sadd(Key,hostname) + if hosts_list: + pool = ThreadPool(10) + pool.map(discovery,hosts_list) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + finally: + loging.write("%s complete" %auto_discovery.__name__) + db_idc.DB.session.remove() + db_op.DB.session.remove() + +@check.proce_lock +def get_app_service(): + def app_service(info): + apps = defaultdict() + app_ports = [] + ip, ssh_port = info + # 判断ssh端口是否可连通 + if tcpping(host=ip, port=ssh_port, timeout=3): + #获取应用监听端口 + try: + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + try: + pf = Ssh.Run("ss -l -t -4 -n -p")['stdout'] + for line in pf: + if '*:*' in line: + line = line.strip().split() + try: + app = line[-1].split('"')[1] + pid = line[-1].split('"')[2].split(',')[1].split('=')[-1] + app_port = line[3].split(':')[-1] + except: + continue + if app and pid and app_port: + try: + app_port = int(app_port) + except: + continue + else: + apps['%s:%s:%s' %(app,pid,app_port)] = app_port + # 单独获取lvs,keepalived + out_vals = Ssh.Run("ipvsadm -ln|wc -l") + if out_vals['stdout']: + if int(out_vals['stdout'][0]) > 3: + app_ports.append('lvs:0') + out_vals = Ssh.Run("ps -ef|grep keepalived|grep -v grep") + if out_vals['stdout']: + for line in out_vals['stdout']: + if 'keepalived' in line: + app_ports.append('keepalived:0') + except: + pass + else: + if apps: + #删除已下架自有服务 + vals = db_project.query.with_entities(db_project.app_port).filter(and_(db_project.ip == ip, db_project.ssh_port == ssh_port,db_project.resource.in_(('tomcat',)))).all() + if vals: + ports = set([int(apps[app]) for app in apps]) + for val in vals: + if int(val[0]) not in ports: + v = db_project.query.filter(and_(db_project.ip == ip, db_project.ssh_port == ssh_port,db_project.app_port==int(val[0]))).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + loging.write('delete self service %s %s' % (ip,val[0])) + #清洗应用服务数据 + for info in apps: + try: + app = info.split(':')[0] + app_port = int(apps[info]) + #java单独处理 + if app =='java': + #判断是不是自有资源服务 + val = db_project.query.filter(and_(db_project.ip == ip, db_project.ssh_port == ssh_port,db_project.app_port == app_port)).all() + if not val: + out_vals = Ssh.Run("ps -ef|grep java|grep -v grep") + for line in out_vals['stdout']: + for java_app in cluster_apps: + if java_app in line: + if java_app == 'KFK': + java_app = 'kafka' + if java_app == 'ZK': + java_app = 'zookeeper' + app_ports.append('%s:0'%java_app) + if app_port in RPCS.keys(): + app_ports.append('%s:%s' %(RPCS[app_port],app_port)) + #发现新的自有服务资源写入数据库 + val = db_project.query.with_entities(db_project.resource,db_project.project,db_project.domain,db_project.business_id).filter(and_(db_project.app_port == app_port,db_project.resource.in_(('tomcat',)))).first() + if val: + resource, project, domain, business_id = val + loging.write('find new self service %s %s %s' %(ip,app_port,project)) + c = db_project(resource=resource,project=project,domain=domain,ip=ip,ssh_port=ssh_port,app_port=app_port,business_id=business_id,sys_args='java',env='生产',gray='',status='使用中',update_date=time.strftime('%Y-%m-%d',time.localtime())) + db_op.DB.session.add(c) + db_op.DB.session.commit() + + else: + if '-' in app: + app = app.split('-')[0] + if 'haprox' in app: + app = 'haproxy' + if 'kube' in app: + app = 'Kubernetes' + if 'memcache' in app: + app = 'memcached' + if 'rain_rate' in app: + app = 'rain_rate' + if 'Accuweather' in app: + app = 'Accuweather' + app = app.replace('(', '') + #判断是否在统计应用列表里 + if app in in_apps: + if app in cluster_apps: + app_port = 0 + if 'nginx' in app: + app_port = 80 + app_ports.append("%s:%s" %(app,app_port)) + else: + RC_CLUSTER.sadd('op_exclude_apps',"%s:%s" %(app,app_port)) + except Exception as e: + logging.error(e) + #写入第三方应用资源表 + if app_ports: + application = db_third.query.with_entities(db_third.resource_type,db_third.app_port).filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port)).all() + application = ['%s:%s' % (val[0], val[1]) for val in application] + try: + # 删除第三方应用服务列表,不删除手工录入信息 + for val in set(application): + if val not in set(app_ports): + if ':' in val: + app,app_port = val.split(':') + infos = db_third.query.with_entities(db_third.id,db_third.update_date).filter(and_(db_third.ip == ip,db_third.resource_type==app,db_third.app_port==int(app_port))).all() + if infos: + if '0000-00-00' not in infos[0]: + third_id = infos[0][0] + # 清除项目资源表 + c = db_project_third.query.filter(db_project_third.third_id == int(third_id)).all() + if c: + for v in c: + db_op.DB.session.delete(v) + db_op.DB.session.commit() + #清除第三方资源表 + c = db_third.query.filter(db_third.id == int(third_id)).all() + if c: + for v in c: + db_idc.DB.session.delete(v) + db_idc.DB.session.commit() + loging.write('del app service %s %s %s ' % (ip, app, app_port)) + except Exception as e: + logging.error(e) + try: + #新增第三方资源信息 + for val in set(app_ports): + if val not in set(application): + if ':' in val: + app, app_port = val.split(':') + RC.sadd('third_app_counts', app) + cluster_type = '非集群' + if app in cluster_apps: + cluster_type = '集群模式' + if app == 'hadoop': + v = db_third(resource_type=app, cluster_type=cluster_type, ip=ip, + ssh_port=ssh_port, app_port=int(app_port),busi_id=0, department='', + person='', contact='', status='使用中',update_date=time.strftime('%Y-%m-%d',time.localtime())) + else: + v = db_third(resource_type=app, cluster_type=cluster_type, ip=ip, + ssh_port=ssh_port, app_port=int(app_port), department='TPD',busi_id=0, + person='章汉龙', contact='18901053089', status='使用中',update_date=time.strftime('%Y-%m-%d',time.localtime())) + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + loging.write('add new app service %s %s %s' % (app, ip, app_port)) + # 新发现数据库写入mysql信息表 + if app == 'mysqld': + db_v = db_mysqld(ip=ip, port=int(app_port), db='', master='否',slave='否', + Master_Host='',Master_Port='',Master_User='') + db_idc.DB.session.add(db_v) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + except: + pass + else: + Ssh.Close() + try: + loging.write("start run %s ......" %get_app_service.__name__) + db_server = db_idc.idc_servers + db_third = db_idc.third_resource + db_project = db_op.project_list + db_project_third = db_op.project_third + db_mysqld = db_idc.idc_mysqldb + RPCS = {9003:'weather-rpc', 9103:'location-rpc',9012:'sns-rpc',8647:'siriasis-rpc',8646:'sharebg-rpc',8648:'running-rpc',8649:'allergy-rpc'} + cluster_apps = ('KFK', 'hadoop', 'cachecloud', 'elasticsearch', 'ZK', 'kafka', 'zookeeper','codis','Kubernetes','mongod','haproxy','lvs','keepalived','docker') + in_apps = ['redis','nginx','mysqld','zookeeper','hadoop','elasticsearch','kafka','Kubernetes','codis','mongod','haproxy','docker','keepalived','rain_rate','searchd', + 'cachecloud','java','lvs','memcached','Accuweather','influxd','etcd'] + in_apps.extend(RPCS.values()) + infos = db_server.query.with_entities(db_server.ip,db_server.ssh_port).filter(and_(db_server.status !='维护中',db_server.comment != '跳过')).all() + if infos: + pool = ThreadPool(10) + pool.map(app_service,infos) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + finally: + loging.write("%s complete!" %get_app_service.__name__) + db_idc.DB.session.remove() + db_op.DB.session.remove() + +@check.proce_lock +def get_project_app(): + def get_third_app(app_list): + try: + project_id, project, ip, ssh_port, app_port = app_list + access_resource = [] + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + Ssh.Run("yum -y install iproute") + #获取应用活动连接 + values = Ssh.Run("ss -l -t -4 -n -p|grep {0}".format(app_port)) + try: + if values['stdout']: + app_pid = values['stdout'][0].strip().split()[-1].split(',')[1] + if app_pid: + values = Ssh.Run("lsof -i -n -P|grep EST|grep {0}".format(app_pid)) + if values['stdout']: + for line in values['stdout']: + if '->' in line: + line = line.split()[8] + if '->' in line: + access_resource.append(line.split('->')[-1]) + except Exception as e: + logging.error(e) + Ssh.Close() + except: + pass + else: + if access_resource: + for info in set(access_resource): + try: + idc_ids = [] + third_ip, third_port = info.split(':') + if '127.0.0.1' in third_ip: + third_ip = ip + # 获取机房机柜ID + vals = db_servers.query.with_entities(db_servers.idc_id).filter(and_(db_servers.ip==ip,db_servers.ssh_port==ssh_port)).all() + if vals: + idc_id = vals[0][0] + # 获取机房信息 + vals = db_idc_id.query.with_entities(db_idc_id.aid).filter(db_idc_id.id==idc_id).all() + if vals: + aid = vals[0][0] + # 获取机房下所有机柜ID + vals = db_idc_id.query.with_entities(db_idc_id.id).filter(db_idc_id.aid==aid).all() + if vals: + idc_ids = [val[0] for val in vals] + # 获取web服务器机房机柜信息 + vals = db_servers.query.with_entities(db_servers.ip,db_servers.ssh_port).filter(and_(db_servers.ip==third_ip,db_servers.idc_id.in_(tuple(idc_ids)))).all() + if vals: + third_ip, third_ssh_port = vals[0] + else: + vals = db_servers.query.with_entities(db_servers.ip, db_servers.ssh_port).filter(and_(db_servers.s_ip.like('%s{0};%'.format(third_ip)), db_servers.idc_id.in_(tuple(idc_ids)))).all() + if vals: + third_ip, third_ssh_port = vals[0] + else: + third_ip = third_ssh_port = None + # 获取远程资源应用名称 + if third_ip and third_ssh_port: + third_id = None + # 查找应用服务ID + vals = db_third.query.with_entities(db_third.id).filter(and_(db_third.ip==third_ip,db_third.ssh_port==third_ssh_port,db_third.app_port==third_port)).all() + if vals: + third_id = vals[0][0] + else: + vals = db_third.query.with_entities(db_third.id).filter(and_(db_third.ip == third_ip, db_third.ssh_port == third_ssh_port,db_third.app_port=='')).all() + if vals: + if len(vals[0]) == 1: + third_id = vals[0][0] + if third_id: + vals = db_project_third.query.filter(and_(db_project_third.project == project,db_project_third.project_id == project_id,db_project_third.third_id == third_id)).all() + if not vals: + v = db_project_third(project=project, project_id=project_id,third_id=third_id) + db_op.DB.session.add(v) + db_op.DB.session.commit() + loging.write('add project app_service %s %s %s' % (project, project_id, third_id)) + except Exception as e: + logging.error(e) + try: + db_servers = db_idc.idc_servers + db_third = db_idc.third_resource + db_idc_id = db_idc.idc_id + db_project_third = db_op.project_third + db_project = db_op.project_list + app_lists = db_project.query.with_entities(db_project.id,db_project.project,db_project.ip,db_project.ssh_port,db_project.app_port).filter(db_project.resource.in_(('tomcat',))).all() + if app_lists: + pool = ThreadPool(10) + pool.map(get_third_app,app_lists) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + db_idc.DB.session.remove() + +@check.proce_lock +def server_per(): + def get_per(host_info): + host,ssh_port,hostname,cpu_core= host_info + icmpping = 0 + disk_io = 0 + mem_use = 0 + cpu_load = 0 + openfile = 0 + if tcpping(host=host, port=ssh_port, timeout=5): + try: + icmpping = 1 + Ssh = SSH.ssh(ip=host, ssh_port=ssh_port) + except: + pass + else: + ssh_values = Ssh.Run('cat /proc/sys/fs/file-nr') + if ssh_values['stdout']: + openfile = int(ssh_values['stdout'][0].split('\t')[0]) + ssh_values = Ssh.Run('w') + if ssh_values['stdout']: + cpu_load = int(int(float(ssh_values['stdout'][0].split(',')[-2]))/int(cpu_core)*100) + ssh_values = Ssh.Run('free -g') + if ssh_values['stdout']: + mem_use = int(float(ssh_values['stdout'][1].split()[2])/float(ssh_values['stdout'][1].split()[1])*100) + ssh_values = Ssh.Run('iostat -c') + if ssh_values['stdout']: + disk_io = float(ssh_values['stdout'][3].split()[3]) + Ssh.Close() + #写入数据库 + try: + tm = time.strftime('%Y-%m-%d %H:%M:%S') + val = db_zabbix.query.filter(and_(db_zabbix.ip==host,db_zabbix.ssh_port==ssh_port,db_zabbix.hostname==hostname)).all() + if val: + db_zabbix.query.filter(and_(db_zabbix.ip == host, db_zabbix.ssh_port == ssh_port, db_zabbix.hostname == hostname)).update({db_zabbix.icmpping:icmpping,db_zabbix.cpu_load:cpu_load, + db_zabbix.mem_use:mem_use,db_zabbix.disk_io:disk_io,db_zabbix.openfile:openfile,db_zabbix.update_time:tm}) + db_idc.DB.session.commit() + else: + v=db_zabbix(ip=host,ssh_port=ssh_port,hostname=hostname,icmpping=icmpping,cpu_load=cpu_load, + mem_use=mem_use,disk_io=disk_io,openfile=openfile,disk_path='',network='',update_time=tm) + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + if icmpping == 1: + now_date = datetime.datetime.now() + dm = now_date.strftime('%Y-%m-%dT%H:%M:00Z') + # 写入influxdb数据库 + json_body = [{"measurement": "server_infos", "tags": {"ip": host, "ssh_port": ssh_port,"hostname":hostname}, + "fields": {'cpu_load':cpu_load,'mem_use':mem_use,'openfile':openfile}, "time": dm}] + Influx_cli.write_points(json_body) + except Exception as e: + logging.error(e) + + #获取服务器在zabbix的磁盘和网络信息 + try: + Key = "zabbix_history_%s_%s" % (host, ssh_port) + val = db_zabbix.query.with_entities(db_zabbix.disk_path,db_zabbix.network).filter(and_(db_zabbix.ip == host, db_zabbix.ssh_port == ssh_port, db_zabbix.hostname == hostname)).all() + if val: + if None not in val[0]: + disks,networks = val[0] + if ',' in disks and ',' in networks: + for disk_path in disks.split(','): + try: + v = zabi.zabbix_history(hostname, 'vfs.fs.size[%s,pfree]' % disk_path) + if not v: + v = zabi.zabbix_history(host, 'vfs.fs.size[%s,pfree]' % disk_path) + RC.hset(Key, disk_path, 100 - int(float(v))) + except: + continue + for network in networks.split(','): + try: + v = zabi.zabbix_history(hostname,'net.if.in[%s]' %network) + if not v: + v = zabi.zabbix_history(host,'net.if.in[%s]' %network) + RC.hset(Key, 'in_%s' %network, int(float(v)/ 1000 / 1000)) + v = zabi.zabbix_history(hostname,'net.if.out[%s]'%network) + if not v: + v = zabi.zabbix_history(host,'net.if.out[%s]' %network) + RC.hset(Key, 'out_%s' % network,int(float(v) / 1000 / 1000)) + except: + continue + RC.expire(Key, 86400) + except Exception as e: + logging.error(e) + try: + zabi = tools.zabbix_api() + db_server = db_idc.idc_servers + db_zabbix = db_idc.zabbix_info + host_infos = db_server.query.with_entities(db_server.ip, db_server.ssh_port, db_server.hostname,db_server.cpu_core).filter(and_(db_server.status !='维护中',db_server.comment !='跳过')).all() + Influx_cli = InfluxDBClient(influxdb_host, influxdb_port, influxdb_user, influxdb_pw, 'zabbix_infos') + if host_infos: + host_infos = [info for info in host_infos if info] + pool = ThreadPool(5) + pool.map(get_per, host_infos) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + finally: + zabi.zabbix_logout() + db_idc.DB.session.remove() + +@check.proce_lock +def zabbix_triggers(): + zabi = tools.zabbix_api() + tm = time.strftime('%H:%M', time.localtime()) + Key = 'zabbix_triggers_%s' % tm + IDS_Key = 'zabbix_ids_%s' % tm + try: + RC.delete(Key) + ID_ensure = [] + priority = {'2': '警告','3': '严重', '4': '危险', '5': '灾难'} + # 获取zabbix报警信息 + z_triggers = zabi.zapi.trigger.get(only_true=1, skipDependent=1, monitored=1, active=1, + output=["triggerid", "description", "priority"], + filter={"value": 1}, sortfield="priority", sortorder="DESC", expandDescription=1, + withLastEventUnacknowledged=1) + if RC.exists('zabbix_ensure'): + ID_ensure = RC.smembers('zabbix_ensure') + for result in z_triggers: + try: + if str(int(result['priority'])) in priority.keys(): + try: + ID = Md5.Md5_make(result['description']) + except Exception as e: + logging.error(e) + try: + #完整报警列表 + RC.sadd(IDS_Key,ID) + #展示未确认的报警 + if ID not in ID_ensure: + RC.hset(Key,ID,result['description']) + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + continue + #已确认报警是否已恢复 + if RC.exists(IDS_Key): + for ID in ID_ensure: + if ID not in RC.smembers(IDS_Key): + RC.srem('zabbix_ensure',ID) + #获取天气信息 + th = time.strftime('%H',time.localtime()) + rth = RC_CLUSTER.get('op_get_weather_value') + if rth != th: + RC_CLUSTER.set('op_get_weather_value', th) + try: + url = "http://t.weather.sojson.com/api/weather/city/101010100" + f = requests.get(url) + INFOS = f.json() + city = INFOS['cityInfo']['city'] + Key = 'weather_inofs' + RC_CLUSTER.hset(Key,'city',city) + DATAS = INFOS['data'] + wendu = DATAS['wendu'] + RC_CLUSTER.hset(Key, 'wendu', wendu) + quality = DATAS['quality'] + RC_CLUSTER.hset(Key, 'quality', quality) + forecast = DATAS['forecast'][1] + type = forecast['type'] + RC_CLUSTER.hset(Key, 'type', type) + high = forecast['high'].split()[-1] + RC_CLUSTER.hset(Key, 'high', high) + RC_CLUSTER.expire(Key,86400) + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + finally: + RC.expire(Key,3600) + RC.expire(IDS_Key,3600) + zabi.zabbix_logout() + +@check.proce_lock +def zabbix_disk_network(): + def get_zabbix(infos): + disk_paths = [] + networks = [] + results = zabi.zapi.host.get(filter={"host": [infos[-1]]}) + if not results: + results = zabi.zapi.host.get(filter={"host": [infos[0]]}) + if results: + hostid = results[0]['hostid'] + results = zabi.zapi.item.get(hostids=hostid, output=["itemids", 'key_']) + if results: + for result in results: + itemid = result['itemid'] + for history in (0, 1, 2, 3, 4): + vals = zabi.zapi.history.get(history=history, itemids=itemid, limit=1, sortfield='clock',sortorder="DESC") + if vals: + if 'vfs.fs.size' in result['key_'] and ',pfree]' in result['key_']: + disk_path = result['key_'].split('[')[-1].split(',')[0] + disk_paths.append(disk_path) + if 'net.if.in' in result['key_']: + networks.append(result['key_'].split('[')[-1].replace(']', '')) + if 'net.if.out' in result['key_']: + networks.append(result['key_'].split('[')[-1].replace(']', '')) + if disk_paths and networks: + disk_path = ','.join(set(disk_paths)) + network = ','.join(set(networks)) + # 修改数据库表数据 + db_zabbix.query.filter(and_(db_zabbix.ip == infos[0], db_zabbix.ssh_port == infos[1], db_zabbix.hostname == infos[2])).update( + {db_zabbix.network: network, db_zabbix.disk_path: disk_path}) + db_idc.DB.session.commit() + try: + zabi = tools.zabbix_api() + db_zabbix = db_idc.zabbix_info + host_infos = db_zabbix.query.with_entities(db_zabbix.ip,db_zabbix.ssh_port,db_zabbix.hostname).all() + if host_infos: + pool = ThreadPool(3) + pool.map(get_zabbix,host_infos) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + finally: + zabi.zabbix_logout() + db_idc.DB.session.remove() + +@check.proce_lock +def es_get_log_status(): + lte_date = datetime.datetime.now() + gte_date = lte_date - datetime.timedelta(minutes=1) + lte_date = lte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + gte_date = gte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + try: + #获取es当前1分钟的数据 + res = helpers.scan(es, index='logstash-nginx-log-*', query={"query": { + "bool": { + "must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}], + "must_not": [{"terms": {"status": [200, 301, 302, 304]}}]}}}) + for info in res: + try: + if info['_source']: + info = info['_source'] + if 'time_iso8601' in info: + try: + time_d, time_t = tools.time_format(info['time_iso8601']) + except Exception as e: + logging.error(e) + else: + if 'upstream_addr' not in info: + info['upstream_addr'] = '-' + vals = [info[k] for k in ('remote_addr','status','host','uri','upstream_addr','upstream_response_time') if k in info] + vals.append(time_t) + counts_logs(vals) + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + +@check.proce_lock +def es_get_log_time(): + lte_date = datetime.datetime.now() + gte_date = lte_date - datetime.timedelta(minutes=1) + lte_date = lte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + gte_date = gte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + try: + #响应时间大于1秒的日志 + res = helpers.scan(es, index='logstash-nginx-log-*', query={"query": {"bool": {"must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"terms": {"status": [200, 301, 302, 304]}},{"range": {"upstream_response_time": {"gte": 1}}}]},}}) + for info in res: + try: + if info['_source']: + info = info['_source'] + if 'time_iso8601' in info: + try: + time_d, time_t = tools.time_format(info['time_iso8601']) + except Exception as e: + logging.error(e) + else: + if 'upstream_addr' not in info: + info['upstream_addr'] = '-' + vals = [info[k] for k in ('remote_addr','status','host','uri','upstream_addr','upstream_response_time')] + vals.append(time_t) + counts_logs(vals) + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + +@check.proce_lock +def cron_run_task(): + loging.write("start run %s ......" %cron_run_task.__name__) + try: + # 清理资产资源表残留信息 + MY_SQL = Mysql.MYSQL(db='mysql') + cmds = ("delete FROM op.project_third where project not in (select DISTINCT(project)from op.project_list);", + "delete FROM op.project_other where server_id not in (select id from idc.servers);", + "delete FROM op.project_third where third_id not in (select id from idc.third_resource);", + "delete FROM idc.crontabs where server_id not in (select id from idc.servers);", + "delete FROM idc.hosts where server_id not in (select id from idc.servers);", + ) + for cmd in cmds: + MY_SQL.Run(cmd) + cmd = "select ip,ssh_port from idc.third_resource;" + values = MY_SQL.Run(cmd) + for vals in values: + ip,ssh_port = vals + cmd = "select id from idc.servers where ip='%s' and ssh_port=%i" %(ip,ssh_port) + if not MY_SQL.Run(cmd): + cmd = "delete from idc.third_resource where ip='%s' and ssh_port=%i" %(ip,ssh_port) + MY_SQL.Run(cmd) + cmd = "select ip,ssh_port from op.project_list;" + values = MY_SQL.Run(cmd) + for vals in values: + ip, ssh_port = vals + cmd = "select id from idc.servers where ip='%s' and ssh_port=%i" % (ip, ssh_port) + if not MY_SQL.Run(cmd): + cmd = "delete from op.project_list where ip='%s' and ssh_port=%i" % (ip, ssh_port) + MY_SQL.Run(cmd) + #清理mysql表相关信息 + cmd = "select ip,port from idc.mysqldb;" + values = MY_SQL.Run(cmd) + for vals in values: + ip, app_port = vals + cmd = "select id from idc.third_resource where ip='%s' and app_port=%i" % (ip, app_port) + if not MY_SQL.Run(cmd): + cmd = "delete from idc.mysqldb where ip='%s' and port=%i" % (ip, app_port) + MY_SQL.Run(cmd) + cmd = "select ip,port from idc.tableinfo;" + values = MY_SQL.Run(cmd) + for vals in values: + ip, port = vals + cmd = "select id from idc.mysqldb where ip='%s' and port=%i" % (ip,port) + if not MY_SQL.Run(cmd): + cmd = "delete from idc.tableinfo where ip='%s' and port=%i" % (ip,port) + MY_SQL.Run(cmd) + #清理zabbix信息表 + cmd = "delete FROM idc.zabbix_info where update_time not like '{0} %';".format(time.strftime('%Y-%m-%d'),time.localtime()) + MY_SQL.Run(cmd) + #清理redis信息表 + db_third = db_idc.third_resource + db_servers = db_idc.idc_servers + db_redis = db_idc.redis_info + vals = db_third.query.with_entities(distinct(db_third.ip)).filter(db_third.resource_type=='redis').all() + vals = tuple([val[0] for val in vals]) + ids = db_servers.query.with_entities(db_servers.id).filter(db_servers.ip.in_(vals)).all() + ids = tuple([int(id[0]) for id in ids]) + v = db_redis.query.filter(~ db_redis.server_id.in_(ids)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + finally: + loging.write("complete %s !" % cron_run_task.__name__) + MY_SQL.Close() + db_idc.DB.session.remove() + +def Get_project_lists(): + #动态更新项目域名接口列表 + try: + db_project = db_op.project_list + dt = time.strftime('%Y-%m-%d', time.localtime()) + url = "http://172.16.21.39:8080/v1/app?limit=0" + f = requests.get(url) + for info in f.json(): + try: + Key = 'api_domain_lists_%s' % dt + RC_CLUSTER.sadd(Key,info['domain']) + RC_CLUSTER.expire(Key,86400) + Key = 'api_uri_lists_%s_%s' % (info['domain'], dt) + RC_CLUSTER.sadd(Key, info['interface']) + RC_CLUSTER.expire(Key,86400) + #更新项目对应域名列表 + RC_CLUSTER.sadd('get_api_projects_%s'%dt, info['name']) + RC_CLUSTER.expire('get_api_projects_%s'%dt,86400) + RC_CLUSTER.sadd("get_api_%s_%s" %(info['name'],dt), info['domain']) + RC_CLUSTER.expire("get_api_%s_%s" % (info['name'], dt),86400) + except Exception as e: + logging.error(e) + for project in RC_CLUSTER.smembers('get_api_projects_%s'%dt): + try: + domains = RC_CLUSTER.smembers("get_api_%s_%s" % (project,dt)) + if domains: + domains = '%s,'%','.join(domains) + values = db_project.query.filter(db_project==project).all() + if values: + db_project.query.filter(db_project==project).update({db_project.domain:domains}) + db_op.DB.session.commit() + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + +@check.proce_lock +def check_host_exist(): + #探测应用服务及服务器是否存活 + try: + dt = time.strftime('%Y-%m-%d', time.localtime()) + db_idc_id = db_idc.idc_id + db_server = db_idc.idc_servers + aid = db_idc_id.query.with_entities(db_idc_id.id,db_idc_id.aid).all() + aid = {info[0]:info[1] for info in aid} + server_ensure = [] + if RC.exists('server_ensure'): + for info in RC.smembers('server_ensure'): + login_fault = RC.hkeys('ssh_login_fault_%s'%dt) + port_fault = RC.hkeys('ssh_port_fault_%s'%dt) + if info not in login_fault and info not in port_fault: + RC.srem('server_ensure',info) + server_ensure = RC.smembers('server_ensure') + server_val = db_server.query.with_entities(db_server.ip, db_server.ssh_port,db_server.idc_id).filter(and_(db_server.status != '维护中', db_server.comment != '跳过')).all() + for info in server_val: + ip,ssh_port,idc_id = info + if tcpping(host=ip, port=ssh_port, timeout=3): + RC.hdel('ssh_port_fault_%s'%dt, '%s:%s' % (ip, ssh_port)) + try: + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + Ssh.Run('whoami') + Ssh.Close() + except: + if '%s:%s' % (ip, ssh_port) not in server_ensure: + RC.hset('ssh_login_fault_%s'%dt, '%s:%s' % (ip, ssh_port), aid[idc_id]) + else: + RC.hdel('ssh_login_fault_%s'%dt, '%s:%s' % (ip, ssh_port)) + RC.hdel('ssh_port_fault_%s'%dt, '%s:%s' % (ip, ssh_port)) + else: + if '%s:%s' % (ip, ssh_port) not in server_ensure: + RC.hset('ssh_port_fault_%s'%dt, '%s:%s' % (ip, ssh_port), aid[idc_id]) + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + +@check.proce_lock +def business_alarm(): + #获取业务接口性能数据 + loging.write("start %s ......" %business_alarm.__name__) + try: + try: + db_influxdb_alarm = db_idc.influxdb_alarm + Keys = ('avg_resp','status_5xx', 'status_4xx', 'resp_1000','pv') + Dbs = {'avg_resp':db_influxdb_alarm.avg_resp, + 'status_5xx':db_influxdb_alarm.status_5xx, + 'status_4xx':db_influxdb_alarm.status_4xx, + 'resp_1000':db_influxdb_alarm.resp_1000} + dd = time.strftime('%Y-%m-%d', time.localtime()) + dt = datetime.datetime.now() + nt = dt.strftime('%Y-%m-%dT%H:%M:%SZ') + #采样时间段 + t = 3 + # 环比时间 + tt = dt - datetime.timedelta(minutes=t) + ot = dt - datetime.timedelta(minutes=t * 2) + tt = tt.strftime('%Y-%m-%dT%H:%M:%SZ') + ot = ot.strftime('%Y-%m-%dT%H:%M:%SZ') + # 同比时间 + sdt = dt - datetime.timedelta(days=1) + stt = sdt - datetime.timedelta(minutes=t) + sot = sdt - datetime.timedelta(minutes=t * 2) + stt = stt.strftime('%Y-%m-%dT%H:%M:%SZ') + sot = sot.strftime('%Y-%m-%dT%H:%M:%SZ') + #获取指定域名列表 + Key = 'api_domain_lists_%s' % dd + hosts = RC_CLUSTER.smembers(Key) + # 获取当前时间段域名接口列表 + cmd = "select host,uri,avg_resp from analysis_logs WHERE time >= '%s' and time <='%s'" % (tt, nt) + result = Influx_cli.query(cmd) + except Exception as e: + logging.error(e) + else: + try: + host_key = 'influx_hosts_%s' % nt + for infos in result.get_points(): + host = infos['host'].replace("'", '') + if host in hosts: + uri = infos['uri'].replace("'", '') + # 获取指定域名接口列表 + Key = 'api_uri_lists_%s_%s' % (host, dd) + uris = RC_CLUSTER.smembers(Key) + if uri in uris: + uri_key = 'influx_%s_%s' % (host, nt) + RC.sadd(host_key, host) + RC.expire(host_key,3600) + RC.sadd(uri_key, uri) + RC.expire(uri_key, 3600) + except Exception as e: + logging.error(e) + else: + for host in RC.smembers(host_key): + uri_key = 'influx_%s_%s' % (host, nt) + for uri in RC.smembers(uri_key): + try: + now_infos = defaultdict() + old_infos = defaultdict() + bef_infos = defaultdict() + try: + #获取当前数据 + cmd = "select mean(*) from analysis_logs WHERE time >= '%s' and time <='%s' and host='%s' and uri='%s'" % ( + tt, nt, host, uri) + result = Influx_cli.query(cmd) + for infos in result.get_points(): + now_infos = infos + except Exception as e: + logging.error(e) + try: + #获取环比数据 + cmd = "select mean(*) from analysis_logs WHERE time >= '%s' and time <='%s' and host='%s' and uri='%s'" % ( + ot, tt, host, uri) + result = Influx_cli.query(cmd) + for infos in result.get_points(): + old_infos = infos + except Exception as e: + logging.error(e) + try: + #获取同比数据 + cmd = "select mean(*) from analysis_logs WHERE time >= '%s' and time <='%s' and host='%s' and uri='%s'" % ( + sot, stt, host, uri) + result = Influx_cli.query(cmd) + for infos in result.get_points(): + bef_infos = infos + except Exception as e: + logging.error(e) + + if now_infos and old_infos and bef_infos: + values = defaultdict() + #获取接口指标列表 + for k in Keys: + try: + key = 'mean_%s' % k + alart_old = 0.0 + alart_bef = 0.0 + old_val = 0.0 + bef_val = 0.0 + now_val = 0.0 + try: + #获取报警阀值 + if key != 'mean_pv': + alarm_arg = db_influxdb_alarm.query.with_entities(func.avg(Dbs[k])).filter(and_(db_influxdb_alarm.host == host, db_influxdb_alarm.uri == uri)).all() + if alarm_arg[0][0]: + alarm_arg = float('%.3f' %alarm_arg[0][0])*0.9 + else: + alarm_arg = 0.0 + except Exception as e: + logging.error(e) + else: + #对数据进行清洗和计算 + if now_infos[key] and old_infos[key] and bef_infos[key]: + if now_infos[key] > 0 and old_infos[key] > 0 and bef_infos[key] > 0: + if key == 'mean_avg_resp': + try: + if now_infos[key] > alarm_arg and now_infos[key]>1: + #当前值 + now_val = float(now_infos[key]) + old_val = float(old_infos[key]) + bef_val = float(bef_infos[key]) + # 环比数据 + alart_old = float(now_val - old_val) / old_val + # 同比数据 + alart_bef = float(now_val - bef_val) / bef_val + except Exception as e: + logging.error(e) + elif key == 'mean_pv': + try: + if now_infos[key] >15000 or (old_infos[key]-now_infos[key]) < -15000: + #当前值 + now_val = int(now_infos[key])*t + old_val = int(old_infos[key])*t + bef_val = int(bef_infos[key])*t + # 环比数据 + alart_old = float(now_val - old_val) /old_val + # 同比数据 + alart_bef = float(now_val - bef_val) /bef_val + except Exception as e: + logging.error(e) + else: + try: + alart_now = float(now_infos[key])/float(now_infos['mean_pv']) + alarm_old = float(old_infos[key])/float(old_infos['mean_pv']) + alarm_bef = float(bef_infos[key])/float(bef_infos['mean_pv']) + if alart_now > alarm_arg and alart_now >0.01: + # 当前值 + now_val = alart_now*100 + old_val = alarm_old*100 + bef_val = alarm_bef*100 + # 环比数据 + alart_old = float(alart_now - alarm_old) / alarm_old + # 同比数据 + alart_bef = float(alart_now - alarm_bef) / alarm_bef + except Exception as e: + logging.error(e) + try: + #环比大于40%以上或者同比大于40%以上 + if alart_old > 0.5 or alart_bef > 0.5: + values[key] = { + 'old': float('%.3f' % alart_old), + 'old_val': float('%.3f' % old_val), + 'bef': float('%.3f' % alart_bef), + 'bef_val': float('%.3f' % bef_val), + 'now': float('%.3f' % now_val), + 'sample': t + } + except Exception as e: + logging.error(e) + try: + #环比减少50%以上,同比减少50%以上 + if alart_old < -0.5 and alart_bef < -0.5: + values[key] = { + 'old':float('%.3f' % alart_old), + 'old_val':float('%.3f' % old_val) , + 'bef':float('%.3f' % alart_bef), + 'bef_val': float('%.3f' %bef_val), + 'now':float('%.3f' %now_val), + 'sample':t + } + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + continue + if values: + incr_key = "influxdb_incr_http://%s%s" % (host, uri) + RC.incr(incr_key) + #计数过期时间 + RC.expire(incr_key,180) + RC.hset('influxdb_mean_%s'%dd,"http://%s%s" % (host, uri),values) + RC.expire('influxdb_mean_%s' % dd,86400) + except Exception as e: + logging.error(e) + continue + finally: + db_idc.DB.session.remove() + + #业务接口性能报警 + try: + alarm_values = defaultdict() + db_project = db_op.project_list + db_business = db_op.business + dd = time.strftime('%Y-%m-%d', time.localtime()) + url_busi_key = 'interface_url_business_%s' %dd + alarm_key = 'interface_alarm_%s' % dd + mean_key = 'influxdb_mean_%s' % (dd) + alarm_lists = 'interface_list_%s' % dd + if RC.exists(mean_key): + values = RC.hgetall(mean_key) + values = {key: eval(values[key]) for key in values} + for url in values: + try: + incr_key = "influxdb_incr_%s" % url + if RC.exists(incr_key): + incr = int(RC.get(incr_key)) + if incr > 1: + business = '未知' + domain = url.split('/')[2] + #获取接口对应的业务信息 + business_id = db_project.query.with_entities(distinct(db_project.business_id)).filter( + db_project.domain.like('%{0},%'.format(domain))).all() + if business_id: + business_id = business_id[0][0] + business = db_business.query.with_entities(db_business.business).filter( + db_business.id == business_id).all() + business = business[0][0] + values[url]['incr'] = incr + values[url]['business'] = business + RC.hset(alarm_key, url, values[url]) + RC.expire(alarm_key,180) + except Exception as e: + logging.error(e) + continue + if RC.exists(alarm_key): + Keys = {'mean_avg_resp': '平均响应时间', + 'mean_status_5xx': '5xx状态码', + 'mean_status_4xx': '4xx状态码', + 'mean_resp_1000': '响应时间大于1s', + 'mean_pv':'pv访问量'} + alarm_values = RC.hgetall(alarm_key) + alarm_values = {key: eval(alarm_values[key]) for key in alarm_values} + for url in alarm_values: + try: + #收集业务名称信息 + RC.hset(url_busi_key, url, alarm_values[url]['business']) + #报警条件判断 + if int(alarm_values[url]['incr']) >3: + RC.sadd(alarm_lists,url) + for key in alarm_values[url]: + if key in Keys: + vals = alarm_values[url][key] + if key == 'mean_avg_resp': + info = '当前数值:%ss' % vals['now'] + elif key == 'mean_pv': + info = '当前pv:{0}'.format(vals['now']) + else: + info = '当前占比:{0}%'.format(vals['now']) + if float(vals['bef']) >=0: + if key =='mean_avg_resp': + bef_info = '同比增长:{0}%(昨天数值:{1}s)'.format(float(vals['bef']) * 100,vals['bef_val']) + elif key =='mean_pv': + bef_info = '同比增长:{0}%(昨天pv:{1})'.format(float(vals['bef']) * 100,int(vals['bef_val'])) + else: + bef_info = '同比增长:{0}%(昨天占比:{1}%)'.format(float(vals['bef']) * 100,vals['bef_val']) + else: + if key =='mean_avg_resp': + bef_info = '同比减少:{0}%(昨天数值:{1}s)'.format(float(vals['bef']) * 100,vals['bef_val']) + elif key =='mean_pv': + bef_info = '同比减少:{0}%(昨天pv:{1})'.format(float(vals['bef']) * 100,int(vals['bef_val'])) + else: + bef_info = '同比减少:{0}%(昨天占比:{1}%)'.format(float(vals['bef']) * 100,vals['bef_val']) + if float(vals['old']) >=0: + if key =='mean_avg_resp': + old_info = '环比增长:{0}%(三分钟前数值:{1}s)'.format(float(vals['old']) * 100,vals['old_val']) + elif key == 'mean_pv': + old_info = '环比增长:{0}%(三分钟前pv:{1})'.format(float(vals['old']) * 100,int(vals['old_val'])) + else: + old_info = '环比增长:{0}%(三分钟前占比:{1}%)'.format(float(vals['old']) * 100,vals['old_val']) + else: + if key == 'mean_avg_resp': + old_info = '环比减少:{0}%(三分钟前数值:{1}s)'.format(float(vals['old']) * 100,vals['old_val']) + elif key == 'mean_pv': + old_info = '环比减少:{0}%(三分钟前pv:{1})'.format(float(vals['old']) * 100,int(vals['old_val'])) + else: + old_info = '环比减少:{0}%(三分钟前占比:{1}%)'.format(float(vals['old']) * 100,vals['old_val']) + text = ['**线上业务:%s**' % alarm_values[url]['business'],"业务接口:%s" % url,'**详情:**', + '性能指标:%s,%s' %(Keys[key],info),bef_info,old_info + ,'采样数据:{0}分钟,持续时间:{1}分钟'.format(vals['sample'],int(alarm_values[url]['incr'])*3), + '**接口性能异常!**'] + if str(alarm_values[url]['business']) not in ['web']: + tools.dingding_msg(text) + #统计业务接口报警次数 + alarm_count_key = 'op_business_alarm_count_%s' %dd + RC_CLUSTER.hincrby(alarm_count_key,url,1) + alarm_busi_key = 'op_business_alarm_busi_%s' %dd + RC_CLUSTER.hincrby(alarm_busi_key,alarm_values[url]['business'],1) + alarm_perf_key = 'op_business_alarm_perf_%s' %dd + RC_CLUSTER.hincrby(alarm_perf_key,Keys[key],1) + RC_CLUSTER.expire(alarm_count_key,604800) + RC_CLUSTER.expire(alarm_busi_key,604800) + RC_CLUSTER.expire(alarm_perf_key,604800) + time.sleep(1) + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + finally: + # 接口性能恢复通知 + if alarm_values and RC.exists(alarm_lists): + url_lists = RC.smembers(alarm_lists) + alarms = [url for url in alarm_values if int(alarm_values[url]['incr']) >2] + for url in url_lists: + if url not in alarms: + business = RC.hget(url_busi_key, url) + text = ['**线上业务:%s**' %business, "业务接口:%s" % url, '**接口性能恢复正常!**'] + RC.srem(alarm_lists, url) + #发送报警恢复信息 + if business not in ['web']: + tools.dingding_msg(text) + RC.expire(alarm_lists,86400) + RC.expire(url_busi_key, 86400) + db_op.DB.session.remove() + loging.write("complete %s !" % business_alarm.__name__) + +@check.proce_lock +def reboot_tomcat(): + try: + loging.write("start %s ......" %reboot_tomcat.__name__) + lte_date = datetime.datetime.now() + gte_date = lte_date - datetime.timedelta(minutes=15) + lte_date = lte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + gte_date = gte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + db_project = db_op.project_list + def action(reboot_lists, text, reboot=False): + try: + cmds = {6695: "tomcat-w5", 6696: "tomcat-w6", 5661: "tomcat-L1", 5662: "tomcat-L2"} + # 判断ip是否在自有服务列表 + for host, app_port in reboot_lists: + if host and app_port: + project = '未知项目' + #判断服务器的真实ip + host = tools.real_ip(host) + #获取服务器ssh端口 + vals = db_project.query.with_entities(db_project.ssh_port, db_project.project).filter( + and_(db_project.ip == host, db_project.app_port == app_port)).all() + if vals and host not in ('192.168.1.15', '192.168.1.16'): + if len(vals[0]) == 2: + ssh_port, project = vals[0] + if reboot: + try: + # 远程进行tomcat重启操作 + Ssh = SSH.ssh(ip=host, ssh_port=ssh_port) + Ssh.Run("supervisorctl restart {0}".format(cmds[int(app_port)])) + Ssh.Close() + time.sleep(15) + except Exception as e: + logging.error(e) + continue + else: + text.append("%s %s %s " % (host, app_port, project)) + if not reboot: + text.append("%s %s %s " % (host, app_port, project)) + except Exception as e: + logging.error(e) + finally: + return text + + #499状态码 + try: + Msg = [] + body = {"size": 0, "query": {"bool": {"must": [{"query_string": {"query": "status:499",}},{"range": {"time_iso8601": { + "gte": gte_date,"lte": lte_date}}}]}},"aggs": {"hosts": {"terms": {"field": "upstream_addr.keyword", + "size": 5,"order": {"_count": "desc"}}}}} + #indexs = ('logstash-nginx-log-whv3*','logstash-nginx-log-lbs*') + indexs = ('logstash-nginx-log-whv3*',) + text = ["**自动重启tomcat以下实例:**"] + for index in indexs: + res = es.search(index=index, body=body) + reboot_lists = [info['key'].split(':') for info in res['aggregations']['hosts']['buckets'] if info['doc_count'] > 100 if len(info['key'].split(':'))==2] + if reboot_lists: + text = action(reboot_lists, text, reboot=True) + Msg.append(text) + except Exception as e: + logging.error(e) + #响应超时 + try: + text = ["**第三方接口业务tomcat响应超时:**"] + body = {"size": 0, + "query": { + "bool": { + "filter": [{ + "range": { + "time_iso8601": { + "gte":gte_date, + "lte":lte_date + } + } + }, { + "query_string": { + "query": "host:(\"coapi.moji.com\" OR \"ele.coapi.moji.com\" OR \"gaode.coapi.moji.com\" OR \"hw\\-p1.api.moji.com\" OR \"meizu.coapi.moji.com\" OR \"vw.coapi.moji.com\" OR \"wdj.mojichina.com\")" + } + }] + } + }, + "aggs": { + "3": { + "terms": { + "field": "upstream_addr.keyword", + "size": 100, + "order": { + "rt": "desc" + } + }, + "aggs": { + "rt": { + "avg": { + "field": "request_time" + } + } + } + } + } + } + res = es.search(index='logstash-nginx-log-coapi*', body=body) + for info in res['aggregations']['3']['buckets']: + rt = int(float(info['rt']['value']) * 1000) + if rt > 200 and int(info['doc_count']) > 10000: + if ',' in info['key']: + for val in info['key'].split(','): + host,app_port = val.split(':') + # 判断服务器的真实ip + host = tools.real_ip(host) + text.append("%s %s coapi " %(host, app_port)) + else: + host, app_port = info['key'].split(':') + # 判断服务器的真实ip + host = tools.real_ip(host) + text.append("%s %s coapi " %(host, app_port)) + Msg.append(text) + except Exception as e: + logging.error(e) + #5xx状态码 + try: + body = {"size": 0, + "query": {"bool": {"must": [{"range": {"status": {"gte": 500, "lte": 599}}}, {"range": {"time_iso8601": { + "gte": gte_date, "lte": lte_date}}}]}}, + "aggs": {"hosts": {"terms": {"field": "upstream_addr.keyword", + "size": 10, "order": {"_count": "desc"}}}}} + indexs = ('logstash-nginx-log-*',) + text = ["**tomcat服务5xx状态码实例:**"] + for index in indexs: + res = es.search(index=index, body=body) + reboot_lists = [info['key'].split(':') for info in res['aggregations']['hosts']['buckets'] if + info['doc_count'] > 100 if len(info['key'].split(':'))==2] + if reboot_lists: + text = action(reboot_lists, text, reboot=False) + Msg.append(text) + #发送钉钉群消息 + for msg in Msg: + if len(msg) >1: + tools.dingding_msg(msg) + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + finally: + loging.write("complete %s !" % reboot_tomcat.__name__) + db_op.DB.session.remove() + +@check.proce_lock +def business_monitor(check_url=None): + def alarm(): + # 记录故障次数 + RC.incr(error_alarm) + RC.expire(error_alarm,300) + # 判断触发报警条件 + if int(RC.get(error_alarm)) > 3: + try: + if int(lock) == 0: + tools.dingding_msg(text,alart_token) + db_busi_m.query.filter(db_busi_m.url == URL).update({db_busi_m.update_time:td, + db_busi_m.alarm_time: td, + db_busi_m.code: 1, + db_busi_m.error_ip: ip}) + db_op.DB.session.commit() + except Exception as e: + logging.error(e) + else: + RC.delete(recovery_alarm) + RC.delete(error_alarm) + #标记恢复通知 + RC.incr(recovery_alarm) + td = time.strftime("%Y-%m-%d %H:%M:00", time.localtime()) + checks = [] + try: + db_busi_m = db_op.business_monitor + values = db_busi_m.query.with_entities(db_busi_m.url,db_busi_m.method,db_busi_m.project,db_busi_m.version,db_busi_m.lock,db_busi_m.alart_token).all() + if check_url: + values = db_busi_m.query.with_entities(db_busi_m.url,db_busi_m.method,db_busi_m.project,db_busi_m.version,db_busi_m.lock,db_busi_m.alart_token).filter(db_busi_m.url==check_url).all() + for val in values: + try: + URL,method,project,version,lock,alart_token = val + domain = URL.split('/')[2] + if ':' in domain: + domain = domain.split(':')[0] + headers = {'Host':domain} + if URL.count(':') >1: + ipaddress = [domain] + else: + ipaddress = tools.dig(domain) + if ipaddress: + for ip in set(ipaddress): + try: + isp = ip_adress.Search(ip) + if isp: + isp = isp.split(',')[-1] + url = URL.split('/') + url[2]= ip + url = '/'.join(url) + error_alarm = 'error_%s_%s'%(project,ip) + recovery_alarm = 'recovery_%s_%s' % (project, ip) + text = ['项目:%s' % project, "线上版本:%s" % version,'监控接口:%s' % URL,'解析IP:%s' % ip, 'ISP线路:%s' % isp,'**健康检测失败!**'] + if method == 'post': + resp = requests.post(url, data={'src': 1}, headers=headers) + else: + resp = requests.get(url,headers=headers) + except: + if check_url: + checks.append(ip) + else: + text.insert(5,"故障原因:request time out") + alarm() + else: + try: + if int(resp.status_code) in (200,301,302,304): + result = resp.json() + ver = '' + proj = '' + if 'rc' in result: + if 'ver' in result['rc']: + ver = result['rc']['ver'] + if 'proj' in result['rc']: + proj = result['rc']['proj'] + db_busi_m.query.filter(db_busi_m.url == URL).update({db_busi_m.version:ver, + db_busi_m.project:proj, + db_busi_m.code: 0, + db_busi_m.update_time:td}) + db_op.DB.session.commit() + #故障恢复通知条件判断 + if RC.exists(recovery_alarm): + RC.incr(recovery_alarm) + RC.expire(recovery_alarm,300) + if int(RC.get(recovery_alarm)) >2: + text = ['项目:%s' % project, "线上版本:%s" % version, '监控接口:%s' % URL, + '解析IP:%s' % ip, 'ISP线路:%s' % isp, '**服务恢复正常!**'] + try: + tools.dingding_msg(text,alart_token) + #自动解除报警锁定及故障状态 + RC.delete(recovery_alarm) + RC.delete(error_alarm) + db_busi_m.query.filter(and_(db_busi_m.url == URL)).update({db_busi_m.lock:0, + db_busi_m.code:0, + db_busi_m.error_ip:''}) + db_op.DB.session.commit() + except Exception as e: + logging.error(e) + else: + if check_url: + checks.append(ip) + else: + text.insert(5,'故障原因:status code %s' % int(resp.status_code)) + alarm() + except Exception as e: + logging.error(e) + except Exception as e: + logging.error(e) + continue + if check_url: + return checks + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + +@check.proce_lock +def es_get_data(): + tm = datetime.datetime.now() + tt = tm.strftime('%H:%M') + td = time.strftime("%Y-%m-%d", time.localtime()) + web_key = 'internet_access_%s' % td + lte_date = datetime.datetime.now() + gte_date = lte_date - datetime.timedelta(minutes=1) + lte_date = lte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + gte_date = gte_date.strftime('%Y-%m-%dT%H:%M:%S+08:00') + # 获取网站并发数据 + try: + body = {"query":{"range":{"time_iso8601": {"gte": "%s" % gte_date,"lte": "%s" % lte_date}}}} + res = es.search(index='logstash-nginx-log-*', body=body) + if res['hits']['total']: + RC.rpush(web_key, [tt,int(res['hits']['total'])]) + RC.expire(web_key,864000) + except Exception as e: + logging.error(e) + try: + #获取错误状态码数据 + err_4xx = 'error_4xx_%s' % td + err_5xx = 'error_5xx_%s' % td + body = {'size': 0, "query": {"bool": {"must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"range": {"status": {"gte": 400, "lte": 499}}}]}, }} + res = es.search(index='logstash-nginx-log-*', body=body) + if res: + RC.rpush(err_4xx, [tt, int(res['hits']['total'])]) + RC.expire(err_4xx, 86400) + body = {'size': 0, "query": {"bool": {"must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"range": {"status": {"gte": 500, "lte": 599}}}]}, }} + res = es.search(index='logstash-nginx-log-*', body=body) + if res: + RC.rpush(err_5xx, [tt, int(res['hits']['total'])]) + RC.expire(err_5xx, 86400) + except Exception as e: + logging.error(e) + #获取响应时间段数据 + try: + upstream = {'0-100':(0,0.1),'100-200':(0.1,0.2),'200-500':(0.2,0.5),'500-1000':(0.5,1),'1000-3000':(1,3),'3000+':(3,60)} + for k in upstream: + Key = 'es_get_time_%s_%s' %(k,td) + res = es.search(index='logstash-nginx-log-*', body={"query": { + "bool": { + "must": [{"range": {"time_iso8601": {"gte": gte_date, "lte": lte_date}}}, + {"range": {"upstream_response_time": {"gte": upstream[k][0], "lte": upstream[k][1]}}}] + }, + }, + "aggs": { + "avg_resp": { + "avg": {"field": "upstream_response_time"} + } + } + }) + val = [int(res['hits']['total']),int(float(res['aggregations']['avg_resp']['value'])*1000)] + RC.hset(Key,'%s_%s'%(k,tt),val) + RC.expire(Key,86400) + except Exception as e: + logging.error(e) + +@check.proce_lock +def influxdb_counts(): + dt = datetime.datetime.now() + tt = dt - datetime.timedelta(hours=1) + nt = dt.strftime('%Y-%m-%dT%H:00:00Z') + tt = tt.strftime('%Y-%m-%dT%H:00:00Z') + Influx_wri = InfluxDBClient(influxdb_host,influxdb_port,influxdb_user,influxdb_pw, 'analysis_logs') + cmd = "select host,uri,avg_resp from analysis_logs WHERE time >= '%s' and time <'%s'" % (tt,nt) + result = Influx_cli.query(cmd) + if result: + for infos in result.get_points(): + try: + host = infos['host'].replace("'", '') + RC.sadd('influx_hosts_%s' % nt, host) + uri = infos['uri'].replace("'", '') + RC.sadd('influx_%s_%s' % (host, nt), uri) + except Exception as e: + logging.error(e) + continue + for host in RC.smembers('influx_hosts_%s' % nt): + for uri in RC.smembers('influx_%s_%s' % (host, nt)): + try: + cmd = "select mean(*) from analysis_logs WHERE time >= '%s' and time <'%s' and host='%s' and uri='%s'" % (tt,nt, host, uri) + result = Influx_cli.query(cmd) + if result: + for infos in result.get_points(): + del infos['time'] + infos = {info:float(infos[info]) for info in infos} + json_body = [{"measurement": "analysis%s" % tt.split('T')[0].split('-')[0], "tags": {"host": host, "uri": uri}, + "fields": infos, 'time': nt}] + Influx_wri.write_points(json_body) + except Exception as e: + logging.error(e) + continue + +@check.proce_lock +def influxdb_alarm(): + dt = datetime.datetime.now() + tt = dt - datetime.timedelta(days=1) + nt = dt.strftime('%Y-%m-%dT00:00:00Z') + dd = tt.strftime('%Y-%m-%d') + tt = tt.strftime('%Y-%m-%dT00:00:00Z') + Influx_cli = InfluxDBClient(influxdb_host, influxdb_port, influxdb_user, influxdb_pw,'analysis_logs') + db_influxdb_alarm = db_idc.influxdb_alarm + Key = 'api_domain_lists_%s' % dd + hosts = RC_CLUSTER.smembers(Key) + try: + for host in hosts: + Key = 'api_uri_lists_%s_%s' % (host, dd) + uris = RC_CLUSTER.smembers(Key) + for uri in uris: + try: + infos = None + cmd = "select max(*) from " + 'analysis%s' %time.strftime('%Y',time.localtime()) + " WHERE time >= '%s' and time <='%s' and host='%s' and uri='%s'" % ( + tt, nt, host, uri) + result = Influx_cli.query(cmd) + if result: + for infos in result.get_points(): + infos = infos + if infos: + c = db_influxdb_alarm(host=host,uri=uri,avg_resp=infos['max_mean_avg_resp'],resp_100=infos['max_mean_resp_100']/infos['max_mean_pv'], + resp_200=infos['max_mean_resp_200']/infos['max_mean_pv'],resp_500=infos['max_mean_resp_500']/infos['max_mean_pv'], + resp_1000=infos['max_mean_resp_1000']/infos['max_mean_pv'],status_4xx=infos['max_mean_status_4xx']/infos['max_mean_pv'], + status_5xx=infos['max_mean_status_5xx']/infos['max_mean_pv'],year=dd) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + +@check.proce_lock +def zabbix_counts(): + dict_load = defaultdict() + dict_mem = defaultdict() + dict_openfile = defaultdict() + free_load = [] + free_mem = [] + free_openfile = [] + now_time = datetime.datetime.now() + dt = now_time - datetime.timedelta(days=3) + dt = dt.strftime('%Y-%m-%dT00:00:00Z') + Influx_cli = InfluxDBClient(influxdb_host, influxdb_port, influxdb_user, influxdb_pw, 'zabbix_infos') + cmd = "select max(*) from server_infos where time >='%s' group by hostname" % dt + try: + results = Influx_cli.query(cmd) + if results: + for key in results.keys(): + hostname = key[-1]['hostname'] + for infos in results[key]: + if infos['max_cpu_load'] >= 0: + dict_load[hostname] = infos['max_cpu_load'] + if infos['max_mem_use'] >=0: + dict_mem[hostname] = infos['max_mem_use'] + if infos['max_openfile'] >=0: + dict_openfile[hostname] = infos['max_openfile'] + except Exception as e: + logging.error(e) + try: + if dict_load: + loads = sorted(dict_load.items(), key=lambda item: int(item[1]), reverse=True) + RC_CLUSTER.set('op_zabbix_server_load_top',loads[:20]) + free_load = [info[0] for info in loads if int(info[-1]) <=3] + if dict_mem: + mems = sorted(dict_mem.items(), key=lambda item: int(item[1]), reverse=True) + + RC_CLUSTER.set('op_zabbix_server_mem_top', mems[:20]) + free_mem = [info[0] for info in mems if int(info[-1]) <= 5] + if dict_openfile: + openfiles = sorted(dict_openfile.items(), key=lambda item: int(item[1]), reverse=True) + RC_CLUSTER.set('op_zabbix_server_openfile_top', openfiles[:20]) + free_openfile = [info[0] for info in openfiles if int(info[-1]) <= 1024] + if free_load and free_mem and free_openfile: + RC_CLUSTER.set('op_zabbix_free_servers',set(free_load)&set(free_mem)&set(free_openfile)) + except Exception as e: + logging.error(e) \ No newline at end of file diff --git a/Modules/Task2.py b/Modules/Task2.py new file mode 100644 index 00000000..5ba39077 --- /dev/null +++ b/Modules/Task2.py @@ -0,0 +1,636 @@ +#-*- coding: utf-8 -*- +import redis +from Modules import loging,Mysql,check,SSH,db_idc,db_op,tools +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import and_,or_,distinct +from influxdb import InfluxDBClient +from collections import defaultdict +import datetime +import time +from functools import reduce +from multiprocessing.dummy import Pool as ThreadPool +from tcpping import tcpping +from kubernetes import client +app = Flask(__name__) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/sql.conf') +logging = loging.Error() +DB = SQLAlchemy(app) +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +RC = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +redis_data = app.config.get('REDIS_DATA') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +MYSQL_HOST = app.config.get('MYSQL_HOST') +MYSQL_PORT = app.config.get('MYSQL_PORT') +MYSQL_USER = app.config.get('MYSQL_USER') +MYSQL_PW = app.config.get('MYSQL_PASSWORD') +MYSQL_INFO_USER = app.config.get('MYSQL_INFO_USER') +MYSQL_INFO_PW = app.config.get('MYSQL_INFO_PASSWORD') +influxdb_host = app.config.get('INFLUXDB_HOST') +influxdb_port = app.config.get('INFLUXDB_PORT') +influxdb_user = app.config.get('INFLUXDB_USER') +influxdb_pw = app.config.get('INFLUXDB_PASSWORD') +influxdb_db = app.config.get('INFLUXDB_DB') +config,contexts,config_file = tools.k8s_conf() +@check.proce_lock +def get_mysqldb_info(): + try: + db_third = db_idc.third_resource + db_mysqldb = db_idc.idc_mysqldb + IDC_MySql = Mysql.MYSQL(MYSQL_USER, MYSQL_PW, MYSQL_HOST, MYSQL_PORT, 'idc') + IDC_MySql.Run("TRUNCATE TABLE tableinfo;") + infos = IDC_MySql.Run("select ip,port from mysqldb where master = '是' or slave = '是';") + except Exception as e: + logging.error(e) + else: + for info in infos: + ip,port = info + try: + INFO_MySql = Mysql.MYSQL(MYSQL_INFO_USER, MYSQL_INFO_PW, ip, port, 'mysql') + exclude_db = ('mysql', 'test', 'information_schema', 'performance_schema') + cmd = "show databases;" + Lists = INFO_MySql.Run(cmd) + Lists = [db[0] for db in Lists if db[0] not in exclude_db] + db_lists = ','.join(Lists) + except: + continue + else: + try: + cmd = "SHOW SLAVE STATUS;" + result = INFO_MySql.Run(cmd) + if result: + result = list(result[0]) + m_ip = result[1].strip() + m_user = result[2].strip() + m_port = result[3] + s_io = result[10].strip() + s_sql = result[11].strip() + #判断获取的master主机信息是否需要解析 + if len(m_ip.split('.')) != 4: + ssh_port = db_third.query.with_entities(db_third.ssh_port).filter(and_(db_third.ip==ip,db_third.app_port==port)).all() + ssh_port = ssh_port[0][0] + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + ssh_cmd = "/bin/cat /etc/hosts|/bin/grep {0}".format(str(m_ip)) + values = Ssh.Run(ssh_cmd) + if values['stdout']: + m_ip = values['stdout'][0].split()[0] + else: + m_ip = None + if m_ip: + if s_io == 'Yes' and s_sql == 'Yes': + #判断真实的主库服务器信息 + values = db_mysqldb.query.with_entities(db_mysqldb.Master_Host,db_mysqldb.Master_Port).filter( + and_(db_mysqldb.ip == m_ip, db_mysqldb.port == m_port,db_mysqldb.slave == '是')).all() + if values: + m_ip,m_port = values[0] + cmd = "update mysqldb set slave='是',master='否',db='%s',Master_Host='%s',Master_Port='%s',Master_User='%s' where ip='%s' and port=%i;" % \ + (db_lists, m_ip, m_port, m_user, ip, int(port)) + else: + check_cmd = "show slave hosts;" + check_result = IDC_MySql.Run(check_cmd) + if check_result: + cmd = "update mysqldb set slave='否',master='是',db='%s',Master_Host='',Master_Port='',Master_User='' where ip='%s' and port=%i;" % ( + db_lists, ip, int(port)) + else: + cmd = "update mysqldb set slave='否',master='是',db='%s',Master_Host='',Master_Port='',Master_User='' where ip='%s' and port=%i;" % ( + db_lists, ip, int(port)) + IDC_MySql.Run(cmd) + except Exception as e: + INFO_MySql.Close() + logging.error(e) + try: + version = INFO_MySql.Run("show variables like 'version';") + version = version[0][-1] + for db in Lists: + cmd = "SHOW TABLE STATUS from %s;" % db + results = INFO_MySql.Run(cmd) + if results: + for result in results: + table_name = result[0] + table_engine = result[1] + table_Rows = int(result[4]) + table_size = int(result[6]) + int(result[8]) + if len(str(table_size)) > 9: + table_size = '%sGB' % (table_size / 1000 / 1000 / 1000) + elif len(str(table_size)) > 6: + table_size = '%sMB' % (table_size / 1000 / 1000) + else: + table_size = '%sKB' % (table_size / 1000) + table_charset = result[14] + cmd = "insert into tableinfo (ip, port, database_name, table_name, Engine_name, Rows,size,Charset, version,update_time) VALUES ('%s',%i,'%s','%s','%s',%i,'%s','%s','%s',now());" % \ + (ip, int(port), db, table_name, table_engine, table_Rows, table_size, table_charset, version) + IDC_MySql.Run(cmd) + except Exception as e: + logging.error(e) + INFO_MySql.Close() + finally: + IDC_MySql.Close() + db_idc.DB.session.remove() + +@check.proce_lock +def task_run(): + try: + try: + # 获取业务访问数据 + db_business = db_op.business + db_project = db_op.project_list + business = db_business.query.with_entities(db_business.id, db_business.business).all() + business = {busi[0]: busi[1] for busi in business} + year = time.strftime('%Y', time.localtime()) + ot = datetime.datetime.now() - datetime.timedelta(days=0) + ot = ot.strftime('%Y-%m-%dT00:00:00Z') + Key = 'op_business_pv_%s' % ot.split('T')[0] + Influx_cli = InfluxDBClient(influxdb_host, influxdb_port, influxdb_user, influxdb_pw, 'analysis_logs') + for id in business: + business_domain = db_project.query.with_entities(distinct(db_project.domain)).filter( + db_project.business_id == int(id)).all() + if business_domain: + hosts = [domain[0] for domain in business_domain] + pv_sum = [] + for host in hosts: + if ',' in host: + hosts.extend(host.split(',')) + else: + try: + cmd = 'select sum(mean_pv) from ' + 'analysis%s' % year + " where time >='%s' and host = '%s';" % ( + ot, host) + result = Influx_cli.query(cmd) + if result: + for infos in result.get_points(): + if infos: + pv_sum.append(infos['sum'] * 60) + except Exception as e: + logging.error(e) + if pv_sum: + pv_sum = reduce(lambda x, y: x + y, pv_sum) + RC_CLUSTER.hset(Key, business[id], pv_sum) + except Exception as e: + logging.error(e) + + #获取数据库状态 + now_date = datetime.datetime.now() + gd = now_date.strftime('%Y-%m-%dT%H:%M:%SZ') + influx_fields = defaultdict() + db_mysqldb = db_idc.idc_mysqldb + infos = db_mysqldb.query.with_entities(db_mysqldb.ip,db_mysqldb.port).filter(or_(db_mysqldb.master == '是',db_mysqldb.slave == '是')).all() + Influx_cli = InfluxDBClient(influxdb_host, influxdb_port, influxdb_user, influxdb_pw,'mysqld_status') + for info in infos: + try: + ip, port = info + MySql = Mysql.MYSQL(MYSQL_INFO_USER, MYSQL_INFO_PW, ip, port, 'mysql') + vals = MySql.Run("SHOW GLOBAL STATUS;") + except: + continue + else: + if vals: + vals = {val[0]: val[1] for val in vals} + #获取QPS + Key = 'op_mysqld_QPS_%s_%s' %(ip,port) + QPS = RC.getset(Key, int(vals['Questions'])) + RC.expire(Key,3600) + if not QPS: + QPS = int(vals['Questions']) + influx_fields['QPS'] = float(int(vals['Questions']) - int(QPS)) / 3000 + #获取TPS + Key = 'op_mysqld_TPS_%s_%s' % (ip, port) + TPS = RC.getset(Key, int(vals['Com_commit']) + int(vals['Com_rollback'])) + RC.expire(Key,3600) + if not TPS: + TPS = int(vals['Com_commit']) + int(vals['Com_rollback']) + influx_fields['TPS'] = float(int(vals['Com_commit']) + int(vals['Com_rollback']) - int(TPS)) / 3000 + #获取读写比 + influx_fields['R/W'] = (int(vals['Com_select']) + int(vals['Qcache_hits'])) / (float(int(vals['Com_insert']) +int(vals['Com_update']) + int(vals['Com_delete']) + int(vals['Com_replace']))) * 100 + #获取慢查询占比 + influx_fields['S/Q'] = float(vals['Slow_queries']) / int(vals['Questions']) * 100 + #获取接受流量 + Key = 'op_mysqld_Bytes_received_%s_%s' % (ip, port) + Bytes_received =RC.getset(Key, int(vals['Bytes_received'])) + RC.expire(Key,3600) + if not Bytes_received: + Bytes_received = int(vals['Bytes_received']) + influx_fields['Bytes_r'] = float((int(vals['Bytes_received']) - int(float(Bytes_received))))/1000 + #获取发送流量 + Key = 'op_mysqld_Bytes_sent_%s_%s' % (ip, port) + Bytes_sent =RC.getset(Key, float(vals['Bytes_sent'])) + RC.expire(Key,3600) + if not Bytes_sent: + Bytes_sent = int(vals['Bytes_sent']) + influx_fields['Bytes_s'] = (int(vals['Bytes_sent']) - int(float(Bytes_sent)))/1000 + #获取连接数 + influx_fields['Connections'] = len(MySql.Run("SHOW processlist;")) + # 写入influxdb数据库 + if influx_fields: + try: + json_body = [{"measurement": "performance", "tags": {"ip": ip, "port":port}, + "fields": influx_fields, "time": gd}] + Influx_cli.write_points(json_body) + except Exception as e: + logging.error(e) + MySql.Close() + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + db_op.DB.session.remove() + #获取k8s的hpa副本数量 + try: + td = time.strftime('%Y-%m-%d',time.localtime()) + th = time.strftime('%H:%M',time.localtime()) + v1 = client.AutoscalingV1Api() + ret = v1.list_horizontal_pod_autoscaler_for_all_namespaces() + Key = 'op_hpa_chart_%s' %td + for i in ret.items: + RC.hset(Key,'%s_%s'%(i.spec.scale_target_ref.name,th),i.status.current_replicas) + except Exception as e: + logging.error(e) +@check.proce_lock +def get_other_info(): + db_project_other = db_op.project_other + db_crontabs = db_idc.crontabs + db_servers = db_idc.idc_servers + db_hosts = db_idc.hosts + infos = db_servers.query.with_entities(db_servers.id,db_servers.ip,db_servers.ssh_port).filter(and_(db_servers.status !='维护中',db_servers.comment !='跳过')).all() + try: + for info in infos: + server_id,ip,ssh_port = info + if tcpping(host=ip, port=ssh_port, timeout=3): + try: + update_date = time.strftime('%Y-%m-%d', time.localtime()) + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + #收集crontab信息 + results = Ssh.Run("cat /var/spool/cron/*") + if results['stdout']: + v = db_crontabs.query.filter(db_crontabs.server_id==int(server_id)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + for result in results['stdout']: + if not result.startswith('#') and '*' in result: + result = result.strip().split() + cron = ' '.join(result[:5]) + action = ' '.join(result[5:]) + c = db_crontabs(cron=cron,action=action,server_id=int(server_id),update_time=update_date) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + # 收集jar运行信息 + results = Ssh.Run("ps -ef|grep java|grep -e '.jar$'") + if results['stdout']: + vals = [] + v = db_project_other.query.filter(db_project_other.server_id == int(server_id)).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + for result in results['stdout']: + if 'hadoop' not in result and 'hive' not in result: + result = result.strip().split()[-1] + if '/' in result: + result = result.split('/')[-1] + vals.append(result) + for val in set(vals): + result = db_project_other.query.filter(and_(db_project_other.project==val,db_project_other.server_id==server_id)).all() + if not result: + c = db_project_other(lable='java', project=val, server_id=server_id,business_id=0, update_time=update_date) + db_op.DB.session.add(c) + db_op.DB.session.commit() + #收集hosts信息 + results = Ssh.Run("cat /etc/hosts") + if results['stdout']: + v = db_hosts.query.filter(db_hosts.server_id == int(server_id)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + for line in results['stdout']: + if not line.startswith('#') and '127.0.0.1' not in line: + line = line.strip().split() + if line: + if len(line) == 2: + if 'localhost' not in line[1]: + c = db_hosts(line[0],line[1],server_id=server_id,update_time=update_date) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + if len(line) > 2: + for hostname in line[1:]: + if not hostname.startswith('#') and not 'localhost' in hostname: + c = db_hosts(line[0],hostname, server_id=server_id, + update_time=update_date) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + Ssh.Close() + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + db_op.DB.session.remove() + +@check.proce_lock +def get_redis_info(): + db_third = db_idc.third_resource + db_redis = db_idc.redis_info + db_servers = db_idc.idc_servers + update_date = time.strftime('%Y-%m-%d', time.localtime()) + try: + server_ids = db_servers.query.with_entities(db_servers.id,db_servers.ip,db_servers.ssh_port,db_servers.hostname).all() + for infos in server_ids: + RC_CLUSTER.hset('op_server_hostnames',infos[0],infos[-1]) + server_ids = {"%s:%s"%(infos[1],infos[2]):infos[0] for infos in server_ids} + redis_list = db_third.query.with_entities(db_third.ip,db_third.ssh_port,db_third.app_port).filter(db_third.resource_type == 'redis').all() + ssh_ports = {"%s:%s" %(infos[0],infos[2]):infos[1] for infos in redis_list} + for ip,ssh_port,app_port in set(redis_list): + #初始化参数 + masterauth = None + requirepass = None + pid = None + conf_dir = None + conf_file = "" + redis_type = {'master': '否', 'slave': '否', 'cluster': '否'} + #判断ssh端口是否连通 + if tcpping(host=ip, port=app_port, timeout=3): + try: + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + cmd = "netstat -lntp|grep :%s" % app_port + results = Ssh.Run(cmd) + if results['stdout']: + for line in results['stdout'][0].split(): + if '/redis' in line: + pid = line.split('/')[0] + break + if pid: + cmd = "/bin/ps -ef|grep -v grep|grep {}".format(pid) + results = Ssh.Run(cmd) + if results['stdout']: + result = results['stdout'][0] + if 'cluster' in result: + redis_type['cluster'] = '是' + else: + try: + result = results['stdout'][0].split()[-1] + if '/' in result: + conf_file = "/usr/local/redis/etc/{}".format(result.split('/')[-1]) + if not conf_file.endswith('.conf'): + cmd = "lsof -p {}|grep 'cwd'".format(pid) + cwd = Ssh.Run(cmd) + if cwd['stdout']: + for line in cwd['stdout']: + if 'redis' in line: + conf_dir = line.split()[-1] + break + if conf_dir: + cmd = "grep {0} -r {1}/|grep '.conf:port'".format(app_port, conf_dir) + results = Ssh.Run(cmd) + if results['stdout']: + for line in results['stdout']: + if ':port {}'.format(app_port) in line: + conf_file = line.split(':')[0] + if conf_file.endswith('.conf'): + cmd = "grep masterauth {}".format(conf_file) + results = Ssh.Run(cmd) + if results['stdout']: + masterauth = results['stdout'][0].split()[-1].strip() + cmd = "grep requirepass {}".format(conf_file) + pw_result = Ssh.Run(cmd) + if pw_result['stdout']: + requirepass = pw_result['stdout'][0].split()[-1].strip() + RC = redis.StrictRedis(ip,int(app_port),password=requirepass,decode_responses=True) + Infos = RC.info() + if Infos['role'] == 'master': + redis_type['master'] = '是' + if Infos['role'] == 'slave': + redis_type['slave'] = '是' + counts = int((Infos['connected_slaves'])) + except Exception as e: + logging.error(e) + else: + try: + #修改记录slave信息 + if counts > 0: + for i in range(counts): + Info = Infos['slave%s' % i] + if isinstance(Info,dict): + slave_ip = Info['ip'] + slave_port = Info['port'] + slave_status = Info['state'] + else: + slave_ip, slave_port, slave_status = Info.split(',') + if slave_status == 'online' and int(slave_port) >1024: + try: + SSH_port = ssh_ports['%s:%s' % (slave_ip, slave_port)] + except: + server_id = slave_ip + else: + try: + server_id = server_ids['%s:%s' %(slave_ip,SSH_port)] + except: + server_id = slave_ip + try: + master_id = server_ids['%s:%s' % (ip, ssh_port)] + except: + master_id = ip + val = db_redis.query.filter(and_(db_redis.server_id == server_id, db_redis.port == slave_port)).all() + if val: + db_redis.query.filter(and_(db_redis.server_id == server_id, db_redis.port == slave_port)).update( + {db_redis.masterauth: masterauth, db_redis.requirepass: requirepass, + db_redis.master: '否',db_redis.slave: '是',db_redis.cluster: '否', + db_redis.Master_Host: master_id,db_redis.Master_Port: app_port,db_redis.update_date: update_date}) + db_idc.DB.session.commit() + else: + c = db_redis(server_id=server_id, port=slave_port, masterauth=masterauth, + requirepass=requirepass, master='否', + slave='是',cluster='否', Master_host=master_id, + Master_Port=app_port, update_date=update_date) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + except Exception as e: + db_idc.DB.session.rollback() + logging.error(e) + try: + #修改记录master或者cluster信息 + if redis_type['master'] == '是' or redis_type['cluster'] == '是': + try: + server_id = server_ids['%s:%s' % (ip, ssh_port)] + except: + server_id = ip + val = db_redis.query.filter(and_(db_redis.server_id == server_id, db_redis.port == app_port)).all() + if val: + db_redis.query.filter( + and_(db_redis.server_id == server_id, db_redis.port == app_port)).update( + {db_redis.masterauth: masterauth, db_redis.requirepass: requirepass, + db_redis.master: redis_type['master'], + db_redis.slave: redis_type['slave'], + db_redis.cluster: redis_type['cluster'], + db_redis.Master_Host: '', + db_redis.Master_Port: '', db_redis.update_date: update_date}) + db_idc.DB.session.commit() + else: + c = db_redis(server_id=server_id, port=app_port, masterauth=masterauth, + requirepass=requirepass, master=redis_type['master'], + slave=redis_type['slave'], cluster=redis_type['cluster'], + Master_host='', Master_Port='', + update_date=update_date) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + except Exception as e: + db_idc.DB.session.rollback() + logging.error(e) + except Exception as e: + logging.error(e) + continue + finally: + Ssh.Close() + else: + loging.write("delete not exist redis %s %s ......" %(ip,app_port)) + v = db_redis.query.filter(and_(or_(db_redis.server_id==server_ids['%s:%s' %(ip,ssh_port)], + db_redis.server_id == ip),db_redis.port==app_port)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + +@check.proce_lock +def get_redis_status(): + db_redis = db_idc.redis_info + db_servers = db_idc.idc_servers + update_date = time.strftime('%Y-%m-%d', time.localtime()) + try: + server_ids = db_servers.query.with_entities(db_servers.id,db_servers.ip,db_servers.hostname).all() + redis_infos = db_redis.query.with_entities(db_redis.server_id,db_redis.port,db_redis.requirepass).filter(db_redis.cluster=='否').all() + def redis_status(infos): + try: + ips = {int(infos[0]): infos[1] for infos in server_ids} + hostnames = {int(infos[0]): infos[-1] for infos in server_ids} + server_id,app_port,requirepass = infos + host = ips[int(server_id)] + except: + pass + else: + try: + Key = 'op_redis_status_%s_%s_%s' % (hostnames[int(server_id)], app_port, update_date) + RC = redis.StrictRedis(host, int(app_port), password=requirepass,decode_responses=True) + Infos = RC.info() + clients = Infos['connected_clients'] + r_clients = RC_CLUSTER.hget(Key,'clients') + if r_clients: + if int(r_clients) > int(clients): + clients = r_clients + keyspace_hits = Infos['keyspace_hits'] + keyspace_misses = Infos['keyspace_misses'] + hit_rate = "%.1f" %(float(keyspace_hits)/(int(keyspace_hits)+int(keyspace_misses))*100) + r_hit_rate = RC_CLUSTER.hget(Key,'hit_rate') + if r_hit_rate: + if int(r_hit_rate) > int(hit_rate): + hit_rate = r_hit_rate + ops = Infos['instantaneous_ops_per_sec'] + r_ops = RC_CLUSTER.hget(Key,'ops') + if r_ops: + if int(r_ops) > int(ops): + ops = r_ops + used_memory = Infos['used_memory_human'] + r_used_memory = RC_CLUSTER.hget(Key,'used_memory') + if r_used_memory: + if int(r_used_memory) > int(used_memory): + used_memory = r_used_memory + RC_CLUSTER.hmset(Key,{'clients':clients,'hit_rate':hit_rate,'ops':ops,'used_memory':used_memory}) + RC_CLUSTER.expire(Key,604800) + except: + pass + pool = ThreadPool(5) + pool.map(redis_status, redis_infos) + pool.close() + pool.join() + except Exception as e: + logging.error(e) + +@check.proce_lock +def alarm_load(): + try: + db_zabbix = db_idc.zabbix_info + db_project = db_op.project_list + zabi = tools.zabbix_api() + redis_key = 'op_alarm_load_hosts' + token = "" + host_infos = db_zabbix.query.with_entities(db_zabbix.ip, db_zabbix.ssh_port,db_zabbix.hostname,db_zabbix.update_time).filter(and_(db_zabbix.cpu_load > 100, db_zabbix.icmpping == 1)).all() + #循环监控疑似问题服务器 + for infos in host_infos: + try: + host,ssh_port,hostname,update_time=infos + loads = [] + Projects = [] + Others = [] + #获取zabbix监控数据 + if time.strftime('%Y-%m-%d',time.localtime()) in update_time: + for key_ in ('system.cpu.load[all,avg5]', 'system.cpu.num'): + val = zabi.zabbix_history(hostname, key_) + if not val: + val = zabi.zabbix_history(host, key_) + if not val: + val = 0 + loads.append(float(val)) + if loads[0] > 0 and loads[1] > 0: + load = loads[0] / loads[1] * 100 + if load >100: + Key = '%s:%s:%s' %(host,hostname,ssh_port) + RC.hincrby(redis_key,Key) + #符合条件后进行重启操作 + if int(RC.hget(redis_key,Key)) >9: + #判断是否是java程序 + ret = db_project.query.filter(and_(db_project.ip==host,db_project.ssh_port==ssh_port)).all() + if ret: + try: + Ssh = SSH.ssh(ip=host,ssh_port=ssh_port) + except Exception as e: + logging.error(e) + else: + #筛查可重启服务进程 + results = Ssh.Run("ps -aux | sort -k3nr |head -n 1") + if results['stdout']: + results = results['stdout'][0].strip().split() + if results[-1].endswith('-rpc.jar'): + pro_jar = results[-1] + if pro_jar in ['moji-location-rpc.jar']: + Projects.append(pro_jar.split('.')[0]) + else: + Others.append(pro_jar.split('.')[0]) + else: + for line in results: + if '-Dcatalina.home=' in line : + Projects.append(line.strip().split('/')[-1]) + break + if Projects: + for project in Projects: + #重启问题tomcat + result = Ssh.Run("supervisorctl restart {0}".format(project)) + if result['stderr']: + text = ['**线上服务重启:%s**' % host, "CPU持续使用率:{0}%".format(load), + "相关进程:{0}".format(project), '**服务重启失败,需手动处理!**'] + tools.dingding_msg(text, alart_token=token) + else: + text = ['**线上服务重启:%s**' % host, "CPU持续使用率:{0}%".format(load), + "相关进程:{0}".format(project), '**服务重启成功!**'] + tools.dingding_msg(text) + else: + project = ' '.join(results[10:]) + if Others: + project = Others[0] + text = ['**线上服务器预警:%s**' % host, "CPU持续使用率:{0}%".format(load), + "相关进程:{0}".format(project), '**请及时进行处理!**'] + tools.dingding_msg(text,alart_token=token) + finally: + Ssh.Close() + except Exception as e: + logging.error(e) + RC.hincrby(redis_key, 'count') + if int(RC.hget(redis_key, 'count')) >9: + RC.delete(redis_key, ) + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + db_op.DB.session.remove() diff --git a/Modules/__init__.py b/Modules/__init__.py index 8b137891..46434eb2 100644 --- a/Modules/__init__.py +++ b/Modules/__init__.py @@ -1 +1,6 @@ - +#-*- coding: utf-8 -*- +import platform +from Modules import loging +logging = loging.Error() +if platform.python_version().startswith('2.7.'): + logging.error("Python %s is not supported!" %platform.python_version()) diff --git a/Modules/check.py b/Modules/check.py new file mode 100644 index 00000000..71a07f7a --- /dev/null +++ b/Modules/check.py @@ -0,0 +1,181 @@ +#-*- coding: utf-8 -*- +from flask import Flask,request,g,render_template_string,redirect,url_for,flash,session +import datetime +import time +from Modules import db_op,loging,db_idc +import redis +from functools import wraps +import socket +from random import choice +from sqlalchemy import and_,distinct +from collections import defaultdict +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/security.conf') +app.config.from_pyfile('../conf/task.conf') +app.config.from_pyfile('../conf/main.conf') +logging = loging.Error() +DB = SQLAlchemy(app) +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +RC= Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +redis_data = app.config.get('REDIS_DATA') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +white_list = app.config.get('WHITE_LIST') +task_servers = app.config.get('TASK_SERVERS') +HOST = socket.gethostbyname(socket.gethostname()) +def timestamp(i): + ''' + i is 0 days ago + ''' + if i>=1: + t = (datetime.datetime.now() + datetime.timedelta(days=1)) + else: + t = (datetime.datetime.now() - datetime.timedelta(days=1)) + tp = int(time.mktime(t.timetuple())) + return tp +#用户登录鉴权 +def login_required(grade = None): + def login_check(func): + @wraps(func) + def Login(*args, **kwargs): + db_auth = db_op.user_auth + try: + user = Redis.get('OP_user_%s' %request.cookies.get('user')) + openid = Redis.get('OP_openid_%s' %request.cookies.get('openid')) + dingId = Redis.get('OP_dingId_%s' %request.cookies.get('dingId')) + token = Redis.get('OP_token_%s' %request.cookies.get('token')) + Error_Key = 'error_%s' % (token) + except: + return redirect(url_for('logout.logout')) + else: + try: + if user and token and dingId and openid: + if token == Redis.get('OP_verify_%s' %dingId): + g.user = user + g.openid = openid + g.dingId = dingId + g.secret_key = request.cookies.get('secret_key') + g.token = token + val = db_auth.query.with_entities(db_auth.grade).filter(and_(db_auth.dingId == dingId,db_auth.token == token,db_auth.openid == openid)).all() + g.grade = val[0][0].split(',') if val else ('11',) + if str(grade) in g.grade: + grades = g.grade + g.ip = request.headers.get('X-Forwarded-For') + if not g.ip: + g.ip = request.remote_addr + if ',' in g.ip: + g.ip = g.ip.split(',')[0] + session['remote_ip'] = g.ip + Redis.sadd('active_users', dingId) + Redis.expire('active_users', 30) + g.active_users = Redis.scard('active_users') + g.date = time.strftime('%Y-%m-%d', time.localtime()) + g.ym = time.strftime('%Y', time.localtime()) + g.weather = RC_CLUSTER.hgetall('weather_inofs') + #新发现物理服务器 + try: + db_server = db_idc.idc_servers + discovery = db_server.query.with_entities(db_server.ip, db_server.ssh_port).filter( + db_server.status == '新发现').all() + if discovery: + tables = ['机房', 'IP', 'ssh_poort'] + discovery = [list(info) for info in discovery] + for infos in discovery: + infos.insert(0, '未知') + discovery.insert(0, tables) + g.discovery = discovery + except Exception as e: + logging.error(e) + # 生成用户权限对应的页面菜单 + try: + for key in ('navMenu', 'nav_val','submenu', 'sub_val'): + if g.Base_Menu[key]: + pass + except: + DB = db_op.op_menu + nav_val = defaultdict() + sub_val = defaultdict() + navMenu = DB.query.with_entities(distinct(DB.Menu_name)).filter(and_(DB.Menu == 'navMenu', DB.grade.in_(grades))).order_by(DB.Menu_id).all() + navMenu = [Menu[0] for Menu in navMenu] + for Menu in navMenu: + val = DB.query.with_entities(DB.id_name, DB.module_name,DB.action_name).filter(and_(DB.grade.in_(grades),DB.Menu_name==Menu)).order_by(DB.sub_id).all() + if val: + nav_val[Menu] = val + submenu = DB.query.with_entities(distinct(DB.Menu_name)).filter(and_(DB.Menu == 'submenu', DB.grade.in_(grades))).order_by(DB.Menu_id).all() + submenu = [menu[0] for menu in submenu] + for Menu in submenu: + val = DB.query.with_entities(DB.module_name, DB.action_name).filter(and_(DB.grade.in_(grades), DB.Menu_name == Menu)).order_by(DB.sub_id).all() + if val: + sub_val[Menu] = val + g.Base_Menu = {'navMenu': navMenu, 'nav_val': nav_val,'submenu': submenu, 'sub_val': sub_val} + return func(*args, **kwargs) + else: + flash('未授权访问该页面!') + return redirect(url_for('login.login')) + else: + flash('该帐号已在其他地方登陆,请确认账号安全!') + return redirect(url_for('login.login')) + else: + return redirect(url_for('login.login')) + except Exception as e: + Redis.set(Error_Key,str(e)) + return redirect(url_for('error.error')) + finally: + db_op.DB.session.remove() + db_idc.DB.session.remove() + return Login + return login_check +#访问ip限制 +def acl_ip(func): + @wraps(func) + def check_ip(*args, **kwargs): + ip_check = [] + try: + if request.headers['X-Forwarded-For']: + src_ip = request.headers['X-Forwarded-For'] + else: + src_ip = request.remote_addr + except: + src_ip = request.remote_addr + if ',' in src_ip: + src_ip = src_ip.split(',')[0] + for ip in white_list: + if '/' in ip: + mask = ip.split('/')[1] + if mask == '32': + ip = ip.split('.') + if mask == '24': + ip = ip.split('.')[:3] + if mask == '16': + ip = ip.split('.')[:2] + if mask == '8': + ip = ip.split('.')[:1] + if src_ip.startswith('.'.join(ip)): + ip_check.append(True) + else: + if ip == src_ip: + ip_check.append(True) + if not ip_check: + return render_template_string('%s 该IP地址未被授权访问!' % src_ip) + return func(*args, **kwargs) + return check_ip +#任务执行锁 +def proce_lock(func): + @wraps(func) + def LOCK(*args, **kwargs): + try: + if HOST in task_servers: + time.sleep(choice([i for i in range(1,10)])) + if RC.exists('task_%s'%func.__name__): + raise AssertionError + RC.set('task_%s' %func.__name__, HOST) + RC.expire('task_%s' % func.__name__,15) + return func(*args, **kwargs) + else: + raise AssertionError + except: + pass + return LOCK \ No newline at end of file diff --git a/Modules/db_idc.py b/Modules/db_idc.py new file mode 100644 index 00000000..7aea5cc5 --- /dev/null +++ b/Modules/db_idc.py @@ -0,0 +1,352 @@ +#-*- coding: utf-8 -*- +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +class idc_servers(DB.Model): + __tablename__ = 'servers' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + idc_id = DB.Column(DB.Integer) + ip = DB.Column(DB.String(20)) + ssh_port = DB.Column(DB.Integer) + s_ip = DB.Column(DB.String(100)) + host_type = DB.Column(DB.String(20)) + hostname = DB.Column(DB.String(45)) + sn = DB.Column(DB.String(50)) + manufacturer = DB.Column(DB.String(50)) + productname = DB.Column(DB.String(50)) + system = DB.Column(DB.String(50)) + cpu_info = DB.Column(DB.String(50)) + cpu_core = DB.Column(DB.Integer) + mem = DB.Column(DB.String(30)) + disk_count = DB.Column(DB.Integer) + disk_size = DB.Column(DB.String(20)) + idrac = DB.Column(DB.String(30)) + purch_date = DB.Column(DB.String(30)) + expird_date = DB.Column(DB.String(30)) + status = DB.Column(DB.String(8)) + comment = DB.Column(DB.String(30)) + def __init__(self,idc_id,ip,ssh_port,s_ip,host_type,hostname,sn,manufacturer,productname,system,cpu_info,cpu_core,mem,disk_size,disk_count,idrac,purch_date,expird_date,status,comment): + self.idc_id = idc_id + self.ip = ip + self.s_ip = s_ip + self.ssh_port = ssh_port + self.host_type = host_type + self.hostname = hostname + self.sn = sn + self.manufacturer = manufacturer + self.productname = productname + self.system = system + self.cpu_info = cpu_info + self.cpu_core = cpu_core + self.mem = mem + self.disk_count = disk_count + self.disk_size = disk_size + self.idrac = idrac + self.purch_date = purch_date + self.expird_date = expird_date + self.status = status + self.comment = comment + def __repr__(self): + values=(self.idc_id,self.ip,self.ssh_port,self.s_ip,self.host_type,self.hostname,self.sn,self.manufacturer,self.productname,self.system,self.cpu_info,self.cpu_core,self.mem,self.disk_count,self.disk_size,self.idrac,self.purch_date,self.expird_date,self.status,self.comment) + return '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s'%values + +class idc_id(DB.Model): + __tablename__ = 'idc_id' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + aid = DB.Column(DB.String(20)) + cid = DB.Column(DB.String(20)) + def __init__(self,aid,cid): + self.aid = aid + self.cid = cid + def __repr__(self): + values=(self.aid,self.cid) + return '%s,%s'%values + +class third_resource(DB.Model): + __tablename__ = 'third_resource' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + resource_type = DB.Column(DB.String(30)) + cluster_type = DB.Column(DB.String(30)) + ip = DB.Column(DB.String(30)) + ssh_port = DB.Column(DB.Integer) + app_port = DB.Column(DB.Integer) + busi_id = DB.Column(DB.Integer) + department = DB.Column(DB.String(30)) + person = DB.Column(DB.String(30)) + contact = DB.Column(DB.String(30)) + status = DB.Column(DB.String(8)) + update_date = DB.Column(DB.String(45)) + def __init__(self,resource_type,cluster_type,ip,ssh_port,app_port,busi_id,department,person,contact,status,update_date): + self.resource_type = resource_type + self.cluster_type = cluster_type + self.ip = ip + self.ssh_port = ssh_port + self.app_port = app_port + self.busi_id = busi_id + self.department = department + self.person = person + self.contact = contact + self.status = status + self.update_date = update_date + def __repr__(self): + values=(self.resource_type,self.cluster_type,self.ip,self.ssh_port,self.app_port,self.busi_id,self.department,self.person,self.contact,self.status,self.update_date) + return '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s'%values + +class idc_networks(DB.Model): + __tablename__ = 'networks' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + idc_id = DB.Column(DB.Integer) + type = DB.Column(DB.String(20)) + ip = DB.Column(DB.String(30)) + redundance = DB.Column(DB.String(8)) + purch_date = DB.Column(DB.String(20)) + expird_date = DB.Column(DB.String(20)) + status = DB.Column(DB.String(8)) + comment = DB.Column(DB.String(30)) + def __init__(self,idc_id,type,ip,redundance,purch_date,expird_date,status,comment): + self.idc_id = idc_id + self.type = type + self.ip = ip + self.redundance = redundance + self.purch_date = purch_date + self.expird_date = expird_date + self.status = status + self.comment = comment + def __repr__(self): + values=(self.idc_id,self.type,self.ip,self.redundance,self.purch_date,self.expird_date,self.status,self.comment) + return '%s,%s,%s,%s,%s,%s,%s,%s'%values + +class idc_store(DB.Model): + __tablename__ = 'store' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + idc_id = DB.Column(DB.Integer) + type = DB.Column(DB.String(30)) + ip = DB.Column(DB.String(20)) + purch_date = DB.Column(DB.String(20)) + expird_date = DB.Column(DB.String(20)) + status = DB.Column(DB.String(8)) + comment = DB.Column(DB.String(30)) + def __init__(self,idc_id,type,ip,purch_date,expird_date,status,comment): + self.idc_id = idc_id + self.type = type + self.ip = ip + self.purch_date = purch_date + self.expird_date = expird_date + self.status = status + self.comment = comment + def __repr__(self): + values=(self.idc_id,self.type,self.ip,self.purch_date,self.expird_date,self.status,self.comment) + return '%s,%s,%s,%s,%s,%s,%s'%values + +class idc_mysqldb(DB.Model): + __tablename__ = 'mysqldb' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + ip = DB.Column(DB.String(20)) + port = DB.Column(DB.Integer) + db = DB.Column(DB.String(500)) + master = DB.Column(DB.String(8)) + slave = DB.Column(DB.String(8)) + Master_Host = DB.Column(DB.String(20),default=None) + Master_User = DB.Column(DB.String(20),default=None) + Master_Port = DB.Column(DB.String(8),default=None) + def __init__(self,ip,port,db,master,slave,Master_Host,Master_User,Master_Port): + self.ip = ip + self.port = port + self.db = db + self.master = master + self.slave = slave + self.Master_Host = Master_Host + self.Master_User = Master_User + self.Master_Port = Master_Port + def __repr__(self): + values=(self.ip,self.port,self.db,self.master,self.slave,self.Master_Host,self.Master_User,self.Master_Port) + return '%s,%i,%s,%s,%s,%s,%s,%s' %values + +class idc_tableinfo(DB.Model): + __tablename__ = 'tableinfo' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + ip = DB.Column(DB.String(20)) + port = DB.Column(DB.Integer) + database_name = DB.Column(DB.String(50)) + table_name = DB.Column(DB.String(50)) + Engine_name = DB.Column(DB.String(50)) + Rows = DB.Column(DB.Integer) + size = DB.Column(DB.String(50)) + Charset = DB.Column(DB.String(50)) + version = DB.Column(DB.String(50)) + update_time = DB.Column(DB.DateTime) + def __init__(self,ip,port,database_name,table_name,Engine_name,Rows,size,Charset,version,update_time): + self.ip = ip + self.port = port + self.database_name = database_name + self.table_name = table_name + self.Engine_name = Engine_name + self.Rows = Rows + self.size = size + self.Charset = Charset + self.version = version + self.update_time = update_time + def __repr__(self): + values=(self.ip,self.port,self.database_name,self.table_name,self.Engine_name,self.Rows,self.size,self.Charset,self.version,self.update_time) + return '%s,%i,%s,%s,%s,%i,%s,%s,%s,%s'%values + +class resource_ip(DB.Model): + __tablename__ = 'resource_ip' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + aid = DB.Column(DB.String(20)) + network = DB.Column(DB.String(30)) + def __init__(self,aid,network): + self.aid = aid + self.network = network + def __repr__(self): + values=(self.aid,self.network) + return '%s,%s'%values + +class other_resource(DB.Model): + __tablename__ = 'other_resource' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + type = DB.Column(DB.String(20)) + domain = DB.Column(DB.String(50)) + provider = DB.Column(DB.String(45)) + def __init__(self,type,domain,provider): + self.type = type + self.domain = domain + self.provider = provider + def __repr__(self): + values=(self.type,self.domain,self.provider) + return '%s,%s,%s'%values + +class zabbix_info(DB.Model): + __tablename__ = 'zabbix_info' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + ip = DB.Column(DB.String(20)) + ssh_port = DB.Column(DB.Integer) + hostname = DB.Column(DB.String(50)) + icmpping = DB.Column(DB.Integer) + cpu_load = DB.Column(DB.Float) + mem_use = DB.Column(DB.Float) + disk_io = DB.Column(DB.Float) + openfile = DB.Column(DB.Integer) + disk_path = DB.Column(DB.String(200)) + network = DB.Column(DB.String(100)) + update_time = DB.Column(DB.String(45)) + def __init__(self,ip,ssh_port,hostname,icmpping,cpu_load,mem_use,disk_io,openfile,disk_path,network,update_time): + self.ip = ip + self.ssh_port = ssh_port + self.hostname = hostname + self.icmpping =icmpping + self.cpu_load = cpu_load + self.mem_use = mem_use + self.disk_io = disk_io + self.openfile = openfile + self.disk_path = disk_path + self.network = network + self.update_time = update_time + def __repr__(self): + values=(self.ip,self.ssh_port,self.hostname,self.icmpping,self.cpu_load,self.mem_use,self.disk_io,self.openfile,self.disk_path,self.network,self.update_time) + return '%s,%i,%s,%i,%i,%i,%i,%i,%s,%s,%s'%values + +class influxdb_alarm(DB.Model): + __tablename__ = 'influxdb_alarm' + __bind_key__ = 'idc' + id = DB.Column(DB.Integer, primary_key=True, autoincrement=True) + host = DB.Column(DB.String(50)) + uri = DB.Column(DB.String(100)) + avg_resp = DB.Column(DB.Float) + resp_100 = DB.Column(DB.Float) + resp_200 = DB.Column(DB.Float) + resp_500 = DB.Column(DB.Float) + resp_1000 = DB.Column(DB.Float) + status_4xx = DB.Column(DB.Float) + status_5xx = DB.Column(DB.Float) + year = DB.Column(DB.String(20)) + def __init__(self, host, uri, avg_resp, resp_100, resp_200, resp_500, resp_1000, status_4xx, status_5xx, year): + self.host = host + self.uri = uri + self.avg_resp = avg_resp + self.resp_100 = resp_100 + self.resp_200 = resp_200 + self.resp_500 = resp_500 + self.resp_1000 = resp_1000 + self.status_4xx = status_4xx + self.status_5xx = status_5xx + self.year = year + def __repr__(self): + values = (self.host, self.uri, self.avg_resp, self.resp_100, self.resp_200, self.resp_500, self.resp_1000, + self.status_4xx, self.status_5xx, self.year) + return '%s,%s,%i,%i,%i,%i,%i,%i,%i,%s' % values + +class crontabs(DB.Model): + __tablename__ = 'crontabs' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + cron = DB.Column(DB.String(45)) + action = DB.Column(DB.String(500)) + server_id = DB.Column(DB.Integer) + update_date = DB.Column(DB.String(45)) + def __init__(self,cron,action,server_id,update_time): + self.cron = cron + self.action = action + self.server_id = server_id + self.update_date = update_time + def __repr__(self): + values=(self.cron,self.action,self.server_id,self.update_date) + return '%s,%s,%i,%s'%values + +class hosts(DB.Model): + __tablename__ = 'hosts' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + host = DB.Column(DB.String(45)) + hostname = DB.Column(DB.String(100)) + server_id = DB.Column(DB.Integer) + update_date = DB.Column(DB.String(45)) + def __init__(self,host,hostname,server_id,update_time): + self.host = host + self.hostname = hostname + self.server_id = server_id + self.update_date = update_time + def __repr__(self): + values=(self.host,self.hostname,self.server_id,self.update_date) + return '%s,%s,%i,%s'%values + +class redis_info(DB.Model): + __tablename__ = 'redis_info' + __bind_key__='idc' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + server_id = DB.Column(DB.String(50)) + port = DB.Column(DB.Integer) + masterauth = DB.Column(DB.String(50)) + requirepass = DB.Column(DB.String(50)) + master = DB.Column(DB.String(8)) + slave = DB.Column(DB.String(8)) + cluster = DB.Column(DB.String(8)) + Master_Host = DB.Column(DB.String(20)) + Master_Port = DB.Column(DB.String(8)) + update_date = DB.Column(DB.String(45)) + def __init__(self,server_id,port,masterauth,requirepass,master,slave,cluster,Master_host,Master_Port,update_date): + self.server_id = server_id + self.port = port + self.masterauth = masterauth + self.requirepass = requirepass + self.master = master + self.slave = slave + self.cluster = cluster + self.Master_Host = Master_host + self.Master_Port = Master_Port + self.update_date = update_date + def __repr__(self): + values=(self.server_id,self.port,self.masterauth,self.requirepass,self.master,self.slave,self.cluster,self.Master_Host,self.Master_Port,self.update_date) + return '%i,%i,%s,%s,%s,%s,%s,%s,%i,%s' %values \ No newline at end of file diff --git a/Modules/db_op.py b/Modules/db_op.py new file mode 100644 index 00000000..ea6fc113 --- /dev/null +++ b/Modules/db_op.py @@ -0,0 +1,392 @@ +#-*- coding: utf-8 -*- +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +class user_auth(DB.Model): + __tablename__ = 'user_auth' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + name = DB.Column(DB.String(20)) + openid = DB.Column(DB.String(100)) + dingId = DB.Column(DB.String(100)) + grade = DB.Column(DB.String(20)) + token = DB.Column(DB.String(45)) + update_time = DB.Column(DB.String(20)) + def __init__(self,name,openid,dingId,grade,token,update_time): + self.name = name + self.openid = openid + self.dingId = dingId + self.grade = grade + self.token = token + self.update_time = update_time + def __repr__(self): + values=(self.name,self.openid,self.dingId,self.grade,self.token,self.update_time) + return '%s,%s,%s,%s,%s,%s' % values + +class project_list(DB.Model): + __tablename__ = 'project_list' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + resource = DB.Column(DB.String(30)) + project = DB.Column(DB.String(30),index=True) + domain = DB.Column(DB.String(30)) + ip = DB.Column(DB.String(30),index=True) + ssh_port = DB.Column(DB.Integer) + app_port = DB.Column(DB.String(30)) + business_id = DB.Column(DB.Integer) + sys_args = DB.Column(DB.String(30)) + env = DB.Column(DB.String(8)) + gray = DB.Column(DB.String(8)) + status = DB.Column(DB.String(8)) + update_date = DB.Column(DB.String(45)) + def __init__(self,resource,project,domain,ip,ssh_port,app_port,business_id,sys_args,env,gray,status,update_date): + self.resource = resource + self.project = project + self.domain = domain + self.ip = ip + self.ssh_port = ssh_port + self.app_port = app_port + self.business_id = business_id + self.sys_args = sys_args + self.env = env + self.gray = gray + self.status = status + self.update_date = update_date + def __repr__(self): + values=(self.resource,self.project,self.domain,self.ip,self.ssh_port,self.app_port,self.business_id,self.sys_args,self.env,self.gray,self.status,self.update_date) + return '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % values + +class publish_records(DB.Model): + __tablename__ = 'publish_records' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + date = DB.Column(DB.String(20)) + time = DB.Column(DB.String(20)) + user = DB.Column(DB.String(20)) + project = DB.Column(DB.String(50)) + version = DB.Column(DB.String(50)) + package_url = DB.Column(DB.String(500)) + describe = DB.Column(DB.String(500)) + package_md5 = DB.Column(DB.String(500)) + package_type = DB.Column(DB.String(20)) + publish_type = DB.Column(DB.String(20)) + restart = DB.Column(DB.String(20)) + check_url = DB.Column(DB.String(500)) + callback_url = DB.Column(DB.String(500)) + token = DB.Column(DB.String(500)) + execute = DB.Column(DB.String(20)) + gray = DB.Column(DB.Integer) + channel = DB.Column(DB.String(20)) + result = DB.Column(DB.String(20)) + flow_number = DB.Column(DB.String(45)) + def __init__(self,date,time,user,project,version,package_url,describe,package_md5,package_type, + publish_type,restart,check_url,callback_url,token,execute,gray,channel,result,flow_number): + self.date = date + self.time = time + self.user = user + self.project = project + self.version = version + self.package_url = package_url + self.describe = describe + self.package_md5 = package_md5 + self.package_type = package_type + self.publish_type = publish_type + self.restart = restart + self.check_url = check_url + self.callback_url = callback_url + self.token = token + self.execute = execute + self.gray = gray + self.channel = channel + self.result = result + self.flow_number = flow_number + def __repr__(self): + values=(self.date,self.time,self.user,self.project,self.version,self.package_url,self.describe,self.package_md5, + self.package_type,self.publish_type,self.restart,self.check_url,self.callback_url,self.token, + self.execute,self.gray,self.channel,self.result,self.flow_number) + return '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%i,%s,%s,%s'%values + +class op_log(DB.Model): + __tablename__ = 'op_log' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + date = DB.Column(DB.String(20)) + time = DB.Column(DB.String(20)) + ip = DB.Column(DB.String(20)) + user = DB.Column(DB.String(20)) + access = DB.Column(DB.String(200)) + def __init__(self,date,time,ip,user,access): + self.date = date + self.time = time + self.ip = ip + self.user = user + self.access = access + def __repr__(self): + values=(self.date,self.time,self.ip,self.user,self.access) + return '%s,%s,%s,%s,%s'%values + +class dns_innr(DB.Model): + __tablename__ = 'dns_innr' + __bind_key__ = 'op' + id = DB.Column(DB.Integer, primary_key=True, autoincrement=True) + domain = DB.Column(DB.String(30)) + field = DB.Column(DB.String(50)) + Type = DB.Column(DB.String(30)) + ip = DB.Column(DB.String(100)) + stats = DB.Column(DB.String(8)) + system = DB.Column(DB.String(8)) + def __init__(self,domain,field,Type,ip,stats,system): + self.domain = domain + self.field = field + self.Type = Type + self.ip = ip + self.stats = stats + self.system = system + + def __repr__(self): + values = (self.domain,self.field,self.Type, self.ip,self.stats,self.system) + return '%s,%s,%s,%s,%s,%s' % values + +class op_menu(DB.Model): + __tablename__ = 'op_menu' + __bind_key__ = 'op' + id = DB.Column(DB.Integer, primary_key=True, autoincrement=True) + Menu = DB.Column(DB.String(10)) + Menu_id = DB.Column(DB.Integer) + sub_id = DB.Column(DB.Integer) + Menu_name = DB.Column(DB.String(10)) + id_name = DB.Column(DB.String(20)) + module_name = DB.Column(DB.String(50)) + action_name = DB.Column(DB.String(50)) + grade = DB.Column(DB.Integer) + def __init__(self,Menu,Menu_id,sub_id,Menu_name,id_name,module_name,action_name,grade): + self.Menu = Menu + self.Menu_id = Menu_id + self.sub_id = sub_id + self.Menu_name = Menu_name + self.id_name = id_name + self.module_name = module_name + self.action_name = action_name + self.grade = grade + + def __repr__(self): + values = (self.Menu,self.Menu_id,self.sub_id,self.Menu_name,self.id_name,self.module_name,self.action_name,self.grade) + return '%s,%s,%s,%s,%s,%s,%s,%s' % values + +class apscheduler_jobs(DB.Model): + __tablename__ = 'apscheduler_jobs' + __bind_key__ = 'op' + id = DB.Column(DB.String(200),primary_key=True) + next_run_time = DB.Column(DB.BigInteger) + job_state = DB.Column(DB.BLOB) + def __init__(self,id,next_run_time,job_state): + self.id = id + self.next_run_time = next_run_time + with open(job_state,'rb') as f: + self.job_state = f.read() + def __repr__(self): + values = (self.id,self.next_run_time,self.job_state) + return '%s,%s,%r' % values + +class user_approval(DB.Model): + __tablename__ = 'user_approval' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + name = DB.Column(DB.String(20)) + openid = DB.Column(DB.String(50)) + dingId = DB.Column(DB.String(50)) + apply_time = DB.Column(DB.String(20)) + approval_time = DB.Column(DB.String(20)) + approval_person = DB.Column(DB.String(20)) + apply_grade = DB.Column(DB.Integer) + status = DB.Column(DB.String(20)) + def __init__(self,name,openid,dingId,apply_time,approval_time,approval_person,apply_grade,status): + self.name = name + self.openid = openid + self.dingId = dingId + self.apply_time = apply_time + self.approval_time = approval_time + self.approval_person = approval_person + self.apply_grade = apply_grade + self.status = status + def __repr__(self): + values=(self.name,self.openid,self.dingId,self.apply_time,self.approval_time,self.approval_person,self.apply_grade,self.status) + return '%s,%s,%s,%s,%s,%s,%s,%s' % values + +class permission(DB.Model): + __tablename__ = 'permission' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + auth = DB.Column(DB.String(20)) + authid = DB.Column(DB.Integer) + def __init__(self,auth,authid): + self.auth = auth + self.authid = authid + def __repr__(self): + values=(self.auth,self.authid) + return '%s,%s' % values + +class business(DB.Model): + __tablename__ = 'business' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + business = DB.Column(DB.String(50)) + describe = DB.Column(DB.String(50)) + person = DB.Column(DB.String(30)) + contact = DB.Column(DB.String(30)) + def __init__(self,business,describe,person,contact): + self.business = business + self.describe = describe + self.person = person + self.contact = contact + def __repr__(self): + values=(self.business,self.describe,self.person,self.contact) + return '%s,%s,%s,%s' % values + +class project_third(DB.Model): + __tablename__ = 'project_third' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + project = DB.Column(DB.String(30)) + project_id = DB.Column(DB.Integer) + third_id = DB.Column(DB.Integer) + def __init__(self,project,project_id,third_id): + self.project = project + self.project_id = project_id + self.third_id = third_id + def __repr__(self): + values=(self.project,self.project_id,self.third_id) + return '%s,%s,%s'%values + +class business_monitor(DB.Model): + __tablename__ = 'business_monitor' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + url = DB.Column(DB.String(50)) + method = DB.Column(DB.String(8)) + project = DB.Column(DB.String(50)) + version = DB.Column(DB.String(20)) + code = DB.Column(DB.Integer) + error_ip = DB.Column(DB.String(50)) + update_time = DB.Column(DB.String(20)) + alarm_time = DB.Column(DB.String(20)) + lock = DB.Column(DB.Integer) + alart_token = DB.Column(DB.String(500)) + def __init__(self,url,method,project,version,code,error_ip,update_time,alarm_time,lock,alart_token): + self.url = url + self.method = method + self.project = project + self.version = version + self.code = code + self.error_ip = error_ip + self.update_time = update_time + self.alarm_time = alarm_time + self.lock = lock + self.alart_token = alart_token + def __repr__(self): + values=(self.url,self.method,self.project,self.version,self.code,self.error_ip,self.update_time,self.alarm_time,self.lock,self.alart_token) + return '%s,%s,%s,%s,%i,%s,%s,%s,%i,%s'%values + +class platform_token(DB.Model): + __tablename__ = 'platform_token' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + platform = DB.Column(DB.String(50)) + channel = DB.Column(DB.String(30)) + token = DB.Column(DB.String(100)) + award = DB.Column(DB.String(30)) + expire = DB.Column(DB.String(30)) + def __init__(self,platform,channel,token,award,expire): + self.platform = platform + self.channel = channel + self.token = token + self.award = award + self.expire = expire + def __repr__(self): + values=(self.platform,self.channel,self.token,self.award,self.expire) + return '%s,%s,%s,%s,%s'%values + +class publish_log(DB.Model): + __tablename__ = 'publish_log' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + fid = DB.Column(DB.Integer) + record = DB.Column(DB.TEXT) + def __init__(self,fid,record): + self.fid = fid + self.record = record + def __repr__(self): + values=(self.fid,self.record) + return '%s,%s'%values + +class project_other(DB.Model): + __tablename__ = 'project_other' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + lable = DB.Column(DB.String(45)) + project = DB.Column(DB.String(100)) + server_id = DB.Column(DB.Integer) + business_id = DB.Column(DB.Integer) + update_date = DB.Column(DB.String(45)) + def __init__(self,lable,project,server_id,business_id,update_time): + self.lable = lable + self.project = project + self.server_id = server_id + self.business_id = business_id + self.update_date = update_time + def __repr__(self): + values=(self.labe,self.project,self.server_id,self.business_id,self.update_date) + return '%s,%s,%i,%i,%s'%values + +class k8s_deploy(DB.Model): + __tablename__ = 'k8s_deploy' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + project = DB.Column(DB.String(50)) + deployment = DB.Column(DB.String(45)) + image = DB.Column(DB.String(100)) + war = DB.Column(DB.String(45)) + container_port = DB.Column(DB.String(45)) + replicas = DB.Column(DB.Integer) + re_requests = DB.Column(DB.String(45)) + re_limits = DB.Column(DB.String(45)) + action = DB.Column(DB.String(45)) + update_date = DB.Column(DB.String(45)) + update_time = DB.Column(DB.String(45)) + def __init__(self,project,deployment,image,war,container_port,replicas,re_requests,re_limits, + action,update_date,update_time): + self.project = project + self.deployment = deployment + self.image = image + self.war = war + self.container_port = container_port + self.replicas = replicas + self.re_requests = re_requests + self.re_limits = re_limits + self.action = action + self.update_date = update_date + self.update_time = update_time + def __repr__(self): + values=(self.project,self.deployment,self.image,self.war,self.container_port,self.replicas,self.re_requests,self.re_limits, + self.action,self.update_date,self.update_time) + return '%s,%s,%s,%s,%i,%i,%s,%s,%s,%s,%s'%values +class k8s_ingress(DB.Model): + __tablename__ = 'k8s_ingress' + __bind_key__='op' + id = DB.Column(DB.Integer, primary_key=True,autoincrement=True) + name = DB.Column(DB.String(50),default='nginx-ingress') + namespace = DB.Column(DB.String(45),default='default') + domain = DB.Column(DB.String(100)) + serviceName = DB.Column(DB.String(100)) + servicePort = DB.Column(DB.Integer) + def __init__(self,name,namespace,domain,serviceName,servicePort): + self.name = name + self.namespace = namespace + self.domain = domain + self.serviceName = serviceName + self.servicePort = servicePort + def __repr__(self): + values=(self.name,self.namespace,self.domain,self.serviceName,self.servicePort) + return '%s,%s,%s,%s,%i'%values \ No newline at end of file diff --git a/Modules/init.py b/Modules/init.py new file mode 100644 index 00000000..48e22185 --- /dev/null +++ b/Modules/init.py @@ -0,0 +1,17 @@ +#-*- coding: utf-8 -*- +from flask import Flask +from flask.templating import Environment +from pyecharts.engine import ECHAERTS_TEMPLATE_FUNCTIONS +from pyecharts.conf import PyEchartsConfig +from flask_limiter import Limiter +from flask_limiter.util import get_ipaddr +app = Flask(__name__) +class web_limiter(object): + def __init__(self,global_limits=["1000/minute"]): + self.global_limits = global_limits + self.limiter = Limiter(app,key_func=get_ipaddr,global_limits=self.global_limits) +class FlaskEchartsEnvironment(Environment): + def __init__(self, *args, **kwargs): + super(FlaskEchartsEnvironment, self).__init__(*args, **kwargs) + self.pyecharts_config = PyEchartsConfig(jshost='https://cdnjs.cloudflare.com/ajax/libs/echarts/4.1.0') + self.globals.update(ECHAERTS_TEMPLATE_FUNCTIONS) \ No newline at end of file diff --git a/Modules/ip_adress.py b/Modules/ip_adress.py new file mode 100644 index 00000000..cf9fb6b0 --- /dev/null +++ b/Modules/ip_adress.py @@ -0,0 +1,244 @@ +# -*- coding:utf-8 -*- +import struct, io, socket, sys +from Modules import loging +from flask import Flask +app = Flask(__name__) +logging = loging.Error() +dbFile = "%s/../conf/ip.db" %app.root_path +class Ip2Region(object): + __INDEX_BLOCK_LENGTH = 12 + __TOTAL_HEADER_LENGTH = 8192 + __f = None + __headerSip = [] + __headerPtr = [] + __headerLen = 0 + __indexSPtr = 0 + __indexLPtr = 0 + __indexCount = 0 + __dbBinStr = '' + + def __init__(self, dbfile): + self.initDatabase(dbfile) + + def memorySearch(self, ip): + """ + " memory search method + " param: ip + """ + if not ip.isdigit(): ip = self.ip2long(ip) + + if self.__dbBinStr == '': + self.__dbBinStr = self.__f.read() # read all the contents in file + self.__indexSPtr = self.getLong(self.__dbBinStr, 0) + self.__indexLPtr = self.getLong(self.__dbBinStr, 4) + self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) / self.__INDEX_BLOCK_LENGTH) + 1 + + l, h, dataPtr = (0, self.__indexCount, 0) + while l <= h: + m = int((l + h) >> 1) + p = self.__indexSPtr + m * self.__INDEX_BLOCK_LENGTH + sip = self.getLong(self.__dbBinStr, p) + + if ip < sip: + h = m - 1 + else: + eip = self.getLong(self.__dbBinStr, p + 4) + if ip > eip: + l = m + 1; + else: + dataPtr = self.getLong(self.__dbBinStr, p + 8) + break + + if dataPtr == 0: raise Exception("Data pointer not found") + + return self.returnData(dataPtr) + + def binarySearch(self, ip): + """ + " binary search method + " param: ip + """ + if not ip.isdigit(): ip = self.ip2long(ip) + + if self.__indexCount == 0: + self.__f.seek(0) + superBlock = self.__f.read(8) + self.__indexSPtr = self.getLong(superBlock, 0) + self.__indexLPtr = self.getLong(superBlock, 4) + self.__indexCount = int((self.__indexLPtr - self.__indexSPtr) / self.__INDEX_BLOCK_LENGTH) + 1 + + l, h, dataPtr = (0, self.__indexCount, 0) + while l <= h: + m = int((l + h) >> 1) + p = m * self.__INDEX_BLOCK_LENGTH + + self.__f.seek(self.__indexSPtr + p) + buffer = self.__f.read(self.__INDEX_BLOCK_LENGTH) + sip = self.getLong(buffer, 0) + if ip < sip: + h = m - 1 + else: + eip = self.getLong(buffer, 4) + if ip > eip: + l = m + 1 + else: + dataPtr = self.getLong(buffer, 8) + break + + if dataPtr == 0: raise Exception("Data pointer not found") + + return self.returnData(dataPtr) + + def btreeSearch(self, ip): + """ + " b-tree search method + " param: ip + """ + if not ip.isdigit(): ip = self.ip2long(ip) + + if len(self.__headerSip) < 1: + headerLen = 0 + # pass the super block + self.__f.seek(8) + # read the header block + b = self.__f.read(self.__TOTAL_HEADER_LENGTH) + # parse the header block + for i in range(0, len(b), 8): + sip = self.getLong(b, i) + ptr = self.getLong(b, i + 4) + if ptr == 0: + break + self.__headerSip.append(sip) + self.__headerPtr.append(ptr) + headerLen += 1 + self.__headerLen = headerLen + + l, h, sptr, eptr = (0, self.__headerLen, 0, 0) + while l <= h: + m = int((l + h) >> 1) + + if ip == self.__headerSip[m]: + if m > 0: + sptr = self.__headerPtr[m - 1] + eptr = self.__headerPtr[m] + else: + sptr = self.__headerPtr[m] + eptr = self.__headerPtr[m + 1] + break + + if ip < self.__headerSip[m]: + if m == 0: + sptr = self.__headerPtr[m] + eptr = self.__headerPtr[m + 1] + break + elif ip > self.__headerSip[m - 1]: + sptr = self.__headerPtr[m - 1] + eptr = self.__headerPtr[m] + break + h = m - 1 + else: + if m == self.__headerLen - 1: + sptr = self.__headerPtr[m - 1] + eptr = self.__headerPtr[m] + break + elif ip <= self.__headerSip[m + 1]: + sptr = self.__headerPtr[m] + eptr = self.__headerPtr[m + 1] + break + l = m + 1 + + if sptr == 0: raise Exception("Index pointer not found") + + indexLen = eptr - sptr + self.__f.seek(sptr) + index = self.__f.read(indexLen + self.__INDEX_BLOCK_LENGTH) + + l, h, dataPrt = (0, int(indexLen / self.__INDEX_BLOCK_LENGTH), 0) + while l <= h: + m = int((l + h) >> 1) + offset = int(m * self.__INDEX_BLOCK_LENGTH) + sip = self.getLong(index, offset) + + if ip < sip: + h = m - 1 + else: + eip = self.getLong(index, offset + 4) + if ip > eip: + l = m + 1; + else: + dataPrt = self.getLong(index, offset + 8) + break + + if dataPrt == 0: raise Exception("Data pointer not found") + + return self.returnData(dataPrt) + + def initDatabase(self, dbfile): + """ + " initialize the database for search + " param: dbFile + """ + try: + self.__f = io.open(dbfile, "rb") + except IOError as e: + print("[Error]: %s" % e) + sys.exit() + + def returnData(self, dataPtr): + """ + " get ip data from db file by data start ptr + " param: dsptr + """ + dataLen = (dataPtr >> 24) & 0xFF + dataPtr = dataPtr & 0x00FFFFFF + + self.__f.seek(dataPtr) + data = self.__f.read(dataLen) + + return { + "city_id": self.getLong(data, 0), + "region": data[4:] + } + + def ip2long(self, ip): + _ip = socket.inet_aton(ip) + return struct.unpack("!L", _ip)[0] + + def isip(self, ip): + p = ip.split(".") + + if len(p) != 4: return False + for pp in p: + if not pp.isdigit(): return False + if len(pp) > 3: return False + if int(pp) > 255: return False + + return True + + def getLong(self, b, offset): + if len(b[offset:offset + 4]) == 4: + return struct.unpack('I', b[offset:offset + 4])[0] + return 0 + + def close(self): + if self.__f != None: + self.__f.close() + + self.__dbBinStr = None + self.__headerPtr = None + self.__headerSip = None + +def Search(ip): + searcher = Ip2Region(dbFile) + try: + data = searcher.btreeSearch(ip) + if isinstance(data,dict): + if 'region' in data.keys(): + data = ",".join(data['region'].split('|')).replace(',0','') + if len(data.split(',')) >3: + data = ','.join(data.split(',')[2:]) + return data + except: + return None + finally: + searcher.close() diff --git a/Modules/k8s_resource.py b/Modules/k8s_resource.py new file mode 100644 index 00000000..e1668e41 --- /dev/null +++ b/Modules/k8s_resource.py @@ -0,0 +1,424 @@ +#-*- coding: utf-8 -*- +from flask import Flask +from Modules import loging,tools,db_op +from kubernetes import client +import os +import shutil +import docker +import oss2 +import time +import redis +from sqlalchemy import and_,desc +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/oss.conf') +app.config.from_pyfile('../conf/docker.conf') +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +docker_user = app.config.get('USER') +docker_password = app.config.get('PASSWORD') +docker_base_url = app.config.get('BASE_URL') +dockerfile_path = app.config.get('DOCKERFILE_PATH') +oss_id = app.config.get('ID') +oss_key = app.config.get('KEY') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +logging = loging.Error() +config,contexts,config_file = tools.k8s_conf() +namespace = "default" +def download_war(dm_name,version,redis_key): + #下载对应项目的最新war包 + try: + #包名需要规范 + Redis.lpush(redis_key, '%s-%s.war package download from oss ......' %(dm_name,version)) + auth = oss2.Auth(oss_id, oss_key) + bucket = oss2.Bucket(auth, 'oss-cn-beijing.aliyuncs.com', 'mojiops') + file_war = None + for obj in oss2.ObjectIterator(bucket, prefix='tomcat-'): + if '%s-%s'%(dm_name,version) in obj.key: + file_war = obj.key + break + if not os.path.exists('%s/%s' %(dockerfile_path,dm_name)): + os.mkdir('%s/%s' %(dockerfile_path,dm_name)) + if file_war: + object = file_war.split('/')[-1] + project_path = '%s/%s/%s.war' %(dockerfile_path,dm_name,dm_name.split('-')[0]) + if os.path.exists(project_path): + os.remove(project_path) + #尝试3次下载 + for i in range(3): + try: + oss2.resumable_download(bucket, file_war, project_path) + except Exception as e: + logging.error(e) + else: + if os.path.exists(project_path): + #生成dockerfile文件 + # + Redis.lpush(redis_key, '%s package download success!' % object) + return object + else: + Redis.lpush(redis_key, '%s package not fond!' % object) + return None + if not os.path.exists(project_path): + Redis.lpush(redis_key, '%s package download fail!' % object) + return None + except Exception as e: + logging.error(e) + Redis.lpush(redis_key, '%s package download fail!' % object) + return None + +def make_image(image,redis_key): + try: + Redis.lpush(redis_key, 'start build image %s......' % image) + dockerfile = "%s/%s" %(dockerfile_path,image.split('/')[-1].split(':')[0]) + if os.path.exists(dockerfile): + try: + client = docker.APIClient(base_url=docker_base_url) + response = [line for line in client.build(path=dockerfile, rm=True, tag=image)] + result = eval(response[-1]) + if 'Successfully' in str(result): + Redis.lpush(redis_key,"docker build %s success!" %image) + else: + Redis.lpush(redis_key,'fail:%s'%result) + return False + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + else: + try: + response = [line for line in client.push(image, stream=True,auth_config={'username':docker_user,'password':docker_password})] + result = eval(response[-1])['aux']['Tag'] + version = image.split(':')[-1] + if version == result: + Redis.lpush(redis_key,"docker push %s success!" % image) + return True + else: + Redis.lpush(redis_key, 'fail:%s' %result) + return False + except Exception as e: + logging.error(e) + Redis.lpush(redis_key, 'fail:%s' %e) + return False + + else: + Redis.lpush(redis_key,'dockerfile %s path not exists!' %dockerfile, 'fail') + return False + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + return False + +class k8s_object(object): + def __init__(self,dm_name,image,container_port,replicas,re_requests={},re_limits={}): + config.load_kube_config(config_file, context=contexts[0]) + self.config_file = config_file + self.dm_name = dm_name + self.image = image + self.container_port = container_port + self.replicas = replicas + self.re_requests = {'cpu':2,'memory': '4G'} + self.re_limits = {'cpu':6,'memory': '8G'} + if re_requests and re_limits: + self.re_requests = re_requests + self.re_limits = re_limits + def export_deployment(self): + # Configureate Pod template container + container = client.V1Container( + name=self.dm_name, + image=self.image, + ports=[client.V1ContainerPort(container_port=int(port)) for port in self.container_port], + image_pull_policy='Always', + env= [client.V1EnvVar(name='LANG',value='en_US.UTF-8'), + client.V1EnvVar(name='LC_ALL', value='en_US.UTF-8') + ], + resources=client.V1ResourceRequirements(limits=self.re_limits, + requests=self.re_requests), + volume_mounts = [client.V1VolumeMount(mount_path='/opt/logs',name='logs')], + liveness_probe=client.V1Probe(initial_delay_seconds=5, + tcp_socket=client.V1TCPSocketAction(port=int(self.container_port[0])) + ) + ) + # Create and configurate a spec section + secrets = client.V1LocalObjectReference('registrysecret') + volume = client.V1Volume(name='logs', host_path=client.V1HostPathVolumeSource(path='/opt/logs')) + template = client.V1PodTemplateSpec( + metadata=client.V1ObjectMeta(labels={"project": self.dm_name}), + spec=client.V1PodSpec(containers=[container], + image_pull_secrets=[secrets],volumes=[volume]) + ) + selector = client.V1LabelSelector(match_labels={"project": self.dm_name}) + # Create the specification of deployment + spec = client.ExtensionsV1beta1DeploymentSpec( + replicas=int(self.replicas), + template=template, + selector=selector, + min_ready_seconds=3 + ) + # Instantiate the deployment object + deployment = client.ExtensionsV1beta1Deployment( + api_version="extensions/v1beta1", + kind="Deployment", + metadata=client.V1ObjectMeta(name=self.dm_name), + spec=spec) + return deployment + + def export_service(self): + ports = [client.V1ServicePort(port=int(port),target_port=int(port)) for port in self.container_port] + spec = client.V1ServiceSpec(ports=ports,selector={'project':self.dm_name}) + service = client.V1Service( + api_version = 'v1', + kind = 'Service', + metadata=client.V1ObjectMeta(name=self.dm_name), + spec=spec) + return service + + def export_ingress(self,ingress_domain,ingress_port): + try: + Rules = [] + db_ingress = db_op.k8s_ingress + Rules_infos = db_ingress.query.with_entities(db_ingress.domain,db_ingress.serviceName,db_ingress.servicePort).all() + if Rules_infos: + for infos in Rules_infos: + domain,serviceName,servicePort = infos + Rules.append(client.V1beta1IngressRule(host=domain, + http=client.V1beta1HTTPIngressRuleValue( + paths=[client.V1beta1HTTPIngressPath(client.V1beta1IngressBackend( + service_name=serviceName, + service_port=int(servicePort) + ))]) + )) + except Exception as e: + logging.error(e) + else: + Rules.append(client.V1beta1IngressRule(host=ingress_domain, + http=client.V1beta1HTTPIngressRuleValue( + paths=[client.V1beta1HTTPIngressPath(client.V1beta1IngressBackend( + service_name=self.dm_name, + service_port=int(ingress_port) + ))]) + )) + spec = client.V1beta1IngressSpec(rules=Rules) + ingress = client.V1beta1Ingress( + api_version='extensions/v1beta1', + kind='Ingress', + metadata=client.V1ObjectMeta(name='nginx-ingress', + namespace=namespace, + annotations={'kubernetes.io/ingress.class': 'nginx'}), + spec=spec + ) + return ingress + finally: + db_op.DB.session.remove() + + def delete_deployment(self): + try: + api_instance = client.ExtensionsV1beta1Api() + body = client.V1DeleteOptions(propagation_policy='Foreground', grace_period_seconds=5) + api_instance.delete_namespaced_deployment(name=self.dm_name, namespace=namespace, body=body) + return True + except Exception as e: + logging.error(e) + return False + +def check_pod(dm_name,replicas,old_pods): + api_instance = client.CoreV1Api() + # 判断pod是否部署成功 + try: + for t in range(30): + phases = [] + ret = api_instance.list_namespaced_pod(namespace=namespace) + if ret: + for i in ret.items: + if dm_name in i.metadata.name: + if i.metadata.name not in old_pods: + if i.status.container_statuses[-1].state.running: + phase = 'Running' + phases.append(phase) + if len(phases) >= int(replicas): + return True + time.sleep(2) + except Exception as e: + logging.error(e) + return False +def delete_pod(dm_name): + try: + api_instance = client.CoreV1Api() + ret = api_instance.list_namespaced_pod(namespace=namespace) + for i in ret.items: + if dm_name in i.metadata.name: + api_instance.delete_namespaced_pod(name=i.metadata.name, + namespace=namespace, + body=client.V1DeleteOptions()) + return True + except Exception as e: + logging.error(e) + return False +def object_deploy(args): + try: + project, object, version, image, container_port, ingress_port, replicas, domain,re_requests, re_limits,redis_key = args + dm_name = object.split('.')[0] + db_k8s = db_op.k8s_deploy + values = db_k8s.query.filter(db_k8s.image == image).all() + if values: + raise Redis.lpush(redis_key, '%s image already exists!' %image) + war = download_war(object,version,redis_key) + if war: + # 制作docker镜像并上传至仓库 + if make_image(image,redis_key): + db_k8s = db_op.k8s_deploy + #部署deployment + Redis.lpush(redis_key,'start deploy deployment %s......' %dm_name) + k8s = k8s_object(dm_name, image, container_port, replicas, re_requests, re_limits) + api_instance = client.ExtensionsV1beta1Api() + try: + deployment = k8s.export_deployment() + api_instance.create_namespaced_deployment(body=deployment, namespace=namespace) + except Exception as e: + logging.error(e) + Redis.lpush(redis_key, 'fail:%s' % e) + else: + try: + Redis.lpush(redis_key, '......deploy deployment success!') + Redis.lpush(redis_key,'start deploy service %s......' %dm_name) + service = k8s.export_service() + old_pods = [] + if check_pod(dm_name, replicas,old_pods): + #部署service + try: + api_instance = client.CoreV1Api() + api_instance.create_namespaced_service(body=service,namespace=namespace) + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + else: + #部署ingress + Redis.lpush(redis_key, '......deploy service success!') + if ingress_port and domain: + api_instance = client.ExtensionsV1beta1Api() + for Domain in domain.split(','): + Redis.lpush(redis_key,'start deploy ingress %s......' % Domain) + try: + ingress = k8s.export_ingress(ingress_domain=Domain.strip(),ingress_port=int(ingress_port)) + api_instance.replace_namespaced_ingress(body=ingress,namespace=namespace, + name='nginx-ingress') + except Exception as e: + logging.error(e) + Redis.lpush(redis_key, 'fail:%s'%e) + else: + Redis.lpush(redis_key, '......deploy ingress success!') + #ingress信息写入数据库 + db_ingress = db_op.k8s_ingress + v = db_ingress(name='nginx-ingress',namespace=namespace,domain=domain, + serviceName=dm_name,servicePort=int(ingress_port)) + db_op.DB.session.add(v) + db_op.DB.session.commit() + #部署日志记录 + try: + v = db_k8s(project=project, deployment=dm_name, image=image,war = war, + container_port=','.join([str(port) for port in container_port]), + replicas=replicas, + re_requests=str(re_requests).replace("'",'"'), + re_limits=str(re_limits).replace("'",'"'), action='create', + update_date=time.strftime('%Y-%m-%d', time.localtime()), + update_time=time.strftime('%H:%M:%S', time.localtime())) + db_op.DB.session.add(v) + db_op.DB.session.commit() + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + else: + #自动删除deployment + k8s.delete_deployment() + Redis.lpush(redis_key,"......create deployment %s fail!" %dm_name) + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + finally: + db_op.DB.session.remove() + Redis.lpush(redis_key,'_End_') + +def object_update(args): + try: + new_image, new_replicas,version,redis_key = args + if new_image and redis_key: + db_k8s = db_op.k8s_deploy + dm_name = new_image.split('/')[-1].split(':')[0] + #生成新镜像 + values = db_k8s.query.with_entities(db_k8s.project, db_k8s.container_port, db_k8s.image, + db_k8s.replicas,db_k8s.re_requests, db_k8s.re_limits).filter(and_( + db_k8s.deployment == dm_name, db_k8s.action != 'delete')).order_by(desc(db_k8s.id)).limit(1).all() + project, container_port,image,replicas, re_requests, re_limits = values[0] + war = download_war(dm_name,version,redis_key) + if not war: + raise Redis.lpush(redis_key, "params error,update fail!") + if not make_image(new_image,redis_key): + raise Redis.lpush(redis_key, "image record not exists,update fail!") + try: + Redis.lpush(redis_key, 'start deploy image %s ......' % new_image) + re_requests = eval(re_requests) + re_limits = eval(re_limits) + container_port = container_port.split(',') + k8s = k8s_object(dm_name, image, container_port, replicas, re_requests, re_limits) + deployment = k8s.export_deployment() + # Update container image + deployment.spec.template.spec.containers[0].image = new_image + if new_replicas: + deployment.spec.replicas = int(new_replicas) + replicas = new_replicas + # Update the deployment + try: + api_instance = client.CoreV1Api() + ret = api_instance.list_namespaced_pod(namespace=namespace) + old_pos = [i.metadata.name for i in ret.items if dm_name in i.metadata.name] + if image == new_image: + if not delete_pod(dm_name): + raise Redis.lpush(redis_key,'delete old pod fail!') + api_instance = client.ExtensionsV1beta1Api() + api_instance.patch_namespaced_deployment(name=dm_name, namespace=namespace, + body=deployment) + except Exception as e: + logging.error(e) + Redis.lpush(redis_key,'deployment parameter fail!') + else: + Redis.lpush(redis_key, 'start check deploy result......') + if check_pod(dm_name, replicas,old_pos): + v = db_k8s(project=project, deployment=dm_name, image=new_image,war=war, + container_port=container_port, + replicas=replicas, re_requests=str(re_requests).replace("'", '"'), + re_limits=str(re_limits).replace("'", '"'), action='update', + update_date=time.strftime('%Y-%m-%d', time.localtime()), + update_time=time.strftime('%H:%M:%S', time.localtime())) + db_op.DB.session.add(v) + db_op.DB.session.commit() + Redis.lpush(redis_key, '%s image deploy success!' % new_image) + else: + deployment.spec.template.spec.containers[0].image = image + if image == new_image: + delete_pod(dm_name) + api_instance = client.ExtensionsV1beta1Api() + api_instance.patch_namespaced_deployment(name=dm_name, namespace=namespace, + body=deployment) + Redis.lpush(redis_key,'%s image deploy fail,auto rolled back!' % new_image) + except Exception as e: + logging.error(e) + Redis.lpush(redis_key, 'fail:%s' % e) + except Exception as e: + logging.error(e) + if 'BaseException' not in str(e): + Redis.lpush(redis_key, 'fail:%s' % e) + finally: + db_op.DB.session.remove() + Redis.lpush(redis_key, '_End_') \ No newline at end of file diff --git a/Modules/loging.py b/Modules/loging.py new file mode 100644 index 00000000..c0aa225d --- /dev/null +++ b/Modules/loging.py @@ -0,0 +1,32 @@ +#-*- coding: utf-8 -*- +import logging +import logzero +from logzero import logger +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from logging.handlers import RotatingFileHandler +app = Flask(__name__) +app.config.from_pyfile('../conf/log.conf') +log_path = app.config.get('LOG_PATH') +log_debug_path = app.config.get('LOG_DEBUG_PATH') +def Error(): + handler = logging.FileHandler(log_path, encoding='UTF-8') + handler.setLevel(logging.ERROR) + logging_format = logging.Formatter('%(asctime)s - %(filename)s - %(funcName)s - %(lineno)s行 - %(message)s') + handler.setFormatter(logging_format) + app.logger.addHandler(handler) + rHandler = RotatingFileHandler(log_path, maxBytes=1024 * 102400, backupCount=3) + app.logger.addHandler(rHandler) + dHandler = RotatingFileHandler(log_debug_path, maxBytes=1024 * 102400, backupCount=3) + app.logger.addHandler(dHandler) + return app.logger +def write(Message,*v,**d): + logzero.logfile(log_path) + logzero.loglevel(logging.INFO) + logger.info(str(Message)) + if v: + for msg in v: + logger.info('%s\n' %msg) + if d: + for k in d: + logger.info('%s\n' %d[k]) \ No newline at end of file diff --git a/Modules/produce.py b/Modules/produce.py new file mode 100644 index 00000000..b765011c --- /dev/null +++ b/Modules/produce.py @@ -0,0 +1,101 @@ +#-*- coding: utf-8 -*- +from flask import Flask,session +import pytz +import time,datetime +from Modules import Task,Task2,loging,db_op +import redis +import socket +import logging as Logging +from flask_sqlalchemy import SQLAlchemy +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore +app = Flask(__name__) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/sql.conf') +app.config.from_pyfile('../conf/task.conf') +logging = loging.Error() +DB = SQLAlchemy(app) +#Logging.basicConfig() +#Logging.getLogger('apscheduler').setLevel(logging.DEBUG) +task_servers = app.config.get('TASK_SERVERS') +TASK_BACKGROUD = app.config.get('TASK_BACKGROUD') +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +RC = Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +HOST = socket.gethostbyname(socket.gethostname()) +jobstores = {'default': SQLAlchemyJobStore(url=app.config.get('SQLALCHEMY_BINDS')['op'])} +scheduler = BackgroundScheduler({'apscheduler.job_defaults.max_instances': '500','apscheduler.job_defaults.coalesce': 'false'}) +scheduler.configure(jobstores=jobstores,timezone=pytz.timezone('Asia/Shanghai')) +def Async_log(user,url): + db_op_log = db_op.op_log + url = url.replace('op_servers', 'xx.xxxx.com') + if 'xx.xxxx.com' in url and not url.endswith('/index'): + try: + ip = session['remote_ip'] + v = db_op_log(date=time.strftime('%Y-%m-%d',time.localtime()),time = time.strftime('%H:%M:%S',time.localtime()),ip=ip,user=user,access=url) + db_op.DB.session.add(v) + db_op.DB.session.commit() + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() +#单点后台执行 +def scheduler_tasks(): + try: + run_date = datetime.datetime.now() + datetime.timedelta(minutes=1) + run_date = run_date.strftime('%H:%M').split(':') + #scheduler.add_job(Task.zabbix_counts, 'cron', second='0', minute=run_date[1], hour=run_date[0], id=Task.zabbix_counts.__name__, replace_existing=True) + #scheduler.add_job(Task2.get_redis_info, 'cron', second='0', minute=run_date[1], hour=run_date[0], id=Task2.get_redis_info.__name__,replace_existing=True) + ################################################################################################################################################ + scheduler.add_job(Task.business_monitor, 'cron', second='0', minute='*', id=Task.business_monitor.__name__,replace_existing=True) + scheduler.add_job(Task.es_get_log_status, 'cron', second='0', minute='*', id=Task.es_get_log_status.__name__,replace_existing=True) + scheduler.add_job(Task.es_get_log_time, 'cron', second='0', minute='*', id=Task.es_get_log_time.__name__,replace_existing=True) + scheduler.add_job(Task.es_get_data, 'cron', second='0', minute='*', id=Task.es_get_data.__name__, replace_existing=True) + scheduler.add_job(Task.server_per, 'cron', second='0', minute='*/15',id=Task.server_per.__name__, replace_existing=True) + scheduler.add_job(Task.zabbix_triggers, 'cron', second='0', minute='*', id=Task.zabbix_triggers.__name__, replace_existing=True) + scheduler.add_job(Task.get_server_info, 'cron', second='0', minute='30',hour='4',id=Task.get_server_info.__name__,replace_existing=True) + scheduler.add_job(Task.auto_discovery, 'cron', second='0', minute='0', hour='*/4',id=Task.auto_discovery.__name__,replace_existing=True) + scheduler.add_job(Task.get_app_service, 'cron', second='0', minute='0', hour='*/5',id=Task.get_app_service.__name__, replace_existing=True) + scheduler.add_job(Task.get_project_app, 'cron', second='0', minute='30', hour='*/8', id=Task.get_project_app.__name__,replace_existing=True) + scheduler.add_job(Task.zabbix_disk_network, 'cron', second='0', minute='0',hour='7',id=Task.zabbix_disk_network.__name__,replace_existing=True) + scheduler.add_job(Task.cron_run_task, 'cron', second='0', minute='0', hour='*/1',id=Task.cron_run_task.__name__, replace_existing=True) + scheduler.add_job(Task.business_alarm, 'cron', second='0', minute='*/3', id=Task.business_alarm.__name__,replace_existing=True) + scheduler.add_job(Task.reboot_tomcat, 'cron', second='0', minute='*/15', id=Task.reboot_tomcat.__name__,replace_existing=True) + scheduler.add_job(Task.influxdb_counts, 'cron', second='0', minute='1',hour='*', id=Task.influxdb_counts.__name__,replace_existing=True) + scheduler.add_job(Task.influxdb_alarm, 'cron', second='0', minute='0', hour='2', id=Task.influxdb_alarm.__name__,replace_existing=True) + scheduler.add_job(Task.Get_project_lists, 'cron', second='0', minute='0', hour='*',id=Task.Get_project_lists.__name__, replace_existing=True) + scheduler.add_job(Task.zabbix_counts, 'cron', second='0', minute='*/15', hour='*', id=Task.zabbix_counts.__name__,replace_existing=True) + scheduler.add_job(Task2.get_mysqldb_info, 'cron', second='0', minute='0', hour='3', id=Task2.get_mysqldb_info.__name__,replace_existing=True) + scheduler.add_job(Task2.task_run, 'cron', second='0', minute='*/5', hour='*', id=Task2.task_run.__name__,replace_existing=True) + scheduler.add_job(Task2.get_other_info, 'cron', second='0', minute='0', hour='5', id=Task2.get_other_info.__name__,replace_existing=True) + scheduler.add_job(Task.check_host_exist, 'cron', second='0', minute='*/15', id=Task.check_host_exist.__name__,replace_existing=True) + scheduler.add_job(Task2.get_redis_info, 'cron', second='0', minute='0', hour='6', id=Task2.get_redis_info.__name__,replace_existing=True) + scheduler.add_job(Task2.get_redis_status, 'cron', second='0', minute='8',hour='*', id=Task2.get_redis_status.__name__,replace_existing=True) + scheduler.add_job(Task2.alarm_load, 'cron', second='0', minute='*', id=Task2.alarm_load.__name__,replace_existing=True) + scheduler.start() + except Exception as e: + logging.error(e) +#实时后台执行 +class Scheduler_publish(object): + def __init__(self): + self.run_date = datetime.datetime.now() + datetime.timedelta(seconds=3) + self.run_date = self.run_date.strftime('%Y-%m-%d %H:%M:%S') + self.tm = time.strftime('%Y%m%d%H%M%S',time.localtime()) + self.scheduler = BackgroundScheduler({'apscheduler.job_defaults.max_instances': '50','apscheduler.job_defaults.coalesce': 'false'}) + self.scheduler.configure(timezone=pytz.timezone('Asia/Shanghai')) + def Scheduler_mem(self,func,args = []): + self.scheduler.add_job(func,'date', run_date=self.run_date,args=[args],id=self.tm,replace_existing=True) + return self.scheduler +#并发后台执行 +class Scheduler_backgroud(object): + def __init__(self): + self.run_date = datetime.datetime.now() + datetime.timedelta(seconds=3) + self.run_date = self.run_date.strftime('%Y-%m-%d %H:%M:%S') + self.scheduler = BackgroundScheduler({'apscheduler.job_defaults.max_instances': '50','apscheduler.job_defaults.coalesce': 'false'}) + self.scheduler.configure(timezone=pytz.timezone('Asia/Shanghai')) + def Run(self): + if HOST in TASK_BACKGROUD: + self.scheduler.add_job(Task.count_es_logs,'cron', second='0', minute='*',id=Task.count_es_logs.__name__, replace_existing=True) + self.scheduler.start() + loging.write("Scheduler backgroud start on %s ......" %HOST) diff --git a/Modules/task_publish.py b/Modules/task_publish.py new file mode 100644 index 00000000..dc741c01 --- /dev/null +++ b/Modules/task_publish.py @@ -0,0 +1,659 @@ +#-*- coding: utf-8 -*- +import os +from tcpping import tcpping +import time +import json +import shutil +from urllib.request import urlretrieve +import requests +import paramiko +from scp import SCPClient +import redis +import zipfile +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from Modules import Md5,db_op,produce,loging,init +from sqlalchemy import and_,desc +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +logging = loging.Error() +app.config.from_pyfile('../conf/redis.conf') +bak_path = '/opt/bak/' +username = 'root' +key_file = '/root/.ssh/id_rsa' +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +db_project = db_op.project_list +db_publish = db_op.publish_records +Publish_types = {'batch':5,'step':1} +#流水日志记录 +def _flow_log(flow_number,Msg): + try: + tm = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()) + if not os.path.exists("%s/flow_logs" %bak_path): + os.system("/bin/mkdir -p %s/flow_logs" %bak_path) + flow_log_ptah = "%s/flow_logs/%s.log" %(bak_path,flow_number) + with open(flow_log_ptah,'a+') as f: + f.write("%s %s\n"%(tm,str(Msg))) + except Exception as e: + logging.error(e) +#终端消息输出 +def _console_out(channel,Msg_key,Msg): + tm = time.strftime('%H:%M:%S', time.localtime()) + if channel == 'web': + Redis.lpush(Msg_key,'%s %s' %(tm,Msg)) +#结果汇总模块 +def _result_handle(result,server_lists_counts,INFOS): + # 将执行信息写入数据库 + try: + channel = INFOS['channel'] + if result in ('Fail','Success'): + package_url = INFOS['package_url'] + package = package_url.split('/')[-1] + project = '-'.join(package.split('-')[:-1]) + version = package.split('-')[-1] + version = version.replace('.zip', '') + version = version.replace('.war', '') + callback_url = INFOS['callback_url'] + c = db_publish(date=time.strftime('%Y-%m-%d', time.localtime()), + time=time.strftime('%H:%M:%S', time.localtime()), + user=INFOS['user'], project=project, version=version, package_url=package_url,describe=INFOS['describe'], + package_md5=INFOS['package_md5'], package_type=INFOS['package_type'], publish_type=INFOS['publish_type'], + restart=INFOS['restart'], check_url=INFOS['check_url'], callback_url=callback_url,token=INFOS['token'], + execute=INFOS['execute'], gray=int(INFOS['gray']),channel=channel, result=result,flow_number=INFOS['timestamp']) + db_op.DB.session.add(c) + db_op.DB.session.commit() + db_op.DB.session.remove() + _flow_log(INFOS['timestamp'],"publish info write to the database") + except Exception as e: + _flow_log(INFOS['timestamp'], e) + #api渠道进行接口回调 + if channel == 'api': + headers = {'Content-Type':'application/json'} + try: + data = json.dumps({'result':str(result), + 'project_name': INFOS['project'], + 'project_version': INFOS['version'], + 'package_md5': INFOS['package_md5'], + 'application_numbers': server_lists_counts, + 'execute': INFOS['execute'], + 'timestamp': INFOS['timestamp'] + }) + except Exception as e: + _flow_log(INFOS['timestamp'], e) + finally: + callback_url = INFOS['callback_url'] + _flow_log(INFOS['timestamp'],data) + #回调接口返回信息 + try: + f = requests.post(callback_url, data=data, headers=headers) + except Exception as e: + _flow_log(INFOS['timestamp'],e) + else: + _flow_log(INFOS['timestamp'],"callback_status:%s"%f.status_code) + #将流水记录写入数据库 + db_publish_log = db_op.publish_log + flow_number = INFOS['timestamp'] + flow_log_ptah = "%s/flow_logs/%s.log" % (bak_path,flow_number) + try: + with open(flow_log_ptah, 'r') as f: + records = f.read().replace("'",'"') + c = db_publish_log(fid =flow_number,record = records) + db_op.DB.session.add(c) + db_op.DB.session.commit() + except Exception as e: + logging.error(e) + finally: + db_op.DB.session.remove() + +#代码部署代理 +def Publish_agent(args): + #代码分发模块 + def _publish_code(package_type, d_files, package_name, ssh, ip, ssh_port, app_port, package_path, package_md5,execute): + #上线操作前进行数据备份 + _console_out(channel, Msg_Key,"ip:%s ssh_port:%s app_port:%s --->start deploy %s......" % (ip, ssh_port, app_port,project)) + if execute == 'publish': + try: + cmd = "[ -e %s%s ] && echo ok" % (web_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = str(stdout.read().strip(),encoding='utf8') + if result == 'ok': + if package_name.endswith('.war'): + cmd = "\cp -rf %s%s %s%s" % (web_path, package_name, bak_path,package_name) + else: + cmd = "/usr/bin/rsync -av --delete %s%s/ %s%s/" % (web_path, package_name, bak_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number,"Error:%s"%result) + return "ip:%s ssh_port:%s app_port:%s --->backup Fail !" % (ip, ssh_port, app_port) + except Exception as e: + _flow_log(flow_number,'Error:%s'%str(e)) + return "ip:%s ssh_port:%s app_port:%s --->backup Fail !" % (ip, ssh_port, app_port) + scp = SCPClient(ssh.get_transport()) + #增量包部署 + if package_type == 'part': + try: + scp.put("%s/"%d_files, "%s%s/" % (web_path, package_name), recursive=True) + except Exception as e: + # 传输错误重试3次 + for i in range(3): + time.sleep(3) + try: + scp.put("%s/" % d_files, "%s%s/" % (web_path, package_name), recursive=True) + except: + if i >=2: + break + continue + else: + cmd = "chown %s:%s -R %s%s/" % (service_user, service_user, web_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number, "Error:%s" % result) + return "ip:%s ssh_port:%s web_path:%s%s --->chown Fail !" % (ip, ssh_port, web_path, package_name) + _flow_log(flow_number,'Error:%s'%str(e)) + return "ip:%s ssh_port:%s app_port:%s --->deploy Fail !" % (ip, ssh_port, app_port) + else: + cmd = "chown %s:%s -R %s%s/" %(service_user,service_user,web_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number, "Error:%s" % result) + return "ip:%s ssh_port:%s web_path:%s%s --->chown Fail !" % (ip, ssh_port,web_path, package_name) + #整包部署 + if package_type == 'full': + try: + d_zip = "%s%s" % (web_path, package_path.split('/')[-1]) + try: + scp.put(package_path, d_zip) + except Exception as e: + # 传输错误重试3次 + for i in range(3): + time.sleep(3) + try: + scp.put(package_path, d_zip) + except: + if i >= 2: + break + continue + _flow_log(flow_number,'Error:%s'%str(e)) + return "ip:%s ssh_port:%s app_port:%s --->transfers Fail !" % (ip, ssh_port, app_port) + cmd = '/usr/bin/md5sum %s' % d_zip + stdin, stdout, stderr = ssh.exec_command(cmd) + R_md5 = str(stdout.read().split()[0],encoding='utf8') + if R_md5 == package_md5: + package_zip = package_path.split('/')[-1] + cmd = "cd %s && /usr/bin/unzip -qo %s && /bin/rm -f %s && [ -e %s ] && echo ok" % (web_path,package_zip,package_zip,package_zip.replace('.zip', '')) + stdin, stdout, stderr = ssh.exec_command(cmd) + result_zip = str(stdout.read().strip(),encoding='utf8') + result = stderr.read() + if result_zip == 'ok': + cmd = "cd %s && /bin/rm -rf %s{,.war} &&/bin/mv %s %s" % (web_path,project,package_zip.replace('.zip', ''), package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number,"Error:%s"%result) + return "ip:%s ssh_port:%s app_port:%s --->deploy Fail !" % (ip, ssh_port, app_port) + else: + cmd = "chown %s:%s %s%s" % (service_user, service_user, web_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number, "Error:%s" % result) + return "ip:%s ssh_port:%s web_path:%s%s --->chown Fail !" % (ip, ssh_port, web_path, package_name) + else: + _flow_log(flow_number, "Error:%s" % result) + return "ip:%s ssh_port:%s app_port:%s --->unzip Fail !" % (ip, ssh_port, app_port) + else: + return "ip:%s ssh_port:%s app_port:%s --->md5 Fail !" % (ip, ssh_port, app_port) + except Exception as e: + if 'old-style' not in str(e): + _flow_log(flow_number,'Error:%s'%str(e)) + return "ip:%s ssh_port:%s app_port:%s --->deploy Fail !" % (ip, ssh_port, app_port) + + #服务重启模块 + def _restart_service(restart, ssh, ip, ssh_port, app_port): + # 判断是否需要重启 + try: + if restart == 'True': + _console_out(channel, Msg_Key,"ip:%s ssh_port:%s app_port:%s --->restart tomcat ......" % (ip, ssh_port, app_port)) + cmd = "supervisorctl restart tomcat-%s" %project + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number,"Error:%s"%result) + return "ip:%s ssh_port:%s app_port:%s --->restart tomcat Fail !'" % (ip, ssh_port, app_port) + else: + time.sleep(15) + except Exception as e: + _flow_log(flow_number,'Error:%s'%str(e)) + return "ip:%s ssh_port:%s app_port:%s --->restart tomcat Fail !" % (ip, ssh_port, app_port) + + #服务健康检测模块 + def _check_service(check_url, ip, ssh_port, app_port): + # 检测服务是否正常启动 + try: + _console_out(channel, Msg_Key,"ip:%s ssh_port:%s app_port:%s --->check service ...... " % (ip, ssh_port, app_port)) + check_url = check_url.split('/') + headers = {'Host': check_url[2]} + check_url[2] = '%s:%s' % (ip, app_port) + check_url = '/'.join(check_url) + for i in range(30): + try: + f = requests.post(check_url, data={'src': 1}, headers=headers) + if int(f.status_code) not in (200, 301, 302): + raise logging.error("error status_cod %s" % f.status_code) + except Exception as e: + logging.error(e) + try: + f = requests.get(check_url, headers=headers) + if int(f.status_code) not in (200,301,302): + raise logging.error("error status_cod %s" %f.status_code) + except Exception as e: + logging.error(e) + if i >= 30: + return "ip:%s ssh_port:%s app_port:%s --->check service Fail !" % (ip, ssh_port, app_port) + break + time.sleep(2) + _console_out(channel, Msg_Key,"ip:%s ssh_port:%s app_port:%s --->check service %i 次......" %(ip, ssh_port, app_port,i)) + continue + else: + break + else: + break + except Exception as e: + _flow_log(flow_number,'Error:%s'%str(e)) + return "ip:%s ssh_port:%s app_port:%s --->check service Fail !" % (ip, ssh_port, app_port) + + #消息队列模块 + def Deploy_project(server_lists_key,ssh,K): + while True: + #部署异常,停止部署 + if Redis.exists(deploy_fail_key): + return 'Fail' + vals = Redis.spop(server_lists_key) + if vals: + try: + ip, ssh_port, app_port = eval(vals) + msg = "ip:%s ssh_port:%s app_port:%s %s start......" % (ip, ssh_port, app_port, execute) + _flow_log(flow_number,msg) + _console_out(channel, Msg_Key,msg) + ssh.connect(ip, int(ssh_port), username, pkey=ssh_key, timeout=30) + #代码分发部署 + _flow_log(flow_number,"start publish_code ......") + result = _publish_code(package_type, d_files, package_name, ssh, ip, ssh_port, app_port, + package_path, package_md5, execute) + if result: + _flow_log(flow_number,result) + raise _console_out(channel,Msg_Key, result) + else: + #记录部署成功的应用服务 + Redis.sadd(deploy_success_key, [ip, ssh_port, app_port]) + msg = "ip:%s ssh_port:%s app_port:%s --->deploy code Success!" % (ip, ssh_port, app_port) + _flow_log(flow_number,msg) + _console_out(channel, Msg_Key,msg) + # 是否需要重启操作 + _flow_log(flow_number,"start _restart_service ......") + result = _restart_service(restart, ssh, ip, ssh_port, app_port) + if result: + _flow_log(flow_number,result) + raise _console_out(channel,Msg_Key, result) + else: + msg = "ip:%s ssh_port:%s app_port:%s --->restart service Success!" % (ip, ssh_port, app_port) + _flow_log(flow_number,msg) + _console_out(channel, Msg_Key,msg) + #检测应用服务是否正常 + _flow_log(flow_number,'start _check_service ......') + result = _check_service(check_url, ip, ssh_port, app_port) + if result: + _flow_log(flow_number,result) + raise _console_out(channel,Msg_Key, result) + else: + msg = "ip:%s ssh_port:%s app_port:%s --->check service Success!" % (ip, ssh_port, app_port) + _flow_log(flow_number,msg) + _console_out(channel, Msg_Key,msg) + # 识别是否是灰度上线并标记服务器 + if execute == 'publish': + if int(INFOS['gray']) == 1: + gray_key = 'gray_server_%s' % K + db_project.query.filter(db_project.ip==ip,db_project.ssh_port==ssh_port,db_project.app_port==app_port).update({db_project.gray:1}) + db_op.DB.session.commit() + Redis.lpush(gray_key,'%s:%s'%(ip,app_port)) + else: + db_project.query.filter(db_project.ip == ip, db_project.ssh_port == ssh_port,db_project.app_port == app_port).update({db_project.gray:0}) + db_op.DB.session.commit() + except Exception as e: + if 'old-style' not in str(e): + _flow_log(flow_number,'Error:%s'%str(e)) + return 'Fail' + else: + _console_out(channel,Msg_Key, "ip:%s ssh_port:%s app_port:%s --->%s Success!" % (ip, ssh_port, app_port, execute)) + finally: + ssh.close() + else: + break + + #自动回滚模块 + def Auto_rollback(deploy_success_key, ssh): + while True: + vals = Redis.spop(deploy_success_key) + if vals: + ip, ssh_port, app_port = eval(vals) + ssh.connect(ip, int(ssh_port), username, pkey=ssh_key, timeout=30) + try: + msg = "ip:%s ssh_port:%s app_port:%s start auto rollbackup......" % (ip, ssh_port, app_port) + _flow_log(flow_number,msg) + _console_out(channel, Msg_Key,msg) + # 回滚操作 + if package_name.endswith('.war'): + cmd = "/usr/bin/rm -rf %s%s{,.war} && \cp %s%s %s%s" % (web_path, project,bak_path, package_name, web_path, package_name) + else: + cmd = "/usr/bin/rsync -av --delete %s%s/ %s%s/" % (bak_path, package_name, web_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _flow_log(flow_number,"Error:%s"%result) + #回滚失败记录 + Redis.sadd(rollback_fail_key, [ip, ssh_port, app_port]) + else: + # 是否需要重启操作 + result = _restart_service(restart, ssh, ip, ssh_port, app_port) + if result: + _flow_log(flow_number,result) + _console_out(channel,Msg_Key, result) + # 检测服务是否正常 + result = _check_service(check_url, ip, ssh_port, app_port) + if result: + _flow_log(flow_number,result) + _console_out(channel,Msg_Key, result) + except Exception as e: + msg = "ip:%s ssh_port:%s app_port:%s --->auto rollback Fail !" % (ip, ssh_port, app_port) + _flow_log(flow_number,'Error:%s'%str(e)) + _flow_log(flow_number,msg) + _console_out(channel,Msg_Key, msg) + continue + else: + msg = "ip:%s ssh_port:%s app_port:%s --->auto rollback Success !"% (ip, ssh_port, app_port) + _flow_log(flow_number,msg) + #回滚成功计数 + Redis.incr(rollback_count_key,1) + _console_out(channel,Msg_Key,msg) + else: + break + #代码分发控制逻辑 + try: + server_lists_key, server_lists_counts, d_files,project,package_name, package_path, Msg_Key, K, INFOS = args + web_path = '/opt/tomcat-%s/webapps/' % project + service_users = {'tomcat': 'www', 'php': 'phper', 'python': 'www'} + service = db_project.query.with_entities(db_project.resource).filter(db_project.project == project).limit(1).all() + service = service[0][0] + service_user = service_users[service] + except Exception as e: + logging.error(e) + else: + try: + #自动回滚失败Key + rollback_fail_key = 'rollback_fail_%s' % K + #自动回滚统计key + rollback_count_key = 'rollback_count_%s' % K + #部署成功Key + deploy_success_key = 'deploy_success_%s' % K + #部署失败Key + deploy_fail_key = 'deploy_fail_%s' % K + KEYS = {'rollback_fail_key': rollback_fail_key, 'deploy_success_key': deploy_success_key,'deploy_fail_key':deploy_fail_key,'rollback_count_key':rollback_count_key} + #获取相关信息 + package_md5 = INFOS['package_md5'] + package_type = INFOS['package_type'] + restart = INFOS['restart'] + check_url = INFOS['check_url'] + execute = INFOS['execute'] + publish_type = INFOS['publish_type'] + channel = INFOS['channel'] + flow_number = INFOS['timestamp'] + #初始化ssh实例 + ssh = paramiko.SSHClient() + ssh_key = paramiko.RSAKey.from_private_key_file(key_file) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + #代码分发 + result = Deploy_project(server_lists_key,ssh,K) + #代码分发失败触发自动回滚,独立回滚操作不触发 + if result == 'Fail' and execute == 'publish': + Redis.set(deploy_fail_key,'Fail') + Auto_rollback(deploy_success_key,ssh) + except Exception as e: + _flow_log(flow_number,'Error:%s'%str(e)) + finally: + try: + #判断并发执行是否完成 + ends_key = '%s_ends' % K + Redis.incr(ends_key) + if int(Redis.get(ends_key))>=int(Publish_types[publish_type]): + result = 'Fail' + #自动回滚失败服务器列表 + deploy_fail_counts = 0 + if Redis.exists(rollback_fail_key): + deploy_fail_counts = len(Redis.smembers(rollback_fail_key)) + _flow_log(flow_number,"auto rollback fail list:%s" % str(Redis.smembers(rollback_fail_key))) + # 部署的服务器列表 + deploy_success_counts = 0 + if Redis.exists(deploy_success_key): + deploy_success_counts = len(Redis.smembers(deploy_success_key)) + else: + Redis.sadd(deploy_success_key,'None') + # 遗漏的服务器列表 + lost_publish_counts = 0 + vals_diff = Redis.sdiff(server_lists_key,deploy_success_key) + if vals_diff: + lost_publish_counts = len(vals_diff) + _flow_log(flow_number,"Lost publish list:%s" % str(vals_diff)) + Auto_rollback_counts = 0 + if Redis.exists(rollback_count_key): + Auto_rollback_counts = int(Redis.get(rollback_count_key)) + if deploy_fail_counts == 0 and deploy_success_counts == server_lists_counts and lost_publish_counts == 0 and Auto_rollback_counts == 0: + result = 'Success' + gray = 'False' + gray_server = None + if int(INFOS['gray']) == 1: + gray = 'True' + gray_key = 'gray_server_%s' % K + gray_server = ' '.join(Redis.lrange(gray_key,0,-1)) + #汇总结果后回调接口 + _result_handle(result,server_lists_counts,INFOS) + _console_out(channel, Msg_Key,'-' * 100) + _console_out(channel, Msg_Key,"_End_:{0},{1},{2},{3},{4}:{5}:{6},{7}".format(deploy_success_counts,deploy_fail_counts,Auto_rollback_counts,lost_publish_counts,server_lists_counts,flow_number,gray,gray_server)) + # 清除统计keys + for Key in KEYS: + Redis.expire(KEYS[Key],15) + Redis.expire(ends_key, 15) + Redis.expire(Msg_Key,15) + except Exception as e: + logging.error(e) +#代码部署中心 +def Publish_center(args): + try: + INFOS, Msg_Key, K = args + server_lists_counts = 0 + #获取上线操作参数 + execute = INFOS['execute'] + channel = INFOS['channel'] + flow_number = INFOS['timestamp'] + _flow_log(flow_number, 'Publish center(%s) start work ......' % os.getpid()) + _flow_log(flow_number,INFOS) + _console_out(channel,Msg_Key, '-' * 100) + _console_out(channel,Msg_Key, "初始化代码分发参数及检测部署环境,检测进行中......") + #页面回滚操作需要将部分参数回写 + if execute == 'rollback' and channel == 'web': + project = INFOS['project'] + version = INFOS['version'] + vals = db_publish.query.with_entities(db_publish.package_url, db_publish.package_md5,db_publish.package_type, + db_publish.publish_type, db_publish.restart, + db_publish.check_url,db_publish.gray).filter( + and_(db_publish.project == project, db_publish.version == version,db_publish.result == 'Success')).order_by(desc(db_publish.date)).all() + if vals: + package_url, package_md5, package_type, publish_type, restart, check_url,gray = vals[0] + INFOS['package_url'] = package_url + INFOS['package_md5'] = package_md5 + INFOS['package_type'] = package_type + INFOS['publish_type'] = publish_type + INFOS['restart'] = restart + INFOS['check_url'] = check_url + INFOS['gray'] = int(gray) + else: + _Msg = "project:%s version:%s --->not relevant data Fail !" % (project, version) + _flow_log(flow_number,_Msg) + raise _console_out(channel,Msg_Key,_Msg) + project = INFOS['project'] + package_url = INFOS['package_url'] + package_md5 = INFOS['package_md5'] + package_type = INFOS['package_type'] + publish_type = INFOS['publish_type'] + gray = INFOS['gray'] + web_path = '/opt/tomcat-%s/webapps/' %project + #判断代码包类型 + Package_name = package_url.split('/')[-1] + os.system("/bin/mkdir -p %s" % web_path) + os.system("/bin/mkdir -p %s" %bak_path) + package_path = "%s%s" %(bak_path,Package_name) + package_name = Package_name.replace('.zip', '') + s_files = "%s%s" % (web_path, package_name) + #验证包规则 + if project == '-'.join(package_name.split('-')[:-1]): + if package_name.endswith('.war'): + package_name = '%s.war' %project + else: + package_name = project + else: + _Msg = "%s package format Fail" %Package_name + _flow_log(flow_number,_Msg) + raise _console_out(channel, Msg_Key,_Msg) + if package_name.endswith('.war') and package_type == 'part': + _Msg = "%s package not allow part publish" % Package_name + _flow_log(flow_number, _Msg) + raise _console_out(channel, Msg_Key, _Msg) + d_files = "%s%s" % (web_path, package_name) + #删除旧包 + if os.path.exists(package_path): + os.remove(package_path) + #获取代码压缩包 + try: + urlretrieve(package_url,package_path) + except Exception as e: + msg = "package_url:%s --->check package url Fail !" %package_url + _flow_log(flow_number,'Error:%s'%str(e)) + _flow_log(flow_number,msg) + raise _console_out(channel,Msg_Key,msg) + #对压缩包进行md5对比 + _flow_log(flow_number,"package_md5:%s"%Md5.Md5_file(package_path)) + if package_md5 == Md5.Md5_file(package_path): + #解压缩代码包 + try: + zip_file = zipfile.ZipFile(package_path) + if os.path.exists('%s%s' %(web_path,package_name)): + try: + shutil.rmtree('%s%s' %(web_path,package_name)) + except: + os.remove('%s%s' %(web_path,package_name)) + zip_file.extractall(web_path) + except Exception as e: + msg = "package:%s --->check unzip package Fail !" %package_name + _flow_log(flow_number,'Error:%s'%str(e)) + _flow_log(flow_number, msg) + raise _console_out(channel,Msg_Key,msg) + else: + _Msg = "package:%s --->check package md5 Fail !" % package_name + _flow_log(flow_number,_Msg) + raise _console_out(channel,Msg_Key,_Msg) + if os.path.exists(s_files): + shutil.move(s_files,d_files) + if os.path.exists(d_files): + #获取应用部署列表 + vals = db_project.query.with_entities(db_project.ip,db_project.ssh_port,db_project.app_port).filter(db_project.project==project).all() + if vals: + server_lists = [list(val) for val in vals] + #判断是否为灰度上线,并尝试获取服务器灰度标识 + if int(gray) == 1: + gray_vals = db_project.query.with_entities(db_project.ip, db_project.ssh_port,db_project.app_port).filter(and_(db_project.project == project,db_project.gray == 1)).all() + if gray_vals: + server_lists = [list(val) for val in gray_vals] + else: + server_lists = [list(val) for val in vals][0] + server_lists_counts = len(server_lists) + #服务器和应用服务连通性检测 + for infos in server_lists: + ip, ssh_port, app_port = infos + try: + try: + ssh = paramiko.SSHClient() + ssh_key = paramiko.RSAKey.from_private_key_file(key_file) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(ip, int(ssh_port), username, pkey=ssh_key, timeout=30) + except: + _Msg = "ip:%s ssh_port:%s --->check sshd Fail !" % (ip, ssh_port) + _flow_log(flow_number, _Msg) + raise _console_out(channel, Msg_Key, _Msg) + else: + #检测线上代码包路径是否存在 + cmd = "[ -d %s ] && echo ok" % web_path + stdin, stdout, stderr = ssh.exec_command(cmd) + result = str(stdout.read().strip(),encoding='utf8') + if result != 'ok': + _Msg = "ip:%s ssh_port:%s web_path:%s --->check app path Fail !" % (ip, ssh_port, web_path) + _flow_log(flow_number, _Msg) + raise _console_out(channel, Msg_Key, _Msg) + #创建备份目录 + if package_name.endswith('.war'): + cmd = "/bin/mkdir -p %s" %bak_path + else: + cmd = "/bin/mkdir -p %s/%s" % (bak_path, package_name) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stderr.read() + if result: + _Msg = "ip:%s ssh_port:%s bak_path:%s --->make bak path Fail !" % (ip, ssh_port, bak_path) + _flow_log(flow_number, "Error:%s" % result) + _flow_log(flow_number, _Msg) + raise _console_out(channel, Msg_Key, _Msg) + ssh.close() + except: + raise + else: + if not tcpping(host=ip, port=app_port, timeout=3): + _Msg = "ip:%s app_port:%s --->check app port Fail !" % (ip, app_port) + _flow_log(flow_number,_Msg) + raise _console_out(channel,Msg_Key,_Msg) + #部署列表写入redis队列 + server_lists_key = '%s_server_lists_key' %K + for info in server_lists: + Redis.sadd(server_lists_key,info) + #启动多个子控制中心并发执行 + _console_out(channel, Msg_Key, "代码分发相关参数及运行环境检测全部通过!") + _console_out(channel, Msg_Key, "启动代码分发模块,开始部署%s,执行过程稍后输出......" % package_name) + _console_out(channel, Msg_Key, '-' * 100) + for i in range(Publish_types[publish_type]): + Scheduler = produce.Scheduler_publish() + Scheduler = Scheduler.Scheduler_mem(Publish_agent, [server_lists_key,server_lists_counts,d_files,project,package_name,package_path,Msg_Key,K,INFOS]) + Scheduler.start() + time.sleep(1) + else: + _Msg = "Error:not find app service lists!" + _flow_log(flow_number,_Msg) + raise _console_out(channel,Msg_Key,_Msg) + else: + _Msg = "package:%s --->move package Fail !" % package_name + _flow_log(flow_number,_Msg) + raise _console_out(channel,Msg_Key,_Msg) + except Exception as e: + if 'old-style' not in str(e): + _flow_log(flow_number,'Error:%s'%str(e)) + result = "代码分发环境检测到错误,不执行代码分发操作!" + _console_out(channel,Msg_Key,result) + _console_out(channel,Msg_Key, '-' * 100) + _console_out(channel,Msg_Key, "_End_:0,0,0,%i,%i:%s" %(server_lists_counts,server_lists_counts,flow_number)) + #汇总结果后回调接口 + _result_handle(result,server_lists_counts,INFOS) + Redis.expire(Msg_Key,15) + finally: + db_op.DB.session.remove() \ No newline at end of file diff --git a/Modules/tools.py b/Modules/tools.py new file mode 100644 index 00000000..a5a98e91 --- /dev/null +++ b/Modules/tools.py @@ -0,0 +1,191 @@ +#-*- coding: utf-8 -*- +from flask import Flask +import requests +import time +import datetime +import pytz +from Modules import loging,db_idc +import dns.resolver +from random import choice +import string +import json +from pyzabbix import ZabbixAPI +from kubernetes import config +app = Flask(__name__) +logging = loging.Error() +app.config.from_pyfile('../conf/zabbix.conf') +app.config.from_pyfile('../conf/jump.conf') +zabbix_url = app.config.get('ZABBIX_URL') +zabbix_user = app.config.get('ZABBIX_USER') +zabbix_pw = app.config.get('ZABBIX_PW') +tokenUrl = app.config.get('TOKENURL') +assetsUrl = app.config.get('ASSETSURL') +username = app.config.get('USERNAME') +password = app.config.get('PASSWORD') +def Produce(length=8,chars=string.ascii_letters+string.digits): + return ''.join([choice(chars) for i in range(length)]) + +def dingding_msg(text,alart_token=None): + ''' + :param text:list + ''' + th = time.strftime("%H", time.localtime()) + #6点以后报警 + if int(th) > 6: + try: + td = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + headers = {'Content-Type': 'application/json'} + urls = ["https://oapi.dingtalk.com/robot/send?access_token=2c5d2dd93f4866b4d25ba60d25d4fdf9031d7b2edf63f5f7e307d5315948f013"] + if alart_token: + urls.append(alart_token) + if isinstance(text, list): + text.append("*%s*" % td) + data = { + "msgtype": "markdown", + "markdown": {"title": "[关注] 新报警信息!", + "text": ' \n\n'.join([ str(line) for line in text])}, + "at": { + "atMobiles": [ + ], + "isAtAll": False + } + } + for url in set(urls): + try: + requests.post(url, data=json.dumps(data), headers=headers) + except Exception as e: + logging.error(e) + continue + except Exception as e: + logging.error(e) + +def dig(domain): + ''' + :param domain:str + ''' + addr = [] + try: + A = dns.resolver.query(domain,'A') + for i in A.response.answer: + for j in i.items: + try: + if j.rdtype == 1: + addr.append(j.address) + except Exception as e: + logging.error(e) + continue + return addr + except Exception as e: + logging.error(e) + +def time_format(iso8601): + try: + if 'T' in iso8601: + iso8601 = iso8601.split('T') + y, m, d = iso8601[0].split('-') + if '-' in iso8601[1]: + H, M, S = iso8601[1].split('-')[0].split(':') + tz = 0 - int(iso8601[1].split('-')[1].split(':')[0]) + if '+' in iso8601[1]: + H, M, S = iso8601[1].split('+')[0].split(':') + tz = int(iso8601[1].split('+')[1].split(':')[0]) + dd = datetime.datetime(int(y), int(m), int(d), int(H), int(M), int(S), tzinfo=pytz.timezone('Asia/Shanghai')) + dd = dd + datetime.timedelta(hours= 8-tz) + return dd.strftime('%Y-%m-%d %H:%M:%S').split() + except Exception as e: + logging.error(e) + +def format_day_date(date): + try: + date = date.split('-') + if len(date) ==3: + if int(date[1]) <10: + date[1] = '0%s' %int(date[1]) + if int(date[2]) <10: + date[2] = '0%s' %int(date[2]) + date = '-'.join(date) + return date + except Exception as e: + logging.error(e) + +def k8s_conf(): + config_file="%s/../conf/k8s.conf" % app.root_path + contexts, active_context = config.list_kube_config_contexts(config_file) + contexts = [context['name'] for context in contexts] + config.load_kube_config(config_file, context=active_context['name']) + return(config,contexts,config_file) + +class zabbix_api(object): + def __init__(self): + self.zapi = ZabbixAPI(zabbix_url) + self.zapi.session.auth = (zabbix_user, zabbix_pw) + self.zapi.session.verify = False + self.zapi.timeout = 5 + self.zapi.login(zabbix_user, zabbix_pw) + self.val = 0.0 + def zabbix_history(self,host, key): + try: + results = self.zapi.host.get(filter={"host": host}) + if results: + hostid = results[0]['hostid'] + results = self.zapi.item.get(hostids=hostid, output=["itemids", 'key_'], search={"key_": key}) + if results: + itemid = results[0]['itemid'] + for history in (0, 1, 2, 3, 4): + vals = self.zapi.history.get(history=history, itemids=itemid, limit=1, sortfield='clock', + sortorder="DESC") + if vals: + if (int(time.time()) - int(vals[0]['clock'])) < 2000: + self.val = float('%.2f' % float(vals[0]['value'].strip())) + return self.val + except Exception as e: + logging.error(e) + finally: + return self.val + def zabbix_logout(self): + self.zapi.user.logout() + +def get_server_list(): + hosts = [] + try: + res = requests.post(tokenUrl, data={ + "username": username, + "password": password + }) + res = res.json() + headers = { + "Authorization": "%s %s" % ("Bearer ", res["token"]) + } + res = requests.get(assetsUrl, headers=headers,timeout=30) + idcs = requests.get("https://xxx.xxx.com/api/assets/v1/labels", headers=headers, timeout=30) + idcs = {info['id']:info['value'] for info in idcs.json()} + for info in res.json(): + idc = idcs[info['labels'][0]] + if idc != 'lan': + hosts.append((info['ip'],info['port'],info['hostname'],idc)) + except Exception as e: + logging.error(e) + finally: + return list(set(hosts)) + +def http_args(request,arg): + #解析请求参数 + args = request.args.to_dict() + if arg in args: + return args[arg] + else: + return None + +def real_ip(ip): + try: + db_servers = db_idc.idc_servers + val = db_servers.query.filter(db_servers.ip == ip).all() + if not val: + val = db_servers.query.with_entities(db_servers.ip).filter(db_servers.s_ip.like('%{0};%'.format(ip))).all() + if val: + ip = val[0][0] + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() + return ip diff --git a/README.md b/README.md index 4d4ee2c6..e8b1ea9e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# opsweb -一套自己用python开发的运维综合管理平台(flask框架+cmdb),已经成功在百合网运行2年有余。基本能够实现日常运维80%以上的重复工作,极大的提高了运维的工作效率,降低的工作强度. +# 主要功能: # + - 登录由第三方钉钉进行统一鉴权 + - 标准CMDB资产管理以及汇总查询 + - 代码上线,包含上线、灰度、回滚等功能并实时显示执行过程 + - 生产环境包含php、java,项目环境及负载均衡配置一键部署 + - k8s容器平台管理及一键式容器化服务上线 + - 自动进行服务器资产、应用服务的信息及关联关系抓取 + - 资源汇总包含mysql、redis、kafka等基础信息 + - 实时大数据分析包含各个线上业务的并发量、流量、响应时间、业务访问占比、用户地区分布 + - 运维审查包括上线操作、申请记录、日志记录 + - 业务关键指标报警、线上故障自动恢复 + - 访问限速、访问黑名单、用户单点登录限制等安全措施 + - 页面级别用户权限控制 + - 通过分布式全局锁,进程排它锁,实现多机多进程部署后台单任务运行 + + 目前已在多家互联网公司落地部署,平台功能也是根据不同公司实际情况进行了差异化定制开发。本平台适合具有python二次开发能力者或运维开发学习者。 + +# 界面展示 +![show](https://github.com/wylok/opsweb/blob/master/static/images/01.jpg) +# 资产管理 +![show](https://github.com/wylok/opsweb/blob/master/static/images/02.jpg) +# 手机界面 +![show](https://github.com/wylok/opsweb/blob/master/static/images/03.jpg) diff --git a/Script/__init__.py b/Script/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/Script/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/__init__.py b/__init__.py index e1e44f2e..46434eb2 100644 --- a/__init__.py +++ b/__init__.py @@ -1,61 +1,6 @@ -#-*- coding: utf-8 -*- -import sys -import os -reload(sys) -sys.setdefaultencoding('utf8') -from flask import Flask,Blueprint -from api import haproxy_conf,zabbix_api,VerifyCode -from views import sql_app,php_list,publish_java,publish_php,pw,update_php,vpn_admin -from views import cdn,init_system,java_list,clear_redis,Chart,svn_admin,chart_center -from views import kafka_info,zookeeper_info,slow_redis ,mysql_parse,deploy -from views import sql_scheduler,git_admin,sql_kill,sql_run,update_java -from views import clean_project,sql_query,dns_conf,Scheduler -from admin import examine,op_user -from flask_limiter import Limiter -from flask_limiter.util import get_ipaddr -import chartkick -from Modules import produce -app = Flask(__name__) -app.secret_key = os.urandom(24) -produce.scheduler_tasks() -limiter = Limiter(app,key_func=get_ipaddr,global_limits=["30/minute"]) -app.config.from_pyfile('conf/main.conf') -app.config.from_pyfile('conf/redis.conf') -page_ck = Blueprint('ck_page', __name__, static_folder=chartkick.js(),static_url_path='/static') -app.register_blueprint(page_ck, url_prefix='/ck') -app.jinja_env.add_extension("chartkick.ext.charts") -app.register_blueprint(sql_app.page_mysql) -app.register_blueprint(sql_app.page_mysql_op) -app.register_blueprint(pw.page_pw) -app.register_blueprint(cdn.page_cdn) -app.register_blueprint(publish_php.page_publish_php) -app.register_blueprint(init_system.page_init_system) -app.register_blueprint(php_list.page_php_list) -app.register_blueprint(java_list.page_java_list) -app.register_blueprint(update_php.page_update_php) -app.register_blueprint(publish_java.page_publish_java) -app.register_blueprint(haproxy_conf.page_haproxy_conf) -app.register_blueprint(clear_redis.page_clear_redis) -app.register_blueprint(Chart.page_chart) -app.register_blueprint(chart_center.page_chart_center) -app.register_blueprint(vpn_admin.page_vpn_admin) -app.register_blueprint(svn_admin.page_svn_admin) -app.register_blueprint(kafka_info.page_kafka_info) -app.register_blueprint(zookeeper_info.page_zk_info) -app.register_blueprint(slow_redis.page_slow_redis) -app.register_blueprint(mysql_parse.page_mysql_parse) -app.register_blueprint(sql_scheduler.page_sql_scheduler) -app.register_blueprint(git_admin.page_git_admin) -app.register_blueprint(examine.page_examine) -app.register_blueprint(sql_kill.page_sql_kill) -app.register_blueprint(sql_run.page_sql_run) -app.register_blueprint(update_java.page_update_java) -app.register_blueprint(op_user.page_op_user) -app.register_blueprint(deploy.page_deploy) -app.register_blueprint(deploy.page_haproxy_reload) -app.register_blueprint(clean_project.page_Clean_project) -app.register_blueprint(zabbix_api.page_zabbix_api) -app.register_blueprint(sql_query.page_sql_query) -app.register_blueprint(dns_conf.page_dns_conf) -app.register_blueprint(VerifyCode.page_VerifyCode) -app.register_blueprint(Scheduler.page_Scheduler) \ No newline at end of file +#-*- coding: utf-8 -*- +import platform +from Modules import loging +logging = loging.Error() +if platform.python_version().startswith('2.7.'): + logging.error("Python %s is not supported!" %platform.python_version()) diff --git a/admin/__init__.py b/admin/__init__.py index 8b137891..46434eb2 100644 --- a/admin/__init__.py +++ b/admin/__init__.py @@ -1 +1,6 @@ - +#-*- coding: utf-8 -*- +import platform +from Modules import loging +logging = loging.Error() +if platform.python_version().startswith('2.7.'): + logging.error("Python %s is not supported!" %platform.python_version()) diff --git a/admin/assets_manage.py b/admin/assets_manage.py new file mode 100644 index 00000000..80211dbc --- /dev/null +++ b/admin/assets_manage.py @@ -0,0 +1,460 @@ +#-*- coding: utf-8 -*- +from flask import Blueprint,render_template,g,request,flash,redirect,url_for +from Modules import check,db_idc,loging,MyForm,produce,db_op,tools +from sqlalchemy import and_ +import pyexcel +from tcpping import tcpping +import redis +import os +import importlib +from collections import defaultdict +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +logging = loging.Error() +page_assets_manage = Blueprint('assets_manage',__name__) +@page_assets_manage.route('/assets_manage',methods = ['GET', 'POST']) +def assets_manage(): + form = MyForm.MyForm_assets_manage() + db_idc_id = db_idc.idc_id + db_server = db_idc.idc_servers + db_network = db_idc.idc_networks + db_store = db_idc.idc_store + db_third = db_idc.third_resource + db_project = db_op.project_list + db_zabbix = db_idc.zabbix_info + db_project_third = db_op.project_third + change_infos = defaultdict() + #确认问题服务器已修复 + if tools.http_args(request,'key') and tools.http_args(request,'infos'): + try: + Redis.hdel(tools.http_args(request,'key'),tools.http_args(request,'infos')) + Redis.sadd('server_ensure',tools.http_args(request,'infos')) + except Exception as e: + logging.error(e) + finally: + return redirect(url_for('index.index')) + if tools.http_args(request,'mid') == 'assets_manage': + if tools.http_args(request,'aid') and tools.http_args(request,'sid') and tools.http_args(request,'action'): + if ':' in tools.http_args(request,'sid'): + js_ip,js_ssh_port = tools.http_args(request,'sid').split(':') + vals = db_server.query.with_entities(db_server.idc_id,db_server.idrac,db_server.purch_date,db_server.expird_date).filter(and_(db_server.ip==js_ip,db_server.ssh_port==js_ssh_port)).all() + if vals: + idc_id,idrac,purch_date,expird_date = vals[0] + cids = db_idc_id.query.with_entities(db_idc_id.aid,db_idc_id.cid).filter(db_idc_id.id==idc_id).all() + if cids: + aid,cid = cids[0] + change_infos['aid'] = aid + change_infos['cid'] = cid + change_infos['idrac'] = idrac + change_infos['purch_date'] = purch_date + change_infos['expird_date'] = expird_date + change_infos['sid'] = tools.http_args(request,'sid') + else: + change_infos['aid'] = tools.http_args(request,'aid') + change_infos['sid'] = tools.http_args(request,'sid') + change_infos['action'] = tools.http_args(request,'action') + if form.submit.data: + try: + aid = form.select_aid.data + ips = form.text.data.strip() + ips = set(ips.splitlines()) + rack = form.rack.data.strip() + if rack: + if not rack.endswith('机柜') and rack != 'KVM': + rack = '%s机柜' %rack + rack.upper() + action = form.select_action.data + purch = form.purch.data + expird = form.expird.data + device = form.select_device.data + idrac = form.idrac.data.strip() + device_type = form.device_type.data.strip() + fault = form.fault.data + old_host = form.old_host.data + #新增设备 + if action == 'add': + if ips: + for info in ips: + if info: + if rack and purch and expird: + host_type = 'physical' + if rack == 'KVM': + host_type = 'vm' + # 获取机房机柜信息 + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid == aid, db_idc_id.cid == rack)).all() + if not idc_id: + c = db_idc_id(aid=aid, cid=rack) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid == aid, db_idc_id.cid == rack)).all() + idc_id = int(idc_id[0][0]) + #判断是否为服务器 + if device == 'server': + if ':' in info: + if len(info.split(':')) == 2: + ip,ssh_port = info.split(':') + else: + raise flash('%s格式不符合要求!' %info) + else: + ip,ssh_port = (info.strip(),20443) + #查询资产信息 + values = db_server.query.filter(and_(db_server.ip == ip,db_server.ssh_port == ssh_port)).all() + if values: + #获取自动发现信息 + discovery_values = db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port,db_server.status == '新发现')).all() + #修改自动发现信息 + if discovery_values: + if idc_id: + db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port,db_server.status == '新发现')).update({db_server.idc_id:idc_id,db_server.status:'未使用',db_server.host_type:host_type,db_server.purch_date:purch,db_server.expird_date:expird,db_server.idrac:idrac}) + db_idc.DB.session.commit() + flash('%s服务器上架成功!' % info) + else: + flash('%s服务器已存在!' % info) + else: + #添加资产信息 + if tcpping(host=ip, port=ssh_port, timeout=3): + s = db_server(idc_id=idc_id,ip=ip,ssh_port=ssh_port,idrac=idrac,purch_date=purch,expird_date=expird,status='未使用',s_ip=None,host_type=host_type,hostname='',sn='',manufacturer='',productname='',system='',cpu_info='',cpu_core='',mem='',disk_count=0,disk_size='',comment='',) + db_idc.DB.session.add(s) + db_idc.DB.session.commit() + flash('%s服务器上架成功!' %info) + else: + flash('运维平台尝试登录{0}的ssh端口失败,服务器不能上架!'.format(info)) + #判断是否为网络设备 + if device == 'network': + if device_type and purch and expird: + ip = info + #查询资产信息 + values = db_network.query.filter(and_(db_network.ip == ip, db_network.idc_id == idc_id)).all() + if values: + raise flash('%s网络设备资产已存在!' % info) + #添加资产信息 + v = db_network(idc_id=idc_id,type=device_type,ip=ip,redundance='是',purch_date=purch,expird_date=expird,status='使用中',comment='') + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + flash('%s网络设备上架成功!' % ip) + else: + raise flash('上架网络设备,设备型号不能为空!') + #判断是否为存储设备 + if device == 'store': + if device_type: + ip = info + #获取资产信息 + values = db_store.query.filter(and_(db_store.ip == ip,db_store.idc_id==idc_id)).all() + if values: + raise flash('%s存储设备资产已存在!' % info) + #添加资产信息 + v = db_store(idc_id=idc_id,type=device_type,ip=ip,purch_date=purch,expird_date=expird,status='使用中',comment='') + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + flash('%s存储设备上架成功!' % ip) + else: + raise flash('上架存储设备,设备型号不能为空!') + else: + raise flash('必填项目不能为空!', 'error') + #下架设备 + if action == 'down': + if ips: + for info in ips: + if info: + idc_id =db_idc_id.query.with_entities(db_idc_id.id).filter(db_idc_id.aid == aid).all() + idc_id = tuple([id[0] for id in idc_id]) + #判断为服务器删除 + if device == 'server': + if ':' in info: + if len(info.split(':')) == 2: + ip, ssh_port = info.split(':') + else: + raise flash('%s格式不符合要求!' %info) + else: + ip, ssh_port = (info, 20443) + server_values = db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port,db_server.idc_id.in_(idc_id))).all() + if server_values: + #判断服务器是否在使用 + project_values = db_project.query.filter(and_(db_project.ip == ip,db_project.ssh_port == ssh_port,db_project.status=='使用中')).all() + third_values = db_third.query.filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port,db_third.status=='使用中')).all() + if project_values or third_values: + raise flash('%s分配有应用资源,不能下架!' %info) + #删除自有资源信息 + v = db_project.query.filter(and_(db_project.ip == ip, db_project.ssh_port == ssh_port)).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + # 删除第三方资源信息 + v = db_third.query.filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + # 删除zabbix信息 + v = db_zabbix.query.filter(and_(db_zabbix.ip == ip, db_zabbix.ssh_port == ssh_port)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + # 删除服务器信息 + v = db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port,db_server.idc_id.in_(idc_id))).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + flash('%s服务器下架成功!' %info) + else: + flash('%s资产没有找到!' %info) + else: + ip = info + #查询是否为网络设备 + v_network = db_network.query.filter(and_(db_network.ip == ip,db_network.idc_id.in_(idc_id))).all() + v_store = db_store.query.filter(and_(db_store.ip == ip,db_store.idc_id.in_(idc_id))).all() + if v_network: + for c in v_network: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + flash('%s网络设备下架成功!' %info) + #查询是否为存储设备 + elif v_store: + for c in v_store: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + flash('%s存储设备下架成功!' %info) + else: + flash('%s资产没有找到!' %info) + #修改设备信息 + if action == 'modify': + if ips: + for info in ips: + if info: + #修改服务器信息 + if device == 'server': + if ':' in info: + if len(info.split(':')) == 2: + ip, ssh_port = info.split(':') + else: + raise flash('%s格式不符合要求!' %info) + else: + ip, ssh_port = (info, 20443) + #修改IP信息 + if old_host: + if ':' in old_host.strip(): + if len(old_host.split(':')) == 2: + old_ip, old_ssh_port = old_host.split(':') + else: + raise flash('%s格式不符合要求!' % info) + else: + old_ip = old_host.strip() + old_ssh_port = 20443 + if len(ips) == 1: + if tcpping(host=ip, port=ssh_port, timeout=5): + # 获取third_id + third_id = db_third.query.with_entities(db_third.id).filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port)).all() + # 清除项目资源表 + if third_id: + v = db_project_third.query.filter(db_project_third.third_id == int(third_id[0][0])).all() + if v: + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + # 删除第三方资源信息 + v = db_third.query.filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port)).all() + if v: + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + # 删除zabbix信息 + v = db_zabbix.query.filter(and_(db_zabbix.ip == ip, db_zabbix.ssh_port == ssh_port)).all() + if v: + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + #修改自有资源信息 + db_project.query.filter(and_(db_project.ip == old_ip, db_project.ssh_port == old_ssh_port)).update({db_project.ip: ip, db_project.ssh_port: ssh_port}) + db_op.DB.session.commit() + #删除新发现服务器信息 + v = db_server.query.filter(and_(db_server.ip == ip,db_server.ssh_port == ssh_port)).all() + if v: + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + #修改服务器信息 + db_server.query.filter(and_(db_server.ip == old_ip,db_server.ssh_port == old_ssh_port)).update({db_server.ip: ip, db_server.ssh_port: ssh_port}) + db_idc.DB.session.commit() + else: + raise flash('运维平台尝试登录新IP{0}的ssh端口失败,服务器IP不能修改!'.format(ip)) + else: + raise flash('修改服务器IP,新IP信息只能填写一行!', 'error') + values = db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port)).all() + if values: + db_infos = {} + if idrac: + db_infos[db_server.idrac] = idrac + if purch: + db_infos[db_server.purch_date] = purch + if expird: + db_infos[db_server.expird_date] = expird + if fault: + db_infos[db_server.status] = '维护中' + if db_infos: + db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port)).update(db_infos) + if rack: + val = db_idc_id.query.filter(and_(db_idc_id.aid == aid,db_idc_id.cid == rack)).all() + if not val: + v = db_idc_id(aid=aid,cid=rack) + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid == aid,db_idc_id.cid == rack)).all() + idc_id = int(idc_id[0][0]) + db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port)).update({db_server.idc_id:idc_id}) + + try: + db_idc.DB.session.commit() + except Exception as e: + flash(e,'error') + else: + flash('%s服务器信息修改成功!' % info) + else: + flash('%s资产没有找到!' % info) + else: + #修改网络或者存储设备信息 + ip = info + network_infos = {} + store_infos = {} + v_network = db_network.query.filter(and_(db_network.ip == ip)).all() + v_store = db_store.query.filter(and_(db_store.ip == ip)).all() + if v_network or v_store: + if device_type: + network_infos[db_network.type] = device_type + store_infos[db_store.type] = device_type + if purch: + network_infos[db_network.purch_date] = purch + store_infos[db_store.purch_date] = purch + if expird: + network_infos[db_network.expird_date] = expird + store_infos[db_store.expird_date] = expird + if network_infos and store_infos: + db_network.query.filter(and_(db_network.aid == aid, db_network.ip == ip)).update(network_infos) + db_store.query.filter(db_store.ip == ip).update(store_infos) + if rack: + val = db_idc_id.query.filter(and_(db_idc_id.aid == aid, db_idc_id.cid == rack)).all() + if not val: + v = db_idc_id(aid=aid, cid=rack) + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid == aid, db_idc_id.cid == rack)).all() + idc_id = int(idc_id[0][0]) + db_network.query.filter(and_(db_network.ip == ip)).update({db_network.idc_id:idc_id}) + db_store.query.filter(and_(db_store.ip == ip)).update({db_store.idc_id:idc_id}) + else: + flash('%设备修改信息为空' %info) + try: + db_idc.DB.session.commit() + except Exception as e: + flash(e, 'error') + else: + flash('%s服务器信息修改成功!' %info) + else: + raise flash('%s资产没有找到!' %info) + except Exception as e: + db_idc.DB.session.rollback() + db_op.DB.session.rollback() + if 'old' not in str(e): + logging.error(e) + flash(e) + finally: + return render_template('Message.html') + return render_template('assets_manage.html',form=form,change_infos=change_infos) +@page_assets_manage.route('/assets_manage/upload',methods = ['POST']) +def upload(): + try: + if request.files['File']: + db_idc_id = db_idc.idc_id + db_server = db_idc.idc_servers + File = request.files['File'] + if File: + if File.filename.endswith('.xlsx') or File.filename.endswith('.xls'): + file_path = "/tmp/%s" %File.filename + if os.path.exists(file_path): + os.remove(file_path) + File.save(file_path) + records = pyexcel.iget_records(file_name=file_path) + for record in records: + try: + aid = record['idc'] + rack = record['rack'] + if not rack.endswith('机柜'): + rack = '%s机柜' % rack + rack.upper() + ip = record['ip'] + ssh_port = record['ssh_port'] + host_type = 'physical' + idrac = record['idrac'] + purch = tools.format_day_date(record['purch_date']) + expird = tools.format_day_date(record['expird_date']) + if aid and rack and ip and purch and expird: + if not ssh_port: + ssh_port = 20443 + # 获取机房机柜信息 + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter( + and_(db_idc_id.aid == aid, db_idc_id.cid == rack)).all() + if not idc_id: + c = db_idc_id(aid=aid, cid=rack) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid == aid, db_idc_id.cid == rack)).all() + idc_id = int(idc_id[0][0]) + # 查询资产信息 + values = db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port)).all() + if values: + # 获取自动发现信息 + discovery_values = db_server.query.filter( + and_(db_server.ip == ip, db_server.ssh_port == ssh_port,db_server.status == '新发现')).all() + # 修改自动发现信息 + if discovery_values: + if idc_id: + db_server.query.filter(and_(db_server.ip == ip, db_server.ssh_port == ssh_port, + db_server.status == '新发现')).update( + {db_server.idc_id: idc_id, db_server.status: '未使用', + db_server.host_type: host_type, db_server.purch_date: purch, + db_server.expird_date: expird, db_server.idrac: idrac}) + db_idc.DB.session.commit() + flash('%s:%s服务器上架成功!' %(ip,ssh_port)) + else: + flash('%s:%s服务器已存在,录入失败!' %(ip,ssh_port)) + else: + # 添加资产信息 + if tcpping(host=ip, port=ssh_port, timeout=3): + s = db_server(idc_id=idc_id, ip=ip, ssh_port=ssh_port, idrac=idrac, purch_date=purch, + expird_date=expird, status='未使用', s_ip=None, host_type=host_type, + hostname='', sn='', manufacturer='', productname='', system='', + cpu_info='', cpu_core='', mem='', disk_count=0, disk_size='', + comment='', ) + db_idc.DB.session.add(s) + db_idc.DB.session.commit() + flash('%s:%s服务器上架成功!' %(ip,ssh_port)) + else: + flash('运维平台尝试登录%s的ssh端口%s失败,录入失败!'%(ip,ssh_port)) + else: + flash("%s:%s相关录入信息不完整,录入失败!" %(ip,ssh_port),'error') + except Exception as e: + logging.error(e) + continue + else: + flash("文件应为Excel格式文件!") + else: + return redirect(url_for('assets_manage.assets_manage')) + except Exception as e: + flash(e) + finally: + return render_template('Message.html') +@page_assets_manage.before_request +@check.login_required(grade=1) +def check_login(error=None): + produce.Async_log(g.user, request.url) + importlib.reload(MyForm) +@page_assets_manage.teardown_request +def db_remove(error=None): + db_idc.DB.session.remove() + db_op.DB.session.remove() \ No newline at end of file diff --git a/admin/examine.py b/admin/examine.py new file mode 100644 index 00000000..f51da319 --- /dev/null +++ b/admin/examine.py @@ -0,0 +1,56 @@ +#-*- coding: utf-8 -*- +from flask import Blueprint,request,render_template,g,flash +from Modules import check,db_op,produce,loging +from sqlalchemy import desc +import redis +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +logging = loging.Error() +app.config.from_pyfile('../conf/redis.conf') +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +page_examine = Blueprint('examine', __name__) +@page_examine.route('/publish_record') +@check.login_required(grade=2) +def publish_record(): + db = db_op.publish_records + vals = [] + try: + GRAY = {0:'否',1:'是'} + vals = db.query.with_entities(db.date,db.time,db.user,db.project,db.version,db.package_url,db.describe, + db.package_type,db.publish_type,db.restart,db.execute,db.gray,db.channel, + db.result,db.flow_number).order_by(desc(db.id)).all() + vals = [list(val) for val in vals] + for val in vals: + val[11] = GRAY[val[11]] + tables = ['日期','时间','操作人','项目','版本号','下载地址','上线描述','包类型','部署方式','服务重启','操作','灰度','来源','执行结果','流水号'] + except Exception as e: + logging.error(e) + return render_template('publish_record.html',tables = tables,values = vals) +@page_examine.route('/op_log') +@check.login_required(grade=1) +def op_log(): + db = db_op.op_log + try: + val = db.query.with_entities(db.date,db.time,db.ip,db.user,db.access).order_by(desc(db.id)).limit(500).all() + if val: + tables = ['日期','时间','IP','用户','访问页面'] + return render_template('op_log.html',tables = tables,values = val) + else: + flash('获取数据错误!',"error") + return render_template('Message.html') + except Exception as e: + flash(e,"error") + return render_template('Message.html') +@page_examine.before_request +@check.login_required(grade=10) +def check_login(error=None): + produce.Async_log(g.user, request.url) +@page_examine.teardown_request +def db_remove(error=None): + db_op.DB.session.remove() \ No newline at end of file diff --git a/admin/resource_pool.py b/admin/resource_pool.py new file mode 100644 index 00000000..6f55f5c1 --- /dev/null +++ b/admin/resource_pool.py @@ -0,0 +1,414 @@ +#-*- coding: utf-8 -*- +import importlib +from flask import Blueprint,render_template,g,flash,request +from Modules import MyForm,check,loging,db_op,produce,db_idc,tools +from sqlalchemy import distinct,and_,or_ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +import redis +import time +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +app.config.from_pyfile('../conf/redis.conf') +logging = loging.Error() +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +page_resource_pool = Blueprint('resource_pool',__name__) +@page_resource_pool.route('/resource_pool',methods = ['GET', 'POST']) +@page_resource_pool.route('/resource_pool//',methods = ['GET', 'POST']) +def resource_pool(action=None,id=None): + importlib.reload(MyForm) + form = MyForm.MyFrom_resource_pool() + form_third = MyForm.MyFrom_third_resource() + db_project = db_op.project_list + db_server = db_idc.idc_servers + db_third = db_idc.third_resource + db_project_third = db_op.project_third + source_type = 'self' + def recyle_resource(ip,ssh_port,app_port): + try: + project_id = db_project.query.with_entities(db_project.id).filter(and_(db_project.ip==ip,db_project.ssh_port==ssh_port,db_project.app_port==app_port)).all() + third_id = db_third.query.with_entities(db_third.id).filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port, db_third.app_port == app_port)).all() + if project_id or third_id: + # 删除自有资源表信息 + v = db_project.query.filter(and_(db_project.ip==ip,db_project.ssh_port==ssh_port,db_project.app_port==app_port)).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + # 删除第三方资源表信息 + v = db_third.query.filter( + and_(db_third.ip == ip, db_third.ssh_port == ssh_port, db_third.app_port == app_port)).all() + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + # 删除项目资源表信息 + if third_id: + third_id = third_id[0][0] + v = db_project_third.query.filter(db_project_third.third_id == third_id).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + if project_id: + project_id = project_id[0][0] + v = db_project_third.query.filter(db_project_third.project_id == project_id).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + # 判断该服务器资源不再被使用 + project_vals = db_project.query.filter(and_(db_project.ip == ip, db_project.ssh_port == ssh_port)).all() + third_vals = db_third.query.filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port)).all() + if not project_vals and not third_vals: + # 资源自动回收 + db_server.query.filter(db_server.ip == ip, db_server.ssh_port == ssh_port).update({db_server.status: "未使用"}) + db_idc.DB.session.commit() + except Exception as e: + logging.error(e) + tables = ['项目','域名','主机名','应用服务','应用端口','开发语言','部署环境','操作'] + third_tables = ['项目','应用服务', '集群类型', '主机名', '应用端口','操作'] + #默认项目列表展示 + project = db_project.query.with_entities(distinct(db_project.project)).limit(1) + project = project[0][0] + values = db_project.query.with_entities(db_project.id,db_project.project,db_project.domain,db_project.ip, + db_project.ssh_port, db_project.resource,db_project.app_port, db_project.sys_args, + db_project.env).filter(db_project.project == project).all() + third_values = [] + third_indexs = [] + # 按照项目查找 + if tools.http_args(request,'project'): + project = tools.http_args(request,'project') + servers = db_server.query.with_entities(db_server.ip,db_server.ssh_port,db_server.hostname).all() + servers = {'%s:%s'%(info[0],info[1]):info[2] for info in servers} + values = db_project.query.with_entities(db_project.id,db_project.ip, + db_project.ssh_port, db_project.resource, db_project.app_port, + db_project.sys_args, db_project.env).filter( + db_project.project == project).order_by(db_project.ip).all() + if values: + values = [list(val) for val in values if '%s:%s'%(val[1],val[2]) in servers ] + for val in values: + val.insert(3,servers['%s:%s'%(val[1],val[2])]) + values.insert(0, tables[1:]) + third_id = db_project_third.query.with_entities(db_project_third.third_id).filter( + db_project_third.project == project).all() + if third_id: + third_id = tuple([val[0] for val in third_id]) + third_values = db_third.query.with_entities(db_third.id, db_third.resource_type, db_third.cluster_type, + db_third.ip, db_third.ssh_port, db_third.app_port).filter( + db_third.id.in_(third_id)).order_by(db_third.resource_type).all() + if third_values: + third_values = [list(val) for val in third_values] + for val in third_values: + val.insert(5, servers['%s:%s'%(val[3],val[4])]) + third_values.insert(0, third_tables[1:]) + return render_template('resource_info.html', values=values, third_values=third_values, project=project, third_indexs=third_indexs,form=form) + # 按照应用服务查找 + if tools.http_args(request,'application'): + app = tools.http_args(request,'application') + app_id = tools.http_args(request,'application-id') + tables = ['应用服务', '集群类型', '主机名', '应用端口'] + host_info = None + values = [] + servers = db_server.query.with_entities(db_server.ip, db_server.ssh_port, db_server.hostname).all() + servers = {'%s:%s' % (info[0], info[1]): info[2] for info in servers} + if app_id: + if app in ('tomcat', 'python','php'): + host_info = db_project.query.with_entities(db_project.ip,db_project.ssh_port,db_project.app_port).filter( + db_project.id == app_id).all() + if host_info: + host_info = [servers['%s:%s'%(host_info[0][0],host_info[0][1])],host_info[0][-1]] + third_id = db_project_third.query.with_entities(db_project_third.third_id).filter( + and_(db_project_third.project_id == app_id)).all() + if third_id: + third_id = [val[0] for val in third_id] + values = db_third.query.with_entities(db_third.id, db_third.resource_type, db_third.cluster_type, + db_third.ip, db_third.ssh_port, db_third.app_port).filter( + db_third.id.in_(tuple(third_id))).order_by(db_third.resource_type).all() + if values: + values = [list(val) for val in values] + else: + host_info = db_third.query.with_entities(db_third.ip,db_third.ssh_port, db_third.app_port).filter( + db_third.id == app_id).all() + if host_info: + host_info = [servers['%s:%s' % (host_info[0][0], host_info[0][1])], host_info[0][-1]] + project_id = db_project_third.query.with_entities(db_project_third.project_id).filter( + and_(db_project_third.third_id == app_id)).all() + if project_id: + project_id = [val[0] for val in project_id] + values = db_project.query.with_entities(db_project.id, db_project.resource, db_project.ip, + db_project.ssh_port, db_project.app_port).filter( + db_project.id.in_(tuple(project_id))).order_by(db_project.resource).all() + if values: + values = [list(val) for val in values] + for i, vals in enumerate(values): + vals.insert(2, '非集群') + values[i] = vals + else: + if app in ('php', 'tomcat', 'python'): + values = db_project.query.with_entities(db_project.id, db_project.resource, db_project.ip, + db_project.ssh_port, db_project.app_port).filter( + db_project.resource == app).order_by(db_project.ip).all() + if values: + values = [list(val) for val in values] + for i, vals in enumerate(values): + vals.insert(2, '非集群') + values[i] = vals + else: + values = db_third.query.with_entities(db_third.id, db_third.resource_type, db_third.cluster_type, + db_third.ip, db_third.ssh_port, db_third.app_port).filter( + db_third.resource_type == app).order_by(db_third.ip).all() + if values: + values = [list(val) for val in values] + if values: + for val in values: + val.insert(5,servers['%s:%s'%(val[3],val[4])]) + return render_template('application_list.html', values=values, app=app, tables=tables, host_info=host_info,form=form) + # 第三方资源回收接口 + if tools.http_args(request,'action') == 'recyle' and tools.http_args(request,'ip') and tools.http_args(request,'ssh_port') and tools.http_args(request,'app_port'): + ip = tools.http_args(request,'ip') + ssh_port = tools.http_args(request,'ssh_port') + app_port = tools.http_args(request,'app_port') + result = recyle_resource(ip, ssh_port, app_port) + if result: + flash(result) + else: + flash('%s %s %s 资源回收完成!' % (ip, ssh_port, app_port)) + return render_template('Message.html') + #修改项目列表 + if action == 'del' and id: + try: + source_type = tools.http_args(request,'resource_type') + if tools.http_args(request,'resource_type') == 'self': + #修改自有资源列表 + db_project.query.filter(db_project.id == id).update({db_project.project: '', db_project.domain: '', db_project.sys_args: '', + db_project.env: '', db_project.gray: '', + db_project.status: '未分配',db_project.update_date:time.strftime('%Y-%m-%d',time.localtime())}) + db_op.DB.session.commit() + # 删除项目资源表信息 + v = db_project_third.query.filter(db_project_third.project_id == id).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + project = db_project.query.with_entities(db_project.project).filter(db_project.id == id).all() + project= project[0][0] + if tools.http_args(request,'resource_type') == 'third': + #删除项目资源表信息 + v = db_project_third.query.filter(db_project_third.third_id == id).all() + for c in v: + db_op.DB.session.delete(c) + db_op.DB.session.commit() + project = db_project_third.query.with_entities(db_project_third.project).filter(db_project_third.third_id == id).all() + project= project[0][0] + except Exception as e: + logging.error(e) + #资源池预分配 + if form.submit_allot.data: + servers = form.servers.data + ip,ssh_port = servers.split(":") + resource = form.resource.data + app_port = form.app_port.data.strip() + if app_port: + #写入资源表 + if resource in ('php','tomcat','python'): + c= db_project(resource=resource,project='',domain='',ip=ip,ssh_port=ssh_port,app_port=app_port,business_id='',sys_args='',env='',gray='',status='未分配',update_date=time.strftime('%Y-%m-%d',time.localtime())) + db_op.DB.session.add(c) + db_op.DB.session.commit() + else: + c= db_third(resource_type=resource,cluster_type='',ip=ip,ssh_port=ssh_port,app_port=app_port,busi_id=0,department='',person='',contact='',status='未分配',update_date=time.strftime('%Y-%m-%d',time.localtime())) + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + flash('%s应用资源预配成功!' %servers) + #资源池资源加锁 + if form.submit_lcok.data: + try: + servers = form.servers.data + ip,ssh_port = servers.split(":") + project_vals = db_project.query.filter(and_(db_project.ip==ip,db_project.ssh_port == ssh_port)).all() + third_vals = db_third.query.filter(and_(db_third.ip == ip, db_third.ssh_port == ssh_port)).all() + if project_vals or third_vals: + db_server.query.filter(and_(db_server.ip==ip,db_server.ssh_port==ssh_port)).update({db_server.status:'使用中'}) + db_idc.DB.session.commit() + else: + raise flash('%s该资源还未分配应用资源,不能锁定!' % servers) + except Exception as e: + logging.error(e) + else: + flash('%s资源将不能在分配应用服务!' %servers) + #第三方资源回收 + if form_third.submit_recucle.data: + hosts = form_third.hosts.data + if hosts: + hosts = hosts.splitlines() + for host in hosts: + if len(host.split(':')) == 3: + ip,ssh_port,app_port = host.split(':') + result = recyle_resource(ip, ssh_port, app_port) + if result: + flash(result) + else: + flash('%s %s %s 资源回收完成!' %(ip,ssh_port,app_port)) + else: + flash('%s格式不符合要求!' %host) + #项目列表增加主机 + if form.submit_add.data: + try: + resource_val = form.hosts_add.data + project = form.Project.data + resource_val = resource_val.split(':') + resource,app_port,ip,ssh_port = resource_val + vals = db_project.query.with_entities(db_project.domain,db_project.sys_args).filter(and_(db_project.project==project,db_project.env=='生产')).limit(1) + domain,sys_args = vals[0] + #写入自有资源表 + db_project.query.filter(and_(db_project.resource==resource,db_project.ip==ip,db_project.ssh_port==ssh_port,db_project.app_port==app_port).update({db_project.project:project, + db_project.domain:domain,db_project.sys_args:sys_args,db_project.env:'生产', + db_project.gray:'0',db_project.status:'使用中', + db_project.update_date:time.strftime('%Y-%m-%d',time.localtime())})) + db_op.DB.session.commit() + #重新加载数据 + values = db_project.query.with_entities(db_project.id,db_project.project, db_project.domain, db_project.ip, + db_project.ssh_port, db_project.resource,db_project.app_port, db_project.sys_args, + db_project.env).filter(db_project.project == project).all() + except Exception as e: + logging.error(e) + #项目列表查询 + if form.submit_query.data: + source_type = form.source_type.data + project = form.Project.data + #数据初始化 + values = [] + try: + #判断是否需要条件查找 + if source_type == 'self': + values = db_project.query.with_entities(db_project.id, db_project.project, db_project.domain, + db_project.ip, + db_project.ssh_port, db_project.resource, + db_project.app_port, db_project.sys_args, + db_project.env).filter(db_project.project == project).all() + if source_type == 'third': + third_id = db_project_third.query.with_entities(db_project_third.third_id).filter(db_project_third.project == project).all() + if third_id: + third_id = [id[0] for id in third_id] + third_values = db_third.query.with_entities(db_third.id,db_third.resource_type, db_third.cluster_type, + db_third.ip, db_third.ssh_port,db_third.app_port).filter(db_third.id.in_(tuple(third_id))).order_by(db_third.resource_type).all() + if third_values: + for i,t_val in enumerate(third_values): + t_val = list(t_val) + t_val.insert(1,project) + third_values[i] = t_val + except Exception as e: + flash(e) + return render_template('resource_pool.html', form=form, form_third=form_third, tables=tables,third_tables=third_tables, values=values, third_values=third_values,source_type=source_type,project=project) + +@page_resource_pool.route('/resource_modify',methods = ['GET', 'POST']) +def resource_modify(): + importlib.reload(MyForm) + form = MyForm.Form_resource_modify() + INFOS = [] + if form.submit.data: + try: + resource = form.resource.data + source_type = form.source_type.data + action = form.action.data + hosts = form.hosts.data + app_port = form.app_port.data + business = form.select_busi.data + actions = {'add':'新增','del':'删除'} + if resource and hosts and app_port: + db_server = db_idc.idc_servers + db_third = db_idc.third_resource + db_business = db_op.business + # 判断是否为第三方资源服务 + if resource.strip() in ('tomcat','php','python'): + raise INFOS.append('录入仅限于第三方资源服务!') + # 获取业务相关信息 + busi = db_business.query.with_entities(db_business.person,db_business.contact).filter(db_business.id==int(business)).all() + if busi: + person,contact = busi[0] + else: + person=contact = None + for host in hosts.splitlines(): + # 判断主机是否存在 + host = host.strip() + infos = db_server.query.with_entities(db_server.ip,db_server.ssh_port).filter(or_(db_server.hostname==host,db_server.ip==host)).all() + if infos: + ip,ssh_port = infos[0] + try: + # 增加资源信息操作 + if action == 'add': + c = db_third(resource_type=resource, cluster_type=source_type, ip=ip,ssh_port=ssh_port,app_port=int(app_port.strip()), + busi_id=int(business),department='', person=person, contact=contact, status='使用中', update_date='0000-00-00') + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + # 删除资源信息操作 + if action == 'del': + v = db_third.query.filter(and_(db_third.resource_type==resource,db_third.ip==ip,db_third.ssh_port==ssh_port,db_third.app_port==int(app_port.strip()))).all() + if v: + for c in v: + db_idc.DB.session.delete(c) + db_idc.DB.session.commit() + else: + raise INFOS.append("%s 没有找到相关资源服务信息" %host) + except Exception as e: + if 'old-style' not in str(e): + logging.error(e) + if not INFOS: + INFOS.append('执行操作发生异常!') + else: + INFOS.append("%s %s操作执行成功!" %(host,actions[action])) + else: + raise INFOS.append('%s 没有找到服务器信息!' %host) + else: + INFOS.append('输入框均需要填写信息!') + except Exception as e: + if 'old-style' not in str(e): + logging.error(e) + if not INFOS: + INFOS.append('执行操作异常!') + return render_template('resource_modify.html',form=form,INFOS=INFOS) + +@page_resource_pool.route('/resource_query//') +def resource_query(busi=None,app=None): + db_busi = db_op.business + db_third = db_idc.third_resource + db_project = db_op.project_list + db_project_third = db_op.project_third + db_server = db_idc.idc_servers + db_idc_id =db_idc.idc_id + idcs = db_idc_id.query.with_entities(db_idc_id.id,db_idc_id.aid).all() + idcs = {int(val[0]):val[1] for val in idcs} + busi_id = db_busi.query.with_entities(db_busi.id).filter(db_busi.business==busi).all() + busi_id = busi_id[0][0] + host_type = {'physical':'物理机','vm':'虚拟机'} + tables = ['应用服务','应用端口','主机名','机房','主机类型','CPU核数','内存大小'] + try: + if app in ('tomcat','php','python'): + vals = db_project.query.with_entities(db_project.ip,db_project.app_port).filter(and_(db_project.resource==app,db_project.business_id==busi_id)).all() + else: + self_ids = db_project.query.with_entities(db_project.id).filter(db_project.business_id==busi_id).all() + self_ids = tuple([int(id[0]) for id in self_ids]) + third_ids = db_project_third.query.with_entities(db_project_third.third_id).filter(db_project_third.project_id.in_(self_ids)).all() + third_ids = tuple([int(id[0]) for id in third_ids]) + vals = db_third.query.with_entities(db_third.ip,db_third.app_port).filter(and_(db_third.id.in_(third_ids),db_third.resource_type==app)).all() + except Exception as e: + logging.error(e) + try: + values = [list(val) for val in vals] + ips = tuple([ip[0] for ip in vals]) + servers = db_server.query.with_entities(db_server.ip,db_server.hostname,db_server.idc_id,db_server.host_type,db_server.cpu_core,db_server.mem).filter(db_server.ip.in_(ips)).all() + servers = [list(val) for val in servers] + for i,val in enumerate(servers): + servers[i][2] = idcs[int(val[2])] + servers[i][3] = host_type[val[3]] + servers = {info[0]:info[1:] for info in servers} + except Exception as e: + logging.error(e) + return render_template('resource_query.html',values=values,servers=servers,busi=busi,app=app,tables=tables) +@page_resource_pool.before_request +@check.login_required(grade=1) +def check_login(error=None): + produce.Async_log(g.user, request.url) +@page_resource_pool.teardown_request +def db_remove(error=None): + db_idc.DB.session.remove() + db_op.DB.session.remove() \ No newline at end of file diff --git a/api/__init__.py b/api/__init__.py index 8b137891..46434eb2 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1 +1,6 @@ - +#-*- coding: utf-8 -*- +import platform +from Modules import loging +logging = loging.Error() +if platform.python_version().startswith('2.7.'): + logging.error("Python %s is not supported!" %platform.python_version()) diff --git a/api/ajax_api.py b/api/ajax_api.py new file mode 100644 index 00000000..e6a93d57 --- /dev/null +++ b/api/ajax_api.py @@ -0,0 +1,114 @@ +#-*- coding: utf-8 -*- +from flask import Blueprint,jsonify,request,redirect,url_for,abort +from Modules import db_op,db_idc,Md5,loging +from sqlalchemy import and_ +from flask import Flask +import time,datetime +import redis +from flask_sqlalchemy import SQLAlchemy +from Modules import init,check +from sqlalchemy import distinct,desc +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +logging = loging.Error() +limiter = init.web_limiter() +limiter = limiter.limiter +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/security.conf') +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +RC = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +redis_data = app.config.get('REDIS_DATA') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +page_ajax_api = Blueprint('ajax_api', __name__) +@page_ajax_api.route('/zb_ensure/') +@check.login_required(grade=1) +def zb_ensure(id=None): + try: + RC.sadd('zabbix_ensure',id) + tm = datetime.datetime.now() - datetime.timedelta(minutes=1) + tm = tm.strftime('%H:%M') + Key = 'zabbix_triggers_%s' % tm + RC.hdel(Key,id) + except Exception as e: + logging.error(e) + finally: + return redirect(url_for('index.index')) + +@page_ajax_api.route('/project_get/') +@limiter.limit("60/minute") +def project_get(project=None): + try: + Key = 'op_project_get_%s' %time.strftime('%H%M%S',time.localtime()) + projects = [] + if project: + db_project = db_op.project_list + db_servers = db_idc.idc_servers + vals = db_project.query.with_entities(db_project.ip,db_project.ssh_port).filter(db_project.project==project).all() + if vals: + for ip,ssh_port in vals: + host_vals = db_servers.query.with_entities(db_servers.hostname,db_servers.ip).filter(and_(db_servers.ip==ip,db_servers.ssh_port==ssh_port)).all() + if host_vals: + RC.sadd(Key,list(host_vals[0])) + for val in RC.smembers(Key): + projects.append(eval(val)) + RC.delete(Key) + rep = jsonify({project:projects,'md5':Md5.Md5_make(str(projects)),'url':request.url}) + return rep + except Exception as e: + return jsonify({'error':str(e),'url':request.url}) + +@page_ajax_api.route('/get_business_bigdata/',methods = ['GET', 'POST']) +@limiter.limit("60/minute") +def business_bigdata_host_get(host=None): + if host: + dt = time.strftime('%Y-%m-%d', time.localtime()) + uris = RC_CLUSTER.smembers('api_uri_lists_%s_%s' %(host, dt)) + uris = [uri.strip() for uri in uris if uri not in ('-','_')] + return jsonify({'results':uris}) + else: + return jsonify({'results':None}) + +@page_ajax_api.route('/get_project_version/',methods = ['GET', 'POST']) +@limiter.limit("60/minute") +def get_project_version(project=None): + try: + if project: + db_publish = db_op.publish_records + vals = db_publish.query.with_entities(distinct(db_publish.version)).filter(db_publish.project==project).order_by(desc(db_publish.version)).all() + if vals: + versions = [val[0] for val in vals] + return jsonify({'results': versions}) + else: + return jsonify({'results': None}) + else: + return jsonify({'results': None}) + except Exception as e: + logging.error(e) + return jsonify({'results': None}) + +@page_ajax_api.route('/assets_info/',methods = ['POST']) +@check.login_required(grade=1) +def assets_info(action=None): + try: + if action == 'update': + params = request.json + if params: + if 'hostname' in params and 'comment' in params: + hosname = params['hostname'] + comment = params['comment'] + db_server = db_idc.idc_servers + db_server.query.filter(db_server.hostname==hosname).update({db_server.comment:comment}) + db_idc.DB.session.commit() + return jsonify({'results': 'success'}) + return abort(400) + except Exception as e: + logging.error(e) + return abort(400) + +@page_ajax_api.teardown_request +def db_remove(exception): + db_op.DB.session.remove() + db_idc.DB.session.remove() \ No newline at end of file diff --git a/api/assets_add.py b/api/assets_add.py new file mode 100644 index 00000000..646bb202 --- /dev/null +++ b/api/assets_add.py @@ -0,0 +1,100 @@ +#-*- coding: utf-8 -*- +from flask import Blueprint,jsonify,request +from Modules import db_op,check,db_idc,loging,SSH,tools +from sqlalchemy import distinct,and_ +import tcpping +import time +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from Modules import init +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +limiter = init.web_limiter() +limiter = limiter.limiter +logging = loging.Error() +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/security.conf') +app.config.from_pyfile('../conf/assets.conf') +PHYSICAL_TYPES = app.config.get('PHYSICAL_TYPES') +page_assets_add = Blueprint('assets_add', __name__) +@page_assets_add.route('/assets_add') +@limiter.limit("60/minute") +@check.acl_ip +def assets_add(): + try: + if tools.http_args(request,'ip'): + ip = tools.http_args(request,'ip') + ssh_port = 20443 + if tools.http_args(request,'ssh_port'): + ssh_port = tools.http_args(request,'ssh_port') + db_ips = db_idc.resource_ip + db_idc_id = db_idc.idc_id + exist_ips = [] + rip = ip.split('.') + rip[3] = 0 + aid = db_ips.query.with_entities(db_ips.aid).filter(db_ips.network==rip).all() + if aid: + aid = aid[0][0] + else: + return jsonify({'error':"没有找到对应的机房信息!"}) + db_server = db_idc.idc_servers + m_ips = db_server.query.with_entities(distinct(db_server.ip)).all() + if m_ips: + exist_ips = [ip[0] for ip in m_ips] + e_ips = db_server.query.with_entities(distinct(db_server.s_ip)).all() + for ips in e_ips: + if None not in ips: + if ';' in ips[0]: + ips = ips[0].split(';') + for ip in ips: + exist_ips.append(ip) + exist_ips.append(ips[0]) + if ip not in exist_ips: + if tcpping(host=ip, port=ssh_port, timeout=1): + try: + Ssh = SSH.ssh(ip=ip, ssh_port=ssh_port) + values = Ssh.Run('/usr/sbin/dmidecode -s system-manufacturer') + if values['stdout']: + if values['stdout'][0].strip('\n') in PHYSICAL_TYPES: + v = db_server(idc_id=1053,ip=ip, ssh_port=ssh_port, s_ip='', host_type='physical', hostname='', sn='', + manufacturer='', productname='', + system='', cpu_info='', cpu_core='', mem='',disk_count=0, disk_size='', idrac='', + purch_date='', + expird_date='', status='新发现', comment='') + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + else: + #判断机房机柜信息 + idc_id = db_idc_id.query.with_entities(db_idc_id.id).filter(and_(db_idc_id.aid==aid,db_idc_id.cid == 'KVM')) + if not idc_id: + c = db_idc_id(aid=aid,cid='KVM') + db_idc.DB.session.add(c) + db_idc.DB.session.commit() + idc_id = db_idc_id.query.with_entities(idc_id.id).filter( + and_(db_idc_id.aid == aid, db_idc_id.cid == 'KVM')) + idc_id = idc_id[0][0] + dt = time.strftime('%Y-%m-%d', time.localtime()) + v = db_server(idc_id=idc_id,ip=ip,ssh_port=ssh_port,s_ip='',host_type='vm',hostname='',sn='',manufacturer='',productname='', + system='',cpu_info='',cpu_core='',mem='',disk_count=0,disk_size='',idrac='',purch_date=dt,expird_date='2999-12-12',status='使用中',comment='') + db_idc.DB.session.add(v) + db_idc.DB.session.commit() + for cmd in ("yum -y install dmidecode","chmod +s /usr/sbin/dmidecode"): + Ssh.Run(cmd) + except Exception as e: + return jsonify({'error':e}) + else: + return jsonify({'info':'%s add success' %ip,'api':request.url,'time':'%s' %time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())}) + finally: + Ssh.Close() + else: + return jsonify({'error':'ssh port not connect!'}) + else: + return jsonify({'error':'服务器信息已存在!'}) + except Exception as e: + logging.error(e) + finally: + db_idc.DB.session.remove() +@page_assets_add.teardown_request +def db_remove(exception): + db_op.DB.session.remove() \ No newline at end of file diff --git a/api/assets_query.py b/api/assets_query.py new file mode 100644 index 00000000..b862b0ea --- /dev/null +++ b/api/assets_query.py @@ -0,0 +1,87 @@ +#-*- coding: utf-8 -*- +from flask import Blueprint,jsonify,request +from Modules import db_op,db_idc,loging +from sqlalchemy import and_ +import time +from flask import Flask +from Modules import init +from flask_sqlalchemy import SQLAlchemy +import redis +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +limiter = init.web_limiter() +limiter = limiter.limiter +logging = loging.Error() +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/security.conf') +redis_data = app.config.get('REDIS_DATA') +redis_port = app.config.get('REDIS_PORT') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +page_assets_query = Blueprint('assets_query', __name__) +@page_assets_query.route('/assets_query',methods = ['POST']) +def assets_query(): + ip = None + idcs = None + status = 'None' + try: + db_servers = db_idc.idc_servers + db_idc_id = db_idc.idc_id + db_token = db_op.platform_token + params = request.get_json() + #服务器机房查询 + if params: + if 'access_token' in params: + access_token = params['access_token'] + # 验证token是否有效 + vals = db_token.query.filter(and_(db_token.token == access_token, db_token.expire > time.strftime('%Y-%m-%d', time.localtime()))).all() + if vals: + #判断参数是否存在 + if 'ip' in params: + ip = params['ip'] + Key = 'op_query_idc_%s' % ip + if ip == 'all': + # 先在缓存中查询结果 + if RC_CLUSTER.exists(Key): + idcs = eval(RC_CLUSTER.get(Key)) + else: + # 获取全量idc信息id + idcs = db_servers.query.with_entities(db_servers.ip,db_servers.idc_id).all() + idcs = [list(idc) for idc in idcs if len(idc) ==2] + for i,info in enumerate(idcs): + idc = db_idc_id.query.with_entities(db_idc_id.aid).filter(db_idc_id.id == int(info[-1])).all() + if idc: + info[-1] = idc[0][0] + else: + idcs.pop(i) + idcs = {idc[0]:idc[-1] for idc in idcs} + # 缓存查询结果 + RC_CLUSTER.set(Key,idcs) + RC_CLUSTER.expire(Key,3600) + else: + # 先在缓存中查询结果 + if RC_CLUSTER.exists(Key): + idcs = RC_CLUSTER.get(Key) + else: + # 获取idc信息id + idc_id = db_servers.query.with_entities(db_servers.idc_id).filter(db_servers.ip==ip).all() + if idc_id: + #获取idc机房信息 + idc_info = db_idc_id.query.with_entities(db_idc_id.aid).filter(db_idc_id.id==int(idc_id[0][0])).all() + if idc_info: + idcs = idc_info[0][0] + #缓存查询结果 + RC_CLUSTER.set(Key,idcs) + RC_CLUSTER.expire(Key,3600) + if idcs: + status = 'ok' + except Exception as e: + status = 'error' + logging.error(e) + finally: + return jsonify({'status': status, 'ip': ip, 'idc': idcs}) + +@page_assets_query.teardown_request +def db_remove(exception): + db_idc.DB.session.remove() + db_op.DB.session.remove() \ No newline at end of file diff --git a/api/deployment_deploy.py b/api/deployment_deploy.py new file mode 100644 index 00000000..dc203fb5 --- /dev/null +++ b/api/deployment_deploy.py @@ -0,0 +1,53 @@ +#-*- coding: utf-8 -*- +from flask import Blueprint,request,jsonify +from Modules import loging,tools,k8s_resource,db_op,produce +from flask import Flask +import time +from sqlalchemy import and_ +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +app.config.from_pyfile('../conf/redis.conf') +app.config.from_pyfile('../conf/docker.conf') +logging = loging.Error() +docke_registry = app.config.get('REGISTRY') +config,contexts,config_file = tools.k8s_conf() +config.load_kube_config(config_file, context=contexts[0]) +page_deployment_deploy = Blueprint('deployment_deploy',__name__) +@page_deployment_deploy.route('/deployment_update',methods = ['POST']) +def deployment_update(): + db_token = db_op.platform_token + params = request.json + new_replicas = None + msg = "image build fail!" + try: + if params: + if 'project' in params and 'version' in params and 'access_token' in params: + token = params['access_token'] + project = params['project'] + version = params['version'] + new_image = "%s/%s:%s" %(docke_registry,project.split('.')[0],version) + if 'replicas' in params: + new_replicas = params['replicas'] + # 验证token是否有效 + vals = db_token.query.filter(and_(db_token.token == token, db_token.expire > time.strftime('%Y-%m-%d', time.localtime()))).all() + if vals: + redis_key = 'op_k8s_update_%s' %time.strftime('%Y%m%d%H%M%S', time.localtime()) + Scheduler = produce.Scheduler_publish() + Scheduler = Scheduler.Scheduler_mem(k8s_resource.object_update, [new_image, new_replicas,version,redis_key]) + Scheduler.start() + msg = "http://xxx.xxxx.com/deploy_query/%s" %redis_key + else: + msg = 'token deny!' + else: + msg = 'url params error!' + else: + msg = 'url params null!' + except Exception as e: + msg = e + finally: + return jsonify({'result': msg}) +@page_deployment_deploy.teardown_request +def db_remove(exception): + db_op.DB.session.remove() diff --git a/api/publish_code.py b/api/publish_code.py new file mode 100644 index 00000000..d5b7b45d --- /dev/null +++ b/api/publish_code.py @@ -0,0 +1,81 @@ +#-*- coding: utf-8 -*- +import redis +import string +import time +from flask import Blueprint,jsonify,request +from Modules import Md5,db_op,produce,loging,task_publish,tools +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from Modules import init +from sqlalchemy import and_ +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +limiter = init.web_limiter() +limiter = limiter.limiter +app.config.from_pyfile('../conf/redis.conf') +logging = loging.Error() +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,password=redis_password,decode_responses=True) +page_publish_code = Blueprint('publish_code',__name__) +@page_publish_code.route('/publish_code',methods = ['POST']) +@limiter.limit("15/minute") +def publish_code(): + try: + timestamp = None + db_token = db_op.platform_token + params = request.json + GRAY = {'Flase':0,'True':1} + # 检查时间戳是否存在 + if 'timestamp' in params['data']: + timestamp = params['data']['timestamp'] + else: + return jsonify({'status': 'timestamp is null', 'timestamp': None}) + #md5对比验证数据 + new_md5 = Md5.Md5_make(params['data']) + if new_md5 == params['data_md5']: + params = params['data'] + token = params['access_token'] + #验证token是否有效 + vals = db_token.query.filter(and_(db_token.token==token,db_token.expire > time.strftime('%Y-%m-%d',time.localtime()))).all() + if vals: + user = params['proposer'] + package_url = params['package_url'] + #检查压缩包下载地址格式 + if not package_url.endswith('.zip') and not package_url.endswith('.war'): + return jsonify({'status': 'the package must be zip or war', 'timestamp': timestamp}) + #获取详细参数 + describe = params['describe'] + package_md5 = params['package_md5'] + package_type = params['package_type'] + publish_type = params['publish_type'] + restart = params['restart'] + execute = params['execute'] + check_url = params['check_url'] + rb_project = params['project_name'] + rb_version = params['project_version'] + callback_url = params['callback_url'] + gray = GRAY[params['gray']] + #生成随机key种子 + K = '%s_%s' %(token,tools.Produce(length=8,chars=string.digits)) + Msg_Key = '%s_publish_msg' % K + INFOS = {'package_url': package_url, 'package_md5': package_md5, 'package_type': package_type, + 'publish_type': publish_type,'user':user,'describe':describe.replace('"','').replace("'",''),'gray':gray, + 'restart': restart, 'execute': execute, 'check_url': check_url.replace('https','http'), 'project': rb_project, + 'version': rb_version,'channel':'api','callback_url':callback_url,'token':token,'timestamp':timestamp} + #启动代码分发控制中心 + Scheduler = produce.Scheduler_publish() + Scheduler = Scheduler.Scheduler_mem(task_publish.Publish_center,[INFOS,Msg_Key,K]) + Scheduler.start() + return jsonify({'status': 'ok', 'timestamp': timestamp}) + else: + return jsonify({'status': 'token deny', 'timestamp': timestamp}) + else: + return jsonify({'status': 'data_md5 error', 'timestamp': timestamp}) + except Exception as e: + logging.error(e) + return jsonify({'status': str(e), 'timestamp': timestamp}) + finally: + db_op.DB.session.remove() \ No newline at end of file diff --git a/api/record_publish.py b/api/record_publish.py new file mode 100644 index 00000000..a3ec220f --- /dev/null +++ b/api/record_publish.py @@ -0,0 +1,60 @@ +#-*- coding: utf-8 -*- +import time +from flask import Blueprint,jsonify,request +from Modules import db_op,loging,init +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import and_ +app = Flask(__name__) +app.config.from_pyfile('../conf/sql.conf') +DB = SQLAlchemy(app) +limiter = init.web_limiter() +limiter = limiter.limiter +app.config.from_pyfile('../conf/redis.conf') +logging = loging.Error() +page_record_publish = Blueprint('record_publish',__name__) +@page_record_publish.route('/record_publish',methods = ['POST']) +@limiter.limit("15/minute") +def record_publish(): + try: + dt = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + db_publish = db_op.publish_records + db_token = db_op.platform_token + params = request.json + if 'access_token' in params: + token = params['access_token'] + #验证token是否有效 + vals = db_token.query.filter(and_(db_token.token==token,db_token.expire > time.strftime('%Y-%m-%d',time.localtime()))).all() + if vals: + # 获取详细参数 + user = params['proposer'] + package_url = params['package_url'] + describe = params['describe'] + project = params['project_name'] + version = params['project_version'] + if user and package_url and describe and project and version: + c = db_publish(date=time.strftime('%Y-%m-%d', time.localtime()), + time=time.strftime('%H:%M:%S', time.localtime()), + user=user, project=project, version=version, package_url=package_url, + describe=describe, + package_md5='', package_type='full', + publish_type='batch', + restart='True', check_url='', callback_url='', + token=token, + execute='publish', gray='', channel='api', result='Success', + flow_number='None') + db_op.DB.session.add(c) + db_op.DB.session.commit() + else: + return jsonify({'status': 'parameter error!', 'datatime': dt}) + else: + return jsonify({'status': 'token deny!', 'datatime': dt}) + else: + return jsonify({'status': 'token not null!', 'datatime': dt}) + except Exception as e: + logging.error(e) + return jsonify({'record': str(e), 'datatime': dt}) + else: + return jsonify({'record': 'ok', 'datatime': dt}) + finally: + db_op.DB.session.remove() \ No newline at end of file diff --git a/conf/__init__.py b/conf/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/conf/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/conf/log.conf b/conf/log.conf new file mode 100644 index 00000000..5628f7fa --- /dev/null +++ b/conf/log.conf @@ -0,0 +1,2 @@ +LOG_LEVEL = "" +LOG_PATH = "" \ No newline at end of file diff --git a/conf/main.conf b/conf/main.conf new file mode 100644 index 00000000..093edee5 --- /dev/null +++ b/conf/main.conf @@ -0,0 +1,4 @@ +SECRET_KEY = '' +DEBUG_TB_INTERCEPT_REDIRECTS = '' +TRAP_HTTP_EXCEPTIONS = True +INIT_OP_PASSWORD = '' \ No newline at end of file diff --git a/conf/redis.conf b/conf/redis.conf new file mode 100644 index 00000000..75815096 --- /dev/null +++ b/conf/redis.conf @@ -0,0 +1,7 @@ +REDIS_HOST = "" +REDIS_PORT = +CELERY_BROKER_URL = "" +CELERY_RESULT_BACKEND = "redis://redis.service.baihe:6379/0" +NODES = [{"host": "", "port": "6379"},{"host": "", "port": "6379"},{"host": "", "port": "6379"}] +NODES_PRODUCE = [{"host": "", "port": "6379"},{"host": "", "port": "6379"},{"host": "", "port": "6379"},{"host": "", "port": "6379"},{"host": "", "port": "6379"},{"host": "", "port": "6379"}] +REDIS_IPS = ('','') \ No newline at end of file diff --git a/conf/sql.conf b/conf/sql.conf new file mode 100644 index 00000000..27d0469e --- /dev/null +++ b/conf/sql.conf @@ -0,0 +1,8 @@ +SQLALCHEMY_BINDS = {'idc': 'mysql://','op': 'mysql://'} +SQLALCHEMY_POOL_SIZE = None +SQLALCHEMY_COMMIT_ON_TEARDOWN = True +SQLALCHEMY_TRACK_MODIFICATIONS = False +MYSQL_USER = '' +MYSQL_PASSWORD = '' +MYSQL_HOST = '' +MYSQL_PORT = 3306 diff --git a/doc/__init__.py b/doc/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/doc/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/index.py b/index.py index bcf74d2e..3353efa5 100644 --- a/index.py +++ b/index.py @@ -1,54 +1,154 @@ -#-*- coding: utf-8 -*- -import time -import datetime -from flask import Blueprint,render_template,g,request,make_response,render_template_string -from Modules import loging,check ,produce,db_op -import string -import random -from sqlalchemy import and_ -import __init__ -app = __init__.app -page_index=Blueprint('index',__name__) -@page_index.route('/index') -def index(): - DB = db_op.rota - date = time.strftime('%Y-%m-%d',time.localtime()) - new_date = datetime.date.today()+datetime.timedelta(1) - ym = time.strftime('%Y.%m',time.localtime()) - if '@' in g.user: - g.user = g.user.split('@')[0] - data=[g.user] - try: - # 生成今日和明日的运维排班 - users = [] - duty = u'报警值班' - #值班人员名单 - pools = [u'xxx', u'xxx',u'xxx'] - for t in (date,new_date): - VAL = DB.query.with_entities(DB.name).filter(and_(DB.date == t,DB.duty == duty)).all() - if VAL: - user = VAL[0][0] - else: - user = random.choice(pools) - c = DB(name = user,duty = duty,date = t) - db_op.DB.session.add(c) - db_op.DB.session.commit() - pools.remove(user) - users.append(user) - data.extend(users) - ip=request.headers.get('X-Forwarded-For') - if not ip : - ip = request.remote_addr - app_resp = make_response(render_template('index.html',datas=data,Year=ym,grade = g.grade,ip=ip)) - app_resp.set_cookie('secret_key',produce.Produce(length=6,chars=string.digits)) - return app_resp - except Exception as e: - loging.write(str(e)) - return render_template_string('获取数据异常!') -@page_index.before_request -@check.login_required(grade=2,page='index') -def check_login(error=None): - produce.Async_log(g.user, request.url) -@page_index.teardown_request -def db_remove(error=None): - db_op.DB.session.remove() \ No newline at end of file +#-*- coding: utf-8 -*- +import time +from flask import Blueprint,render_template,g,request,make_response,flash +from Modules import loging,check,produce,db_idc,tools +import string +import redis +import datetime +from pyecharts import Gauge,Line,Bar +from flask import Flask +from sqlalchemy import and_ +from collections import defaultdict +from flask_sqlalchemy import SQLAlchemy +app = Flask(__name__) +app.config.from_pyfile('conf/redis.conf') +app.config.from_pyfile('conf/sql.conf') +DB = SQLAlchemy(app) +logging = loging.Error() +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +RC = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +redis_data = app.config.get('REDIS_DATA') +RC_CLUSTER = redis.StrictRedis(host=redis_data, port=redis_port,decode_responses=True) +page_index=Blueprint('index',__name__) +@page_index.route('/index') +def index(): + try: + db_zabbix = db_idc.zabbix_info + td = time.strftime("%Y-%m-%d", time.localtime()) + td_1 = datetime.datetime.now() - datetime.timedelta(days=1) + td_1 = td_1.strftime("%Y-%m-%d") + td_7 = datetime.datetime.now() - datetime.timedelta(days=7) + td_7 = td_7.strftime("%Y-%m-%d") + TM = time.strftime('%M', time.localtime()) + BUSIS = [] + #仪表盘展示 + try: + gauge_busi = Gauge(width='100%',height='100%') + gauge_busi.add("线上业务健康率", "", 100,scale_range=[0, 100], is_toolbox_show=False) + gauge_server = Gauge(width='100%',height='100%') + gauge_server.add("线上服务器连通率", "", 100, scale_range=[0, 100],is_toolbox_show=False) + gauge_network = Gauge(width='100%', height='100%') + gauge_network.add("机房网络连通率", "", 100, scale_range=[0, 100], is_toolbox_show=False) + gauge = {'network':gauge_network,'server':gauge_server, 'busi':gauge_busi} + except Exception as e: + logging.error(e) + #业务信息展示 + try: + total_key = 'op_totals_alarms_tmp' + for i in range(7): + data_now = datetime.datetime.now() - datetime.timedelta(days=i) + dd = data_now.strftime('%Y-%m-%d') + alarm_count_key = '%s_%s' % ('op_business_alarm_count', dd) + if RC_CLUSTER.exists(alarm_count_key): + vals = RC_CLUSTER.hgetall(alarm_count_key) + vals = sorted(vals.items(), key=lambda item: int(item[1])) + for val in vals: + RC_CLUSTER.hincrby(total_key, dd, val[1]) + line = Line("业务监控报警近期统计", width='105%', height=250, title_pos='center', title_text_size=12) + if RC_CLUSTER.exists(total_key): + vals = RC_CLUSTER.hgetall(total_key) + vals = sorted(vals.items(), key=lambda item: item[0],reverse=True) + RC_CLUSTER.delete(total_key) + attrs = [val[0] for val in vals] + vals = [int(val[1]) for val in vals] + line.add("", attrs, vals, is_label_show=True, is_toolbox_show=False,is_legend_show = False, + xaxis_interval=0,is_fill=True,area_opacity=0.3,is_smooth=True) + bar = Bar("线上业务PV实时统计", width='105%', height=250, title_pos='center', title_text_size=12) + busi_vals = RC_CLUSTER.hgetall('op_business_pv_%s' %td) + if busi_vals: + busi_vals = sorted(busi_vals.items(), key=lambda item: int(float(item[1])), reverse=True) + bar_vals = [val[0] for val in busi_vals[:8]] + bar_counts = [float('%.4f' %(float(val[1])/100000000)) for val in busi_vals[:8]] + bar.add("", bar_vals, bar_counts, is_label_show=True, is_toolbox_show=False, legend_orient='vertical', + legend_pos='right', xaxis_interval=0,yaxis_formatter='亿') + BUSIS.extend((bar,line)) + except Exception as e: + logging.error(e) + #网站并发访问展示 + try: + NEW_DATA = [eval(v) for v in RC.lrange('internet_access_%s' %td, 0, -1)] + attr = [DATA[0] for DATA in NEW_DATA] + vals =[int(int(DATA[1])/60) for DATA in NEW_DATA] + line = Line('墨迹线上实时并发访问',title_pos='center',title_text_size=12,width='109%',height='250px') + line.add("今天", attr, vals,is_toolbox_show=False,is_smooth=True,mark_point=["max", "min"], + mark_point_symbolsize=80,is_datazoom_show=True,datazoom_range=[v for v in range(100,10)], + datazoom_type= 'both',legend_pos='70%') + if RC.exists('internet_access_%s' % td_1): + OLD_DATA = [eval(v) for v in RC.lrange('internet_access_%s' % td_1, 0, -1)] + OLD_DATA = [val for val in OLD_DATA if val[0] in attr] + old_attr = [DATA[0] for DATA in OLD_DATA] + old_vals = [int(int(DATA[1]) / 60) for DATA in OLD_DATA] + if attr and vals: + line.add("昨天", old_attr,old_vals, is_toolbox_show=False, is_smooth=True, mark_point=["max", "min"], + mark_point_symbolsize=80,is_datazoom_show=True,datazoom_range=[v for v in range(100,10)], + datazoom_type= 'both',legend_pos='70%') + if RC.exists('internet_access_%s' % td_7): + OLD_DATA = [eval(v) for v in RC.lrange('internet_access_%s' % td_7, 0, -1)] + OLD_DATA = [val for val in OLD_DATA if val[0] in attr] + old_attr = [DATA[0] for DATA in OLD_DATA] + old_vals = [int(int(DATA[1]) / 60) for DATA in OLD_DATA] + if attr and vals: + line.add("上周", old_attr,old_vals, is_toolbox_show=False, is_smooth=True, mark_point=["max", "min"], + mark_point_symbolsize=80, is_datazoom_show=True, datazoom_range=[v for v in range(100, 10)], + datazoom_type='both', legend_pos='70%') + except Exception as e: + logging.error(e) + #监控数据展示 + try: + tm = datetime.datetime.now() - datetime.timedelta(minutes=1) + tm = tm.strftime('%H:%M') + z_triggers = RC.hgetall('zabbix_triggers_%s' %tm) + if z_triggers: + z_triggers = [[t,z_triggers[t]]for t in z_triggers] + except Exception as e: + logging.error(e) + #服务器预警信息 + try: + z_infos = defaultdict() + cpu_load = db_zabbix.query.with_entities(db_zabbix.ip,db_zabbix.ssh_port,db_zabbix.cpu_load).filter(and_(db_zabbix.cpu_load >100,db_zabbix.icmpping ==1)).all() + disk_io = db_zabbix.query.with_entities(db_zabbix.ip,db_zabbix.ssh_port,db_zabbix.disk_io).filter(and_(db_zabbix.disk_io>30,db_zabbix.icmpping ==1)).all() + openfile = db_zabbix.query.with_entities(db_zabbix.ip, db_zabbix.ssh_port, db_zabbix.openfile).filter(and_(db_zabbix.openfile >500000,db_zabbix.icmpping ==1)).all() + if cpu_load: + z_infos['cpu_load']=cpu_load + if disk_io: + z_infos['disk_io'] = disk_io + if openfile: + z_infos['openfile'] = openfile + except Exception as e: + logging.error(e) + # 获取问题服务器列表 + fault_servers = defaultdict() + try: + for key in ('ssh_login_fault_%s'%td, 'ssh_port_fault_%s'%td): + if RC.exists(key): + fault_vals = RC.hgetall(key) + if fault_vals: + fault_servers[key] = zip([fault_vals[val] for val in fault_vals],[val.split(':')[0] for val in fault_vals],[val.split(':')[1] for val in fault_vals]) + except Exception as e: + logging.error(e) + app_resp = make_response(render_template('index.html',gauge=gauge,line=line,tm=TM,z_triggers=z_triggers,z_infos=z_infos,fault_servers=fault_servers,BUSIS=BUSIS)) + app_resp.set_cookie('secret_key',tools.Produce(length=8,chars=string.digits),path='/') + return app_resp + except Exception as e: + logging.error(e) + flash('获取数据错误!',"error") + return render_template('Message.html') +@page_index.before_request +@check.login_required(grade=10) +def check_login(exception = None): + produce.Async_log(g.user, request.url) +@page_index.teardown_request +def db_remove(exception): + db_idc.DB.session.remove() \ No newline at end of file diff --git a/login.py b/login.py index 95cc8b53..872f3d79 100644 --- a/login.py +++ b/login.py @@ -1,76 +1,102 @@ -#-*- coding: utf-8 -*- -from flask import Blueprint,redirect,url_for,render_template,make_response,flash,request,session -from sqlalchemy import and_ -from Modules import db_op,MyForm, Md5, check ,produce -import string -import redis -import __init__ -app = __init__.app -limiter = __init__.limiter -redis_host = app.config.get('REDIS_HOST') -redis_port = app.config.get('REDIS_PORT') -Redis = redis.StrictRedis(host=redis_host, port=redis_port) -page_login = Blueprint('login', __name__) -@page_login.route('/login',methods = ['GET', 'POST']) -@limiter.limit("60/minute") -def login(): - form = MyForm.MyForm_login() - form.name.label = '用户名:' - form.password.label = '密码:' - user = request.cookies.get('user') - if user: - Incr = Redis.incr('%s_Incr' %user) - else: - Incr = 0 - if form.submit.data: - if form.name.data and form.password.data: - user = form.name.data - pw = form.password.data - Key_Incr = '%s_Incr' %user - Key_Lock = '%s_lock' %user - try: - if Incr >= 30: - raise flash('该帐号异常登陆,已被锁定3分钟!') - if Incr >= 5: - if form.code.data: - if str(form.code.data) != str(session['verify_code']): - raise flash('输入验证码错误!') - else: - raise flash('请输入验证码,看不清点击验证码刷新!') - va_p = db_op.idc_users.query.filter(and_(db_op.idc_users.name == user, db_op.idc_users.passwd == Md5.Md5_make(pw))).first() - produce.Async_log(user, request.url) - if va_p: - URL = url_for('index.index') - if pw == app.config.get('INIT_OP_PASSWORD'): - URL = url_for('pw.pw') - flash('请修改初始密码!') - timestamp = None - if form.remember_me.data: - timestamp = check.timestamp(7) - ID = produce.Produce(length=24,chars=string.hexdigits) - app_resp = make_response( redirect(URL)) - app_resp.set_cookie('user',user,expires=timestamp) - app_resp.set_cookie('ID',ID,expires=timestamp) - Redis.set('OP_ID_%s'%user,ID) - Redis.delete(Key_Lock) - Redis.delete(Key_Incr) - return app_resp - else: - Redis.incr(Key_Incr) - if Incr >= 30: - Redis.set(Key_Lock,'True') - Redis.expire(Key_Incr,60) - Redis.expire(Key_Lock,180) - flash('用户名或者密码错误!') - URL = url_for('login.login') - app_resp = make_response(redirect(URL)) - app_resp.set_cookie('user', user) - return app_resp - except Exception as e: - if 'old' not in str(e): - flash(str(e)) - return render_template('login.html',form=form,verify_incr = Incr) -@page_login.teardown_request -def db_remove(error=None): - db_op.DB.session.remove() - +#-*- coding: utf-8 -*- +from flask import Blueprint,redirect,url_for,render_template,make_response,request,json,flash,session +from sqlalchemy import and_ +from Modules import db_op, check,loging,Md5,tools +import string +import redis +import time +from Modules import init +import requests +import os +from flask_sqlalchemy import SQLAlchemy +from flask import Flask +app = Flask(__name__) +app.config.from_pyfile('conf/redis.conf') +app.config.from_pyfile('conf/main.conf') +app.config.from_pyfile('conf/redis.conf') +DB = SQLAlchemy(app) +ENV = None +if os.path.exists('/etc/opweb.conf'): + with open('/etc/opweb.conf','r') as f: + ENV = f.read().strip() +limiter = init.web_limiter() +limiter = limiter.limiter +logging = loging.Error() +redis_host = app.config.get('REDIS_HOST') +redis_port = app.config.get('REDIS_PORT') +redis_password = app.config.get('REDIS_PASSWORD') +Redis = redis.StrictRedis(host=redis_host, port=redis_port,decode_responses=True) +page_login = Blueprint('login', __name__) +@page_login.route('/login',methods = ['GET', 'POST']) +@limiter.limit("60/minute") +def login(): + try: + try: + token = tools.Produce(length=24, chars=string.hexdigits) + except Exception as e: + logging.error(e) + ym = time.strftime('%Y',time.localtime()) + session['Menu'] = {} + #钉钉验证授权 + if tools.http_args(request,'code') and tools.http_args(request,'state') == 'STATE': + db_auth = db_op.user_auth + code = tools.http_args(request,'code') + #获取token + try: + url = "https://oapi.dingtalk.com/sns/gettoken?appid=dingoadq3qon8zb34vzdff&appsecret=Tu6IlXjTn1m4vqrOA580xLOt2VbOK26bVu3sBOtvp0MnqIp2zpcwkL3qVpqAT7rG" + if ENV == 'dev': + url = "https://oapi.dingtalk.com/sns/gettoken?appid=dingoa7wymhx6dbeffjels&appsecret=I-v3OXL1hFKYZlJ3b6pqABmoNGYREXePpdzQ5JaSK8DqJdQyn_1J3wEUYBTpdiE_" + r = requests.get(url) + access_token = r.json()['access_token'] + r = requests.post("https://oapi.dingtalk.com/sns/get_persistent_code?access_token=%s" %access_token,data=json.dumps({"tmp_auth_code":code})) + openid = r.json()['openid'] + persistent_code = r.json()['persistent_code'] + r = requests.post("https://oapi.dingtalk.com/sns/get_sns_token?access_token=%s" %access_token,data=json.dumps({"openid": openid,"persistent_code": persistent_code})) + sns_token = r.json()['sns_token'] + #获取用户信息 + r = requests.get('https://oapi.dingtalk.com/sns/getuserinfo?sns_token=%s' %sns_token) + user_info = r.json()['user_info'] + nick = user_info['nick'] + dingId = user_info['dingId'] + except Exception as e: + logging.error(e) + #授权用户登陆 + if nick and dingId: + try: + val = db_auth.query.filter(and_(db_auth.dingId == dingId, db_auth.openid == openid)).all() + if val: + db_auth.query.filter(and_(db_auth.dingId == dingId, db_auth.openid == openid)).update({db_auth.token:token,db_auth.update_time:time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())}) + db_op.DB.session.commit() + URL = url_for('index.index') + timestamp = check.timestamp(7) + else: + #跳转至权限申请页 + URL = url_for('approval.apply') + timestamp = check.timestamp(1) + except Exception as e: + logging.error(e) + app_resp = make_response(redirect(URL)) + try: + app_resp.set_cookie('user', Md5.Md5_make(nick), expires=timestamp,path='/') + app_resp.set_cookie('openid', Md5.Md5_make(openid), expires=timestamp,path='/') + app_resp.set_cookie('dingId', Md5.Md5_make(dingId), expires=timestamp,path='/') + app_resp.set_cookie('token', Md5.Md5_make(token), expires=timestamp,path='/') + except Exception as e: + logging.error(e) + else: + Redis.set('OP_verify_%s' %dingId,token) + Redis.set('OP_token_%s' % Md5.Md5_make(token), token) + Redis.set('OP_dingId_%s' % Md5.Md5_make(dingId), dingId) + Redis.set('OP_user_%s' %Md5.Md5_make(nick),nick) + Redis.set('OP_openid_%s' % Md5.Md5_make(openid), openid) + return app_resp + except Exception as e: + flash('登录失败!') + logging.error(e) + finally: + db_op.DB.session.remove() + return render_template('login.html',ym=ym,ENV=ENV) +@page_login.teardown_request +def db_remove(error=None): + db_op.DB.session.remove() + diff --git a/logout.py b/logout.py index 28174f8e..2a739a75 100644 --- a/logout.py +++ b/logout.py @@ -1,11 +1,11 @@ -#-*- coding: utf-8 -*- -from flask import Blueprint,redirect,make_response -from Modules import check -page_logout = Blueprint('logout',__name__) -@page_logout.route('/logout') -def logout(): - timestamp = check.timestamp(0) - app_resp = make_response(redirect('/')) - app_resp.set_cookie('user', expires=timestamp) - app_resp.set_cookie('ID', expires=timestamp) +#-*- coding: utf-8 -*- +from flask import Blueprint,redirect,make_response,request +from Modules import check +page_logout = Blueprint('logout',__name__) +@page_logout.route('/logout') +def logout(): + timestamp = check.timestamp(0) + app_resp = make_response(redirect('/login')) + for key in request.cookies: + app_resp.set_cookie(key, expires=timestamp) return app_resp \ No newline at end of file diff --git a/main.py b/main.py index 542a2eba..c7c70b2e 100644 --- a/main.py +++ b/main.py @@ -1,25 +1,78 @@ -#-*- coding: utf-8 -*- -from flask import redirect,url_for,make_response,render_template -from flask_wtf.csrf import CsrfProtect -import index,login,logout -from flask_qrcode import QRcode -from flask_moment import Moment -import __init__ -app = __init__.app -app.debug = True -CsrfProtect(app) -moment = Moment(app) -qrcode = QRcode(app) -limiter = __init__.limiter -app.config.get('TRAP_HTTP_EXCEPTIONS') -app.register_blueprint(login.page_login) -app.register_blueprint(logout.page_logout) -app.register_blueprint(index.page_index) -@app.route('/') -@limiter.exempt -def main(): - return redirect(url_for('index.index')) -@app.errorhandler(404) -def page_not_found(error): - resp = make_response(render_template('404.html'),404) +#-*- coding: utf-8 -*- +import os +from flask import Flask,redirect,url_for,make_response,render_template,session,render_template_string +from flask_moment import Moment +from flask_sqlalchemy import SQLAlchemy +from api import ajax_api,assets_add,publish_code,assets_query,record_publish,deployment_deploy +import index,login,logout +import socket +import time +from views import chart_center,publish,deploy,k8s,k8s_deploy +from views import dns_conf,sch_list,app_service +from views import business_m,Error,report,influxdb_m +from views import Assets,mysql_info,business,approval +from admin import examine,assets_manage,resource_pool +from Modules import init,produce +from flask_debugtoolbar import DebugToolbarExtension +class MyFlask(Flask): + jinja_environment = init.FlaskEchartsEnvironment +app = MyFlask(__name__) +app.config.from_pyfile('conf/main.conf') +app.config.from_pyfile('conf/redis.conf') +app.config.from_pyfile('conf/task.conf') +app.config.from_pyfile('conf/sql.conf') +app.config.get('TRAP_HTTP_EXCEPTIONS') +task_servers = app.config.get('TASK_SERVERS') +DB = SQLAlchemy(app) +app.secret_key = os.urandom(24) +app.debug = False +limiter = init.web_limiter() +limiter = limiter.limiter +moment = Moment(app) +task_run = produce.Scheduler_backgroud() +toolbar = DebugToolbarExtension(app) +HOST = socket.gethostbyname(socket.gethostname()) +app.register_blueprint(Assets.page_Assets) +app.register_blueprint(mysql_info.page_mysql_info) +app.register_blueprint(assets_manage.page_assets_manage) +app.register_blueprint(publish.page_publish) +app.register_blueprint(resource_pool.page_resource_pool) +app.register_blueprint(chart_center.page_chart_center) +app.register_blueprint(examine.page_examine) +app.register_blueprint(dns_conf.page_dns_conf) +app.register_blueprint(sch_list.page_sch_list) +app.register_blueprint(ajax_api.page_ajax_api) +app.register_blueprint(business_m.page_business_monitor) +app.register_blueprint(Error.page_error) +app.register_blueprint(business.page_business) +app.register_blueprint(approval.page_approval) +app.register_blueprint(deploy.page_deploy) +app.register_blueprint(assets_add.page_assets_add) +app.register_blueprint(assets_query.page_assets_query) +app.register_blueprint(login.page_login) +app.register_blueprint(logout.page_logout) +app.register_blueprint(index.page_index) +app.register_blueprint(publish_code.page_publish_code) +app.register_blueprint(report.page_report) +app.register_blueprint(influxdb_m.page_influxdb_m) +app.register_blueprint(record_publish.page_record_publish) +app.register_blueprint(app_service.page_app_service) +app.register_blueprint(k8s.page_k8s) +app.register_blueprint(k8s_deploy.page_k8s_deploy) +app.register_blueprint(deployment_deploy.page_deployment_deploy) +if HOST in task_servers: + produce.scheduler_tasks() +task_run.Run() +@app.route('/') +@limiter.exempt +def main(): + session['Menu'] = {} + return redirect(url_for('index.index')) +@app.errorhandler(404) +def page_not_found(error): + resp = make_response(render_template('404.html',ym=time.strftime('%Y',time.localtime())),404) + return resp +@app.errorhandler(405) +def method_not_allowed(error): + resp = make_response(render_template_string("Method Not Allowed: 405 The method is not allowed for the requested URL!"),405) return resp \ No newline at end of file diff --git a/static/__init__.py b/static/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/static/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/static/css/all.css b/static/css/all.css new file mode 100644 index 00000000..03c0a79f --- /dev/null +++ b/static/css/all.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.3.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-concierge-bell:before{content:"\f562"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/static/css/all.min.css b/static/css/all.min.css new file mode 100644 index 00000000..a8ce3f55 --- /dev/null +++ b/static/css/all.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-concierge-bell:before{content:"\f562"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-r-project:before{content:"\f4f7"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;src:url(/static/font/fa-brands-400.eot);src:url(/static/font/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(/static/font/fa-brands-400.woff2) format("woff2"),url(/static/font/fa-brands-400.woff) format("woff"),url(/static/font/fa-brands-400.ttf) format("truetype"),url(/static/font/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;src:url(/static/font/fa-regular-400.eot);src:url(/static/font/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(/static/font/fa-regular-400.woff2) format("woff2"),url(/static/font/fa-regular-400.woff) format("woff"),url(/static/font/fa-regular-400.ttf) format("truetype"),url(/static/font/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;src:url(/static/font/fa-solid-900.eot);src:url(/static/font/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(/static/font/fa-solid-900.woff2) format("woff2"),url(/static/font/fa-solid-900.woff) format("woff"),url(/static/font/fa-solid-900.ttf) format("truetype"),url(/static/font/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/static/css/bootstrap.min.css b/static/css/bootstrap.min.css new file mode 100644 index 00000000..df96c864 --- /dev/null +++ b/static/css/bootstrap.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/static/css/buttons.css b/static/css/buttons.css new file mode 100644 index 00000000..8b0aa85b --- /dev/null +++ b/static/css/buttons.css @@ -0,0 +1,33 @@ +.btn-sub { + color: lightslategrey; + border-color: lightslategrey; + overflow: hidden; + position: relative; +} +.btn-sub:before, .btn-sub:after { + content: ""; + position: absolute; + z-index: -1; + height: 100%; + width: 0; + top: 0; + -webkit-transition: all .4s; + transition: all .4s; +} +.btn-sub:before { + left: -30px; + -webkit-transform: skew(-45deg, 0); + transform: skew(-45deg, 0); +} +.btn-sub:after { + right: -30px; + -webkit-transform: skew(-45deg, 0); + transform: skew(-45deg, 0); +} +.btn-sub:hover { + color:black; + border-color:black; +} +.btn-sub:hover:before, .btn-sub:hover:after { + width: 100%; +} \ No newline at end of file diff --git a/static/css/dandelion.css b/static/css/dandelion.css new file mode 100644 index 00000000..04343f8b --- /dev/null +++ b/static/css/dandelion.css @@ -0,0 +1,11 @@ +/* + * Dandelion Admin v1.0 - Obfuscated Main Stylesheet + * This file has been obfuscated using this tool: http://refresh-sf.com/yui/ + * Buyers will get a full, commented CSS source code in their downloadable package. + * + * This file is part of Dandelion Admin, an Admin template build for sale at ThemeForest. + * + */ +a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,meter,nav,object,ol,output,p,pre,progress,q,rp,rt,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video,xmp{border:0;margin:0;padding:0;font-size:100%}html,body{height:100%}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}b,strong{font-weight:bold}img{color:transparent;font-size:0;vertical-align:middle;-ms-interpolation-mode:bicubic}li{display:list-item}table{border-collapse:collapse;border-spacing:0}th,td,caption{font-weight:normal;vertical-align:top;text-align:left}q{quotes:none}q:before,q:after{content:'';content:none}sub,sup,small{font-size:75%}sub,sup{line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}svg{overflow:hidden} +body{background-image:url("/static/images/blueprint.png")}div#da-header #da-header-top{background-image:url("/static/images/carbon.png")} +::-moz-focus-inner{border:0;padding:0;margin:0}.da-button{display:inline-block;border:0;outline:0;font:12px/17px 'Helvetica Neue',Arial,Helvetica,sans-serif;padding:4px 12px;margin:0;width:auto;cursor:pointer;text-decoration:none;zoom:1;overflow:hidden;*overflow:visible;color:#fff;border:1px solid;text-shadow:1px 1px rgba(0,0,0,0.35);-webkit-border-radius:2px;-o-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-appearance:none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);-o-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);-webkit-tap-highlight-color:rgba(0,0,0,0)}.da-button.left{float:left}.da-button.small{padding:2px 8px}.da-button.large{padding:6px 16px}.da-button:active{background-image:none!important;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,1);-o-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,1)}.da-button:disabled{background-image:none;-webkit-box-shadow:none;-o-box-shadow:none;-moz-box-shadow:none;box-shadow:none;color:#dadada;background-color:#f8f8f8;border-color:#ddd;cursor:default}.da-button img{margin:0 2px;max-width:16px;max-height:16px}.da-button.blue{background-color:#61a4e4;background-image:linear-gradient(bottom,#61a4e4 0,#78b4ec 100%);background-image:-o-linear-gradient(bottom,#61a4e4 0,#78b4ec 100%);background-image:-moz-linear-gradient(bottom,#61a4e4 0,#78b4ec 100%);background-image:-webkit-linear-gradient(bottom,#61a4e4 0,#78b4ec 100%);background-image:-ms-linear-gradient(bottom,#61a4e4 0,#78b4ec 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#61a4e4),color-stop(1,#78b4ec));border-color:#21629c}.da-button,.da-button.green{background-color:#a7d037;background-image:linear-gradient(bottom,#a7d037 0,#c8e342 100%);background-image:-o-linear-gradient(bottom,#a7d037 0,#c8e342 100%);background-image:-moz-linear-gradient(bottom,#a7d037 0,#c8e342 100%);background-image:-webkit-linear-gradient(bottom,#a7d037 0,#c8e342 100%);background-image:-ms-linear-gradient(bottom,#a7d037 0,#c8e342 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#a7d037),color-stop(1,#c8e342));border-color:#779625}.da-button.red{background-color:#e15656;background-image:linear-gradient(bottom,#e15656 0,#f77272 100%);background-image:-o-linear-gradient(bottom,#e15656 0,#f77272 100%);background-image:-moz-linear-gradient(bottom,#e15656 0,#f77272 100%);background-image:-webkit-linear-gradient(bottom,#e15656 0,#f77272 100%);background-image:-ms-linear-gradient(bottom,#e15656 0,#f77272 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#e15656),color-stop(1,#f77272));border-color:#bb2929}.da-button.pink{background-color:#ea799b;background-image:linear-gradient(bottom,#ea799b 0,#eea3bc 100%);background-image:-o-linear-gradient(bottom,#ea799b 0,#eea3bc 100%);background-image:-moz-linear-gradient(bottom,#ea799b 0,#eea3bc 100%);background-image:-webkit-linear-gradient(bottom,#ea799b 0,#eea3bc 100%);background-image:-ms-linear-gradient(bottom,#ea799b 0,#eea3bc 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#ea799b),color-stop(1,#eea3bc));border-color:#b04264}.da-button.gray{background-color:#e3e3e3;background-image:linear-gradient(bottom,#e3e3e3 0,#f9f9f9 100%);background-image:-o-linear-gradient(bottom,#e3e3e3 0,#f9f9f9 100%);background-image:-moz-linear-gradient(bottom,#e3e3e3 0,#f9f9f9 100%);background-image:-webkit-linear-gradient(bottom,#e3e3e3 0,#f9f9f9 100%);background-image:-ms-linear-gradient(bottom,#e3e3e3 0,#f9f9f9 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#e3e3e3),color-stop(1,#f9f9f9));color:#666;text-shadow:1px 1px rgba(0,0,0,0.1);border-color:#9e9e9e}.da-button.orange{background-color:#fab341;background-image:linear-gradient(bottom,#fab341 0,#ffcb72 100%);background-image:-o-linear-gradient(bottom,#fab341 0,#ffcb72 100%);background-image:-moz-linear-gradient(bottom,#fab341 0,#ffcb72 100%);background-image:-webkit-linear-gradient(bottom,#fab341 0,#ffcb72 100%);background-image:-ms-linear-gradient(bottom,#fab341 0,#ffcb72 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#fab341),color-stop(1,#ffcb72));border-color:#cc731e}.da-highlight{color:#d44d24}::-webkit-input-placeholder{color:#999}:-moz-placeholder{color:#999}.placeholder{color:#999}a{color:#73991f;text-decoration:none;outline:0}a:hover{text-decoration:underline}::-moz-selection{color:#fff;background:#a9ca60}::selection{color:#fff;background:#a9ca60}body{background-color:#f2f2f2;color:#444;font:12px/1.5 'Helvetica Neue',Arial,Helvetica,sans-serif}div#da-wrapper{height:auto;min-height:100%;position:relative;min-width:320px}div#da-wrapper,div#da-wrapper.fluid{width:100%}div#da-wrapper .da-container,div#da-wrapper.fluid .da-container{width:96%;margin:auto}div#da-wrapper.fixed{min-width:960px}div#da-wrapper.fixed .da-container{width:960px}div#da-header{z-index:200;position:relative}div#da-header #da-header-top{position:relative}div#da-header #da-header-top #da-logo-wrap{float:left}div#da-header #da-header-top #da-logo{width:230px;height:55px;display:table-cell;vertical-align:middle}div#da-header #da-header-top #da-logo-img img{max-width:230px;max-height:55px}div#da-header #da-header-top #da-header-toolbar{float:right;border-left:1px solid #161616;border-left:1px solid rgba(0,0,0,0.6)}div#da-header #da-header-top #da-header-toolbar #da-user-profile,div#da-header #da-header-top #da-header-toolbar #da-header-button-container{float:left;position:relative}div#da-header #da-header-top #da-header-toolbar #da-user-profile{border-left:1px solid #535353;border-left:1px solid rgba(255,255,255,0.2);border-right:1px solid #161616;border-right:1px solid rgba(0,0,0,0.6);padding:0 12px;cursor:pointer;background:url("/static//images/user-drop-arrow.png") no-repeat right center;padding-right:24px}div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-avatar,div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-info{float:left}div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-avatar{height:32px;padding:12px 0;margin-right:12px}div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-avatar img{max-width:32px;max-height:32px}div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-info{font-size:12px;color:#fff;line-height:1;padding:13px 0;font-weight:bold;position:relative}div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-info .da-user-title{color:#eaeaea;font-size:11px;margin-top:6px;font-weight:normal;display:block}div#da-header #da-header-top #da-header-toolbar #da-header-button-container{border-left:1px solid #535353;border-left:1px solid rgba(255,255,255,0.2);padding:10px 0 10px 7px}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul{margin-bottom:0}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li{margin-left:0;list-style:none}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button{float:left;display:block;width:36px;height:36px;margin-left:4px;position:relative;background:url("/static/images/toolbar-button.png") no-repeat center top}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button.active{background-position:center bottom}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button.active>a{margin-top:1px}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button>a{text-decoration:none;text-indent:-99999px;display:block;margin:auto;cursor:pointer;width:36px;height:36px;outline:0;background-position:center center;background-repeat:no-repeat}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.notif.da-header-button>a{background-image:url(ƒ"/static/images/blueprint.png")}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.message.da-header-button>div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.logout.da-header-button>div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button span.da-button-count{position:absolute;color:#fff;left:100%;margin-left:-12px;top:-4px;font-size:10px;display:block;padding:4px;line-height:1;z-index:250;min-width:10px;text-align:center;background:#e15656;border:1px solid #900;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25);-o-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25);-webkit-border-radius:9px;-o-border-radius:9px;-moz-border-radius:9px;border-radius:9px}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button.active span.da-button-count{top:-3px}div#da-header #da-header-top #da-header-toolbar #da-header-button-container ul li.da-header-button .da-header-dropdown{margin-top:11px}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown{position:absolute;top:100%;right:0;display:none;padding:4px 0;margin:2px 0 0;background-color:#fff;border:1px solid #c5c5c5;border:1px solid rgba(100,100,100,0.4);-webkit-border-radius:3px;-moz-border-radius:3px;-o-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.25);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.25);-o-box-shadow:0 5px 10px rgba(0,0,0,0.25);box-shadow:0 5px 10px rgba(0,0,0,0.25)}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li{margin:0;list-style:none}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li.da-dropdown-caret{position:absolute;top:-8px;width:20px;height:8px;overflow:hidden}div#da-header #da-header-top #da-header-toolbar #da-user-profile ul.da-header-dropdown li.da-dropdown-caret{right:30px}div#da-header #da-header-top #da-header-toolbar .da-header-button ul.da-header-dropdown li.da-dropdown-caret{right:8px}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li.da-dropdown-caret .caret-outer,div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li.da-dropdown-caret .caret-inner{padding:0;margin:0;position:absolute;top:0;left:0;display:block}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li.da-dropdown-caret .caret-outer{border-left:11px solid transparent;border-right:11px solid transparent;border-bottom:11px solid #aaa;border-bottom-color:rgba(0,0,0,0.2)}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li.da-dropdown-caret .caret-inner{top:1px;left:1px;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid white}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li.da-dropdown-divider{line-height:1;padding-top:1px;margin:5px 1px 6px;border-bottom:1px solid #DDD}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li a,div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li span{position:relative;display:block;float:none;clear:both;text-decoration:none;padding:3px 22px;font-size:12px;font-weight:normal;line-height:18px;color:#444;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li a:hover{background-color:#a9ca60;color:#fff}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li ul.da-dropdown-sub{width:200px}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li .da-dropdown-sub-title{font-size:12px;font-weight:bold;padding:9px 22px;background-color:#eee;border-top:1px solid #cacaca;border-bottom:1px solid #cacaca}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li .da-dropdown-sub-footer{padding:5px 22px;cursor:pointer;background-color:#eee;border-bottom:1px solid #cacaca}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown li .da-dropdown-sub-footer:hover{background-color:#e0e0e0;color:#444}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li{border-bottom:1px solid #cacaca;overflow:hidden}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li>a,div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li>span{padding:6px 22px}div#da-header #da-header-top #da-header-toolbar .message ul.da-header-dropdown ul.da-dropdown-sub li>a,div#da-header #da-header-top #da-header-toolbar .message ul.da-header-dropdown ul.da-dropdown-sub li>span{background:url("../images/icons/color/email.png") no-repeat 12px center;padding-left:40px}div#da-header #da-header-top #da-header-toolbar .message ul.da-header-dropdown ul.da-dropdown-sub li.read>a,div#da-header #da-header-top #da-header-toolbar .message ul.da-header-dropdown ul.da-dropdown-sub li.read>span{background-image:url("../images/icons/color/email_open.png")}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li.unread a,div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li.unread span,div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li a:hover,div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li span:hover{background-color:#f8f8f8}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li span{padding:0}div#da-header #da-header-top #da-header-toolbar ul.da-header-dropdown ul.da-dropdown-sub li span.time{font-size:10px;color:#666}div#da-header #da-header-bottom{background:url("../images/header-bottom.png") repeat-x left top}div#da-header #da-header-bottom #da-search{float:left;width:180px;padding:9px 0;position:relative}div#da-header #da-header-bottom #da-search input[type="text"]{width:100%;background:#f0f0f0 url("../images/search-icon.png") no-repeat right center;border:1px solid #cecece;margin:0;outline:0;padding:4px 12px;padding-right:24px;-webkit-border-radius:6px;-moz-border-radius:6px;-o-border-radius:6px;border-radius:6px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-o-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div#da-header #da-header-bottom #da-search input[type="text"]:focus{background-color:#fff}div#da-header #da-header-bottom #da-breadcrumb{margin-left:230px}div#da-header #da-header-bottom #da-breadcrumb ul,div#da-header #da-header-bottom #da-breadcrumb ul li{margin:0;list-style:none}div#da-header #da-header-bottom #da-breadcrumb ul li{display:inline-block;line-height:1;padding:15px 0;margin-right:24px}div#da-header #da-header-bottom #da-breadcrumb ul li img{max-width:16px;max-height:16px;margin-right:10px;float:left}div#da-header #da-header-bottom #da-breadcrumb ul li a,div#da-header #da-header-bottom #da-breadcrumb ul li span{text-decoration:none;color:#444;line-height:16px}div#da-header #da-header-bottom #da-breadcrumb ul li span{cursor:default}div#da-header #da-header-bottom #da-breadcrumb ul li.active{border-top:5px solid #a9ca60;padding-top:10px;padding-bottom:22px;background:url("../images/breadcrumb-arrow.png") no-repeat center bottom}div#da-content{clear:both;padding-bottom:58px}div#da-wrapper #da-sidebar-separator,div#da-wrapper.fluid #da-sidebar-separator{position:absolute;left:180px;width:50px;margin-left:2%;top:0;bottom:0;background:url("../images/container-separator.png") repeat-y center top}div#da-wrapper.fixed #da-sidebar-separator{left:50%;margin-left:-300px}div#da-content #da-sidebar{position:relative;float:left;z-index:150}div#da-content #da-sidebar .da-button-container{padding:2px;margin-bottom:6px;border:1px solid #cacaca;-webkit-border-radius:4px;-o-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,1);-o-box-shadow:inset 0 1px 0 rgba(255,255,255,1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,1);box-shadow:inset 0 1px 0 rgba(255,255,255,1);background-color:#fff;background-image:linear-gradient(bottom,#fff 0,#f1f1f1 100%);background-image:-o-linear-gradient(bottom,#fff 0,#f1f1f1 100%);background-image:-moz-linear-gradient(bottom,#fff 0,#f1f1f1 100%);background-image:-webkit-linear-gradient(bottom,#fff 0,#f1f1f1 100%);background-image:-ms-linear-gradient(bottom,#fff 0,#f1f1f1 100%);background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#fff),color-stop(1,#f1f1f1))}div#da-content #da-sidebar #da-main-nav.da-button-container{width:174px}div#da-content #da-main-nav ul,div#da-content #da-main-nav ul li{margin:0;list-style:none}div#da-content #da-main-nav ul{border:1px solid #cacaca;background:#fff;-webkit-border-radius:4px;-o-border-radius:4px;-moz-border-radius:4px;border-radius:4px}div#da-content #da-main-nav ul li{display:block;border-top:1px solid #fff;border-bottom:1px solid #cacaca;background-color:#fdfdfd;-webkit-box-shadow:inset 0 0 2px rgba(255,255,255,1);-o-box-shadow:inset 0 0 2px rgba(255,255,255,1);-moz-box-shadow:inset 0 0 2px rgba(255,255,255,1);box-shadow:inset 0 0 2px rgba(255,255,255,1)}div#da-content #da-main-nav ul li:first-child{border-top:0;-webkit-border-radius:4px 4px 0 0;-o-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}div#da-content #da-main-nav ul li:last-child{border-bottom:0;-webkit-border-radius:0 0 4px 4px;-o-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}div#da-content #da-main-nav ul li a,div#da-content #da-main-nav ul li span{display:block;color:#444;cursor:pointer;text-decoration:none;padding:18px 32px 18px 48px;outline:0;position:relative;background:url("../images/menu-bulb-off.png") right center no-repeat}div#da-content #da-main-nav ul li.active a,div#da-content #da-main-nav ul li.active span{background-image:url("../images/menu-bulb-on.png")}div#da-content #da-main-nav ul li span.da-nav-count{display:block;position:absolute;left:0;top:-1px;background:0;margin:0;font-size:10px;line-height:1;z-index:100;text-align:center;background:#f0f0f0;border:1px solid #bbb;min-width:10px;padding:2px 4px;border-left:0;border-top:0;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.25);-moz-box-shadow:inset 0 0 6px rgba(0,0,0,0.25);-o-box-shadow:inset 0 0 6px rgba(0,0,0,0.25);box-shadow:inset 0 0 6px rgba(0,0,0,0.25);-webkit-border-radius:0 0 2px 0;-moz-border-radius:0 0 2px 0;-o-border-radius:0 0 2px 0;border-radius:0 0 2px 0}div#da-content #da-main-nav ul li span.da-nav-icon{padding:0;margin:0;width:32px;height:32px;background:none!important;position:absolute;left:8px;top:50%;margin-top:-16px}div#da-content #da-main-nav ul li span.da-nav-icon img{max-width:24px;max-height:24px;position:absolute;left:50%;top:50%;margin-left:-12px;margin-top:-12px}div#da-content #da-main-nav ul li ul{border:0;background:#e9e9e9 url("../images/submenu-shadow.png") repeat-x left top;-webkit-border-radius:0;-o-border-radius:0;-moz-border-radius:0;border-radius:0}div#da-content #da-main-nav ul li ul.closed{display:none}div#da-content #da-main-nav ul li ul li{border:0;background:0;font-size:12px;-webkit-box-shadow:none;-o-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div#da-content #da-main-nav ul li ul li:hover{background:url("../images/menu-hover.png")}div#da-content #da-main-nav ul li ul li a,div#da-content #da-main-nav ul li ul li span{padding:6px 0;padding-left:48px;background:none!important}div#da-content #da-content-wrap{margin-left:230px;padding-bottom:20px;margin-top:20px}div#da-content #da-content-wrap #da-content-area{float:left;width:100%;display:block}div#da-footer{background:#fff;border-top:1px solid #d0d0d0;padding:20px 0;text-align:center;position:absolute;bottom:0;width:100%}div#da-footer p{margin:0}@media only screen and (max-width:1023px){div#da-wrapper.fixed{min-width:0}div#da-wrapper.fixed .da-container{width:96%}}@media only screen and (min-width:481px) and (max-width:1023px){div#da-header #da-header-bottom #da-search{width:115px}div#da-header #da-header-bottom #da-breadcrumb{margin-left:165px}div#da-content #da-sidebar-separator{left:115px!important}div#da-wrapper.fixed #da-sidebar-separator{margin-left:2%}div#da-content #da-sidebar #da-main-nav.da-button-container{width:109px}div#da-content #da-main-nav ul li span.da-nav-icon{position:relative;width:auto;height:auto;left:auto;top:auto;text-align:center;margin:0 0 4px 0}div#da-content #da-main-nav ul li span.da-nav-icon img{max-width:none;max-height:none;position:relative;left:auto;top:auto;margin-left:0;margin-top:0}div#da-content #da-main-nav ul li a,div#da-content #da-main-nav ul li span{padding:16px;text-align:center;background-position:right top}div#da-content #da-main-nav ul li ul li a,div#da-content #da-main-nav ul li ul li span{padding:6px 0;text-align:center}div#da-content #da-content-wrap{margin-left:165px}}@media only screen and (max-width:768px){div#da-header #da-header-top da-header-toolbar #da-user-profile #da-user-avatar{margin:0}div#da-header #da-header-top #da-header-toolbar #da-user-profile #da-user-info{display:none}}@media only screen and (max-width:480px){div#da-wrapper .da-container{width:100%!important}div#da-header #da-header-bottom{height:auto}div#da-header #da-header-top #da-logo-wrap{float:none;position:absolute;width:100%;height:55px;border-bottom:1px solid #161616}div#da-header #da-header-top #da-logo{position:absolute;left:50%;text-align:center;margin-left:-115px}div#da-header #da-header-top #da-header-toolbar{border:0;width:100%;margin-top:56px;border-top:1px solid #535353}div#da-header #da-header-top #da-header-toolbar #da-user-profile{border:0;float:left}div#da-header #da-header-top #da-header-toolbar #da-header-button-container{padding:10px 7px;border:0;float:right}div#da-header #da-header-top #da-header-toolbar #da-user-profile ul.da-header-dropdown{right:auto;left:0}div#da-header #da-header-top #da-header-toolbar #da-user-profile ul.da-header-dropdown li.da-dropdown-caret{right:auto;left:16px}div#da-header #da-header-bottom #da-search{display:none}div#da-header #da-header-bottom #da-breadcrumb{display:none}div#da-content #da-sidebar-separator{display:none}div#da-content #da-sidebar{display:block;height:55px;width:100%;cursor:pointer;float:none;background:url("../images/icons/black/16/list.png") center 14px no-repeat,url("../images/header-bottom.png") repeat-x left top}div#da-content #da-sidebar #da-main-nav.da-button-container{width:100%;padding:0;background:0;border:0}div#da-content #da-sidebar #da-main-nav ul{border:0}div#da-content #da-sidebar #da-main-nav ul,div#da-content #da-sidebar #da-main-nav ul li{-webkit-border-radius:0;-moz-border-radius:0;-o-border-radius:0;border-radius:0}div#da-content #da-main-nav>ul>li:last-child{border-bottom:1px solid #cacaca}div#da-content #da-main-nav{position:absolute;display:none;top:41px}div#da-content #da-main-nav.open{display:block}div#da-content{margin-top:auto}div#da-content #da-content-wrap{margin:0 2%}#da-customizer{display:none}}#da-customizer{z-index:300;position:fixed;top:0;left:50%;margin-left:-91px}#da-customizer #da-customizer-content{background:#fff;padding:8px 16px;width:150px;height:0;padding-top:0;overflow:hidden;border-color:#999;border-color:rgba(0,0,0,.2);border-style:solid;border-width:1px;border-top-width:0;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;-o-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-o-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}#da-customizer #da-customizer-content ul,#da-customizer #da-customizer-content ul li{margin:0;list-style:none}#da-customizer #da-customizer-content ul{margin-bottom:5px}#da-customizer #da-customizer-content ul li{padding:4px}#da-customizer #da-customizer-content ul li span.da-customizer-title{display:block;margin-bottom:4px}#da-customizer #da-customizer-content ul li select{background-color:#fcfcfc;border:1px solid #d1d1d1;padding:4px;margin:0;outline:0;width:100%;-webkit-border-radius:2px;-o-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}#da-customizer ul#da-customizer-layouts{list-style:none;margin:0}#da-customizer ul#da-customizer-layouts li{float:left;padding:0;padding-right:8px}#da-customizer ul#da-customizer-layouts li label,#da-customizer ul#da-customizer-layouts li input{display:inline;margin:0;padding:0;width:auto;margin-left:2px;vertical-align:middle}#da-customizer span.da-customizer-tooltip{font-size:.9em;display:inline-block;line-height:1;cursor:pointer}#da-customizer #da-customizer-button{padding-bottom:5px}#da-customizer #da-customizer-button button{width:100%}#da-customizer-dialog textarea{background-color:#fcfcfc;border:0;padding:6px;outline:0;width:100%;margin:0;color:#444;font-size:1em;height:200px;resize:none;font-family:"Courier New",Courier,monospace;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);-moz-box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);-o-box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04)}#da-customizer #da-customizer-pulldown{display:block;margin-left:71px;width:40px;height:40px;cursor:pointer;margin-top:-1px;background:url("../images/icons/black/32/cog_5.png") no-repeat center center #fff;border-color:#999;border-color:rgba(0,0,0,.2);border-style:solid;border-width:1px;border-top-width:0;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;-o-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-o-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}div#da-error-wrapper{width:320px;padding:30px 0;margin:auto;position:relative}div#da-error-wrapper .da-error-heading{color:#e15656;text-align:center;font-size:24px;font-family:Georgia,"Times New Roman",Times,serif}@-webkit-keyframes error-swing{0%{-webkit-transform:rotate(1deg)}100%{-webkit-transform:rotate(-2deg)}}@-moz-keyframes error-swing{0%{-moz-transform:rotate(1deg)}100%{-moz-transform:rotate(-2deg)}}@keyframes error-swing{0%{transform:rotate(1deg)}100%{transform:rotate(-2deg)}}div#da-error-wrapper #da-error-code{width:285px;height:170px;padding:127px 16px 0 16px;position:relative;margin:auto;margin-bottom:20px;z-index:5;line-height:1;font-size:32px;text-align:center;background:url("/static/images/error-hanger.png") no-repeat center center;-webkit-transform-origin:center top;-moz-transform-origin:center top;transform-origin:center top;-webkit-animation:error-swing infinite 2s ease-in-out alternate;-moz-animation:error-swing infinite 2s ease-in-out alternate;animation:error-swing infinite 2s ease-in-out alternate}div#da-error-wrapper #da-error-code span{font-size:96px;display:block}div#da-error-wrapper #da-error-pin{width:38px;height:38px;display:block;margin:auto;margin-bottom:-27px;z-index:10;position:relative;background:url("/static/images/error-pin.png") no-repeat center center}div#da-error-wrapper p{text-align:center;font-size:14px}div#da-error-wrapper p a{ margin:5px;color:#fff;background:#a6d037;text-decoration:none;padding:1px 6px;display:inline-block;-webkit-border-radius:4px;-o-border-radius:4px;-moz-border-radius:4px;border-radius:4px}form.da-form input{outline:0}form.da-form input[type="text"],form.da-form input[type="password"],form.da-form select,form.da-form textarea,form.da-form .customfile{background-color:#fcfcfc;border:1px solid #d1d1d1;padding:6px;outline:0;width:100%;margin:0;color:#444;font-size:1em;-webkit-border-radius:2px;-o-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);-moz-box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);-o-box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);box-shadow:0 1px 0 rgba(255,255,255,1),inset 0 1px 1px rgba(0,0,0,0.04);-webkit-transition:background 150ms linear;-moz-transition:background 150ms linear;-ms-transition:background 150ms linear;-o-transition:background 150ms linear;transition:background 150ms linear}form.da-form input[type="text"]:focus,form.da-form input[type="password"]:focus,form.da-form select:focus,form.da-form textarea:focus{border-color:#bbc1c9;background-color:#fff}form.da-form input[type="text"].error,form.da-form input[type="password"].error,form.da-form select.error,form.da-form textarea.error{border-color:#e48c85}form.da-form input[type="text"]:disabled,form.da-form input[type="password"]:disabled,form.da-form select:disabled,form.da-form textarea:disabled{background:#f0f0f0}form.da-form textarea{height:10em;margin:2px 0;resize:none}.customfile-input{position:absolute;height:100px;cursor:pointer;background:transparent;border:0;opacity:0;-moz-opacity:0;filter:alpha(opacity=0);z-index:999999}form.da-form .customfile{position:relative;overflow:hidden}.customfile-disabled{opacity:.5;filter:alpha(opacity=50);cursor:default}.customfile-feedback{line-height:15px;display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.customfile-button{outline:0;float:right;line-height:15px;padding:6px 10px;margin:-7px -7px -7px 0;cursor:pointer;z-index:9999;background:#f3f3f3 url("../images/button-gradient.png") repeat-x left bottom;border:1px solid #d1d1d1;-webkit-border-radius:0 2px 2px 0;-o-border-radius:0 2px 2px 0;-moz-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0;-webkit-box-shadow:inset 0 1px 1px rgba(255,255,255,0.6);-o-box-shadow:inset 0 1px 1px rgba(255,255,255,0.6);-moz-box-shadow:inset 0 1px 1px rgba(255,255,255,0.6);box-shadow:inset 0 1px 1px rgba(255,255,255,0.6)}.customfile:not(.customfile-disabled):active .customfile-button{background-image:none;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);-o-box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.1)}form.da-form .da-form-row .ui-slider{margin-top:12px;margin-bottom:24px}form.da-form .da-form-row .ui-progressbar{margin-top:7px;margin-bottom:24px}form.da-form .da-form-row .chzn-container-multi .chzn-choices .search-field input{-webkit-border-radius:0;-moz-border-radius:0;-o-border-radius:0;border-radius:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;-o-box-sizing:content-box;box-sizing:content-box}.da-message{font-size:12px;border-bottom:1px solid #d2d2d2;padding:15px 8px 15px 45px;position:relative;vertical-align:middle;cursor:pointer;background-color:#f8f8f8;background-position:12px 12px;background-repeat:no-repeat}.da-message p,.da-message ul,.da-message ol{margin:0}.da-message ul li,.da-message ol li{list-style-position:inside;list-style-type:inherit;margin:0}.da-message.error{background-color:#ffcbca;background-image:url("../images/message-error.png");border-color:#eb979b;color:#9b4449}.da-message.error .da-message-close{background-position:right bottom}.da-message.success{background-color:#e1f1c0;background-image:url("../images/message-success.png");border-color:#b5d56d;color:#62a426}.da-message.success .da-message-close{background-position:left bottom}.da-message.warning{background-color:#fef0b1;background-image:url("../images/message-warning.png");border-color:#ddca76;color:#a98b15}.da-message.warning .da-message-close{background-position:right top}.da-message.info{background-color:#bce5f7;background-image:url("../images/message-info.png");border-color:#a6d3e8;color:#11689e}.da-message.info .da-message-close{background-position:left top}form.da-form .da-button-row:after,form.da-form .da-form-row:after,form.da-form:after,form.da-form-item:after,.da-form ul.da-form-list:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .da-button-row,* html .da-form-row,* html .da-form,* html .da-form-item,* html .da-form ul.da-form-list{height:1%}.da-form .da-form-row{padding:23px 16px;display:block;border-bottom:1px solid #d3d3d3}.da-form .da-form-row:last-child{border-bottom:0}.da-form .da-button-row{background-color:#f2f0f0;border-top:1px solid #d3d3d3;margin:0;padding:16px;margin-top:-1px;text-align:right;-webkit-border-radius:0 0 4px 4px;-o-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,1)}form.da-form .da-form-col-1-8 label,form.da-form .da-form-col-2-8 label,form.da-form .da-form-col-3-8 label,form.da-form .da-form-col-4-8 label,form.da-form .da-form-col-5-8 label,form.da-form .da-form-col-6-8 label,form.da-form .da-form-col-7-8 label,form.da-form .da-form-col-8-8 label{white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis}form.da-form .da-form-col-1-8,form.da-form .da-form-col-2-8,form.da-form .da-form-col-3-8,form.da-form .da-form-col-4-8,form.da-form .da-form-col-5-8,form.da-form .da-form-col-6-8,form.da-form .da-form-col-7-8,form.da-form .da-form-col-8-8{float:left;padding:0 1%;box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-webkit-box-sizing:border-box;-khtml-box-sizing:border-box}form.da-form .da-form-col-1-8.alpha,form.da-form .da-form-col-2-8.alpha,form.da-form .da-form-col-3-8.alpha,form.da-form .da-form-col-4-8.alpha,form.da-form .da-form-col-5-8.alpha,form.da-form .da-form-col-6-8.alpha,form.da-form .da-form-col-7-8.alpha,form.da-form .da-form-col-8-8.alpha{padding-left:0;clear:left}form.da-form .da-form-col-1-8.omega,form.da-form .da-form-col-2-8.omega,form.da-form .da-form-col-3-8.omega,form.da-form .da-form-col-4-8.omega,form.da-form .da-form-col-5-8.omega,form.da-form .da-form-col-6-8.omega,form.da-form .da-form-col-7-8.omega,form.da-form .da-form-col-8-8.omega{padding-right:0;clear:right}form.da-form .da-form-col-1-8{width:12.5%}form.da-form .da-form-col-2-8{width:25%}form.da-form .da-form-col-3-8{width:37.5%}form.da-form .da-form-col-4-8{width:50%}form.da-form .da-form-col-5-8{width:62.5%}form.da-form .da-form-col-6-8{width:75%}form.da-form .da-form-col-7-8{width:87.5%}form.da-form .da-form-col-8-8{width:100%}.da-form ul.da-form-list{display:block;width:100%!important}.da-form ul.da-form-list.inline{margin-bottom:0}.da-form ul.da-form-list,.da-form ul.da-form-list li{margin:0;line-height:normal;list-style:none}.da-form ul.da-form-list{margin-bottom:4px}.da-form ul.da-form-list li{margin-bottom:4px}.da-form ul.da-form-list li:last-child{margin:0}.da-form ul.da-form-list.inline li{float:left;margin:5px 10px 0 0}.da-form ul.da-form-list li input,.da-form ul.da-form-list li label{float:none!important;padding:0;margin:0 2px 0 0;width:auto!important;display:inline!important;vertical-align:middle!important}.da-form label{cursor:pointer;display:block}.da-form label .required{color:#f00}.da-form fieldset{margin-bottom:0;border-top:1px solid #d3d3d3}.da-form fieldset legend{padding:4px 6px;margin:0 10px;background-color:#fafafa;border:1px solid #d3d3d3}.da-form .da-form-item .errorMessage,.da-form .da-form-item label.error{display:block;color:#d44d24;font-size:11px;width:100%!important;margin-top:1px;padding:0!important}.da-form .da-form-item .formNote{display:block;color:#555;font-size:11px;margin-bottom:1px;font-style:italic;margin-top:-17px;text-align:right;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.da-form .da-form-item a.formNote{color:#73991f}.da-form .da-form-item .formNote.left{text-align:left}.da-form .da-form-row .da-form-item{position:relative}.da-form .da-form-row .da-form-item>*,.da-form .da-form-row .da-form-item.default>*{width:70%}.da-form .da-form-row .da-form-item.small>*{width:35%}.da-form .da-form-row .da-form-item.large>*{width:100%}.da-form .da-form-row,.da-form .da-form-inline .da-form-row,.da-form .da-form-row.da-form-inline{width:100%;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.da-form .da-form-row label,.da-form .da-form-inline .da-form-row label,.da-form .da-form-row.da-form-inline label{float:left;width:120px;padding:6px 0}.da-form .da-form-row .da-form-item,.da-form .da-form-inline .da-form-row .da-form-item,.da-form .da-form-row.da-form-inline .da-form-item{margin-left:136px}.da-form .da-form-block .da-form-row,.da-form .da-form-row.da-form-block{width:100%;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.da-form .da-form-block .da-form-row label,.da-form .da-form-row.da-form-block label{float:none;width:auto;display:block;padding:0 0 6px 0}.da-form .da-form-block .da-form-row .da-form-item,.da-form .da-form-row.da-form-block .da-form-item{margin:0;display:block}@media only screen and (max-width:768px){.da-form .da-form-row label,.da-form .da-form-inline .da-form-row label,.da-form .da-form-row.da-form-inline label{clear:both;float:none;width:auto;padding:0 0 6px 0}.da-form .da-form-row .da-form-item,.da-form .da-form-inline .da-form-row .da-form-item,.da-form .da-form-row.da-form-inline .da-form-item{clear:both;float:none;margin:0}.da-form .da-form-item .formNote{text-align:left;margin-top:auto}}@media only screen and (max-width:480px){.da-form .da-form-row{padding:12px}.da-form .da-button-row{padding:8px}.da-form .da-form-item .errorMessage,.da-form .da-form-item label.error{margin:1px 0 0 0}}.da-gallery ul,.da-gallery ul li{margin:0;list-style:none;text-align:center}.da-gallery ul li{display:inline-block;position:relative;margin:15px;width:150px;height:150px}.da-gallery ul li a{display:block;position:relative;z-index:15;zoom:1}.da-gallery ul li a img{padding:6px;max-width:138px;max-height:138px;background-color:#fff;border:1px solid #dcdcdc;border:1px solid rgba(255,255,255,0);-webkit-border-radius:3px;-o-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.4);-o-box-shadow:0 1px 4px rgba(0,0,0,0.4);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.4);box-shadow:0 1px 4px rgba(0,0,0,0.4)}.da-gallery ul li a:after,.da-gallery ul li a:before{content:"";position:absolute;width:100%;height:100%;border:6px solid #fafafa;background-color:#808080;left:0;top:0;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.4);-o-box-shadow:0 1px 4px rgba(0,0,0,0.4);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.4);box-shadow:0 1px 4px rgba(0,0,0,0.4);-webkit-border-radius:3px;-o-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.da-gallery ul li a:after{z-index:-10;-webkit-transform:rotate(8deg);-o-transform:rotate(8deg);-moz-transform:rotate(8deg);transform:rotate(8deg)}.da-gallery ul li a:before{z-index:-5;-webkit-transform:rotate(4deg);-o-transform:rotate(4deg);-moz-transform:rotate(4deg);transform:rotate(4deg)}.da-gallery ul li:nth-child(even) a:after{-webkit-transform:rotate(-6deg);-o-transform:rotate(-6deg);-moz-transform:rotate(-6deg);transform:rotate(-6deg)}.da-gallery ul li:nth-child(even) a:before{-webkit-transform:rotate(-2deg);-o-transform:rotate(-2deg);-moz-transform:rotate(-2deg);transform:rotate(-2deg)}.da-gallery ul li:nth-child(3n) a:after{-webkit-transform:rotate(6deg);-o-transform:rotate(6deg);-moz-transform:rotate(6deg);transform:rotate(6deg)}.da-gallery ul li:nth-child(3n) a:before{-webkit-transform:rotate(3deg);-o-transform:rotate(3deg);-moz-transform:rotate(3deg);transform:rotate(3deg)}.da-gallery ul li:nth-child(5n) a:after{-webkit-transform:rotate(-8deg);-o-transform:rotate(-8deg);-moz-transform:rotate(-8deg);transform:rotate(-8deg)}.da-gallery ul li:nth-child(5n) a:before{-webkit-transform:rotate(-4deg);-o-transform:rotate(-4deg);-moz-transform:rotate(-4deg);transform:rotate(-4deg)}.da-gallery ul li:nth-child(8n) a:after{-webkit-transform:rotate(-8deg);-o-transform:rotate(-8deg);-moz-transform:rotate(-8deg);transform:rotate(-8deg)}.da-gallery ul li:nth-child(8n) a:before{-webkit-transform:rotate(-3deg);-o-transform:rotate(-3deg);-moz-transform:rotate(-3deg);transform:rotate(-3deg)}.da-gallery ul li span.da-gallery-hover ul li,.da-gallery ul li span.da-gallery-hover ul li a,.da-gallery ul li span.da-gallery-hover ul li a:after,.da-gallery ul li span.da-gallery-hover ul li a:before{width:auto;height:auto;border:0;margin:auto;-webkit-transform:none;-o-transform:none;-moz-transform:none;transform:none}.da-gallery ul li span.da-gallery-hover{display:block;z-index:20;position:relative;margin:0;padding:0;right:0;bottom:0}.da-gallery ul li span.da-gallery-hover ul{position:absolute;right:10px;bottom:11px}.da-gallery ul li span.da-gallery-hover ul li{padding:0;float:left;margin:0 1px;overflow:hidden}.da-gallery ul li span.da-gallery-hover ul li a{display:block;text-indent:-99999px;text-decoration:none;width:32px;height:32px;margin:0;padding:0;border:1px solid #060606;opacity:0;-webkit-border-radius:3px;-o-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.08);-o-box-shadow:inset 0 1px 0 rgba(255,255,255,0.08);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.08);box-shadow:inset 0 1px 0 rgba(255,255,255,0.08);-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out;background-color:#202020;background-repeat:no-repeat;background-position:center center}.da-gallery ul li span.da-gallery-hover ul li.da-gallery-update a{background-image:url("../images/icons/white/16/pencil.png");top:50px}.da-gallery ul li span.da-gallery-hover ul li.da-gallery-delete a{background-image:url("../images/icons/white/16/cross_small.png");top:150px}.da-gallery ul li:hover span.da-gallery-hover ul li a{top:0;opacity:1}.da-gallery ul li span.da-gallery-hover ul li a:hover{background-color:#101010}div.da-panel:after,div.da-panel .da-panel-content:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}div.da-panel,div.da-panel .da-panel-content{display:inline-block}* html div.da-panel,* html div.da-panel .da-panel-content{height:1%}div.da-panel,div.da-panel .da-panel-content{display:block}div.da-panel,div.da-panel-widget{margin-bottom:20px;position:relative;-webkit-border-radius:3px;-o-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-transition:all 100ms ease-out;-o-transition:all 100ms ease-out;-moz-transition:all 100ms ease-out;transition:all 100ms ease-out}div.da-panel .da-panel-header{display:block!important;position:relative;border:1px solid #bfbfbf;background-image:url("../images/panel-header.png");padding:0 16px;-webkit-border-radius:3px 3px 0 0;-o-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);-o-box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);box-shadow:inset 0 1px 0 rgba(255,255,255,0.5)}div.da-panel .da-panel-header .da-panel-title{font-size:14px;display:block;line-height:20px;padding:16px 0;margin-right:32px}div.da-panel .da-panel-header .da-panel-title img{max-height:16px;max-width:16px;display:inline;margin-right:4px}div.da-panel .da-panel-header .da-panel-toggler{background:url("../images/icons/black/16/bended_arrow_up.png") no-repeat center center;border-left:1px solid #c3c3c3;margin-top:-26px;height:16px;width:32px;padding:18px 0;padding-right:16px;display:block;position:absolute;right:0;top:50%;cursor:pointer}div.da-panel.collapsed .da-panel-header .da-panel-toggler{background-image:url("../images/icons/black/16/bended_arrow_down.png")}div.da-panel.collapsed>*{display:none}div.da-panel .da-panel-content,div.da-panel-widget{background-color:white;background-image:url("../images/panel-content.png");background-repeat:repeat-x;background-position:left top;border:1px solid #bfbfbf}div.da-panel .da-panel-content{border-top:0}div.da-panel.scrollable .da-panel-content .viewport{overflow:hidden;position:relative}div.da-panel.scrollable .da-panel-content .viewport .overview{position:absolute;left:0;top:0}div.da-panel.scrollable .da-panel-content .scrollbar{width:10px;height:16px;padding:3px 0;background-color:#fff;border-top:1px solid #d0d0d0}div.da-panel.scrollable .da-panel-content .scrollbar .track{height:16px;width:10px;background:url("../images/scrollbar-track-bg.png") repeat-x;position:relative}div.da-panel.scrollable .da-panel-content .scrollbar .thumb{height:13px;width:10px;margin-top:1px;border-top:1px solid #fff;cursor:pointer;overflow:hidden;top:0;position:absolute;background:#f0f0f0 url("../images/scrollbar-grip.png") center center no-repeat}div.da-panel.scrollable .da-panel-content .scrollbar .thumb .end{display:none}div.da-panel.scrollable .da-panel-content .disable{display:none}div.da-panel .da-panel-content.with-padding,div.da-panel-widget{padding:1em 1.2em}div.da-panel-toolbar ul:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}div.da-panel-toolbar ul{display:inline-block}* html div.da-panel-toolbar ul{height:1%}div.da-panel-toolbar ul{display:block}div.da-panel-toolbar ul,div.da-panel-toolbar ul li{margin:0;list-style:none}div.da-panel-toolbar ul,div.da-panel-toolbar ul.top{border:1px solid #bfbfbf;border-bottom-color:#cacaca;border-top:0;background:url("../images/toolbar-bg.png") repeat-x left bottom}div.da-panel-toolbar ul.bottom{border-bottom-color:#bfbfbf}div.da-panel-toolbar ul li{display:block;float:left;position:relative}div.da-panel-toolbar ul li a,div.da-panel-toolbar ul li span{display:block;padding:6px 10px;text-decoration:none;color:#444;border-right:1px solid #d6d6d6;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,1),inset 1px 0 0 rgba(255,255,255,1);-o-box-shadow:inset 0 1px 0 rgba(255,255,255,1),inset 1px 0 0 rgba(255,255,255,1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,1),inset 1px 0 0 rgba(255,255,255,1);box-shadow:inset 0 1px 0 rgba(255,255,255,1),inset 1px 0 0 rgba(255,255,255,1)}div.da-panel-toolbar>ul>li>a:active,div.da-panel-toolbar>ul>li>span:active{background-color:#f0f0f0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-o-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}div.da-panel-toolbar ul li a img,div.da-panel-toolbar ul li span img{max-height:16px;max-width:16px;margin-right:8px}div.da-panel-toolbar ul li ul{position:absolute;top:100%;left:-1px;margin:0;z-index:300;border:0;background:0;display:none}div.da-panel-toolbar.bottom ul li ul{bottom:100%;top:auto}div.da-panel-toolbar ul li ul li{display:block;float:none;min-width:120px;background:#fafafa;border:1px solid #cacaca;border-top-color:#fafafa;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.3);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.3);-o-box-shadow:inset 0 1px 0 rgba(255,255,255,0.3);box-shadow:inset 0 1px 0 rgba(255,255,255,0.3)}div.da-panel-toolbar ul li ul li a,div.da-panel-toolbar ul li ul li span{border-right:0;-webkit-box-shadow:none;-moz-box-shadow:none;-o-box-shadow:none;box-shadow:none}div.da-panel-toolbar ul li ul li:hover{background:#a9ca60;border:1px solid #779625!important}div.da-panel-toolbar ul li ul li:hover>a,div.da-panel-toolbar ul li ul li:hover>span{color:#fff}div.da-panel-toolbar ul li ul li:first-child{border-top:1px solid #cacaca}div.da-panel-toolbar ul li:hover>ul{display:block}div.da-panel-toolbar ul li ul li ul{left:100%;top:0;margin-top:-1px}div.da-panel-toolbar.bottom ul li ul li ul{bottom:0;top:auto;margin-top:0;margin-bottom:-1px}div.da-panel-toolbar.bottom ul li ul li:hover ul li:first-child{border-left:1px solid #cacaca}html .fc,.fc table{margin-bottom:0}.fc .fc-event-skin{background-color:#505050;border-color:#000}.fc .fc-button-content{background:transparent url("../images/button-gradient.png") repeat-x left bottom;-webkit-border-radius:2px;-o-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.6);-o-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.6)}.fc .fc-button-content:active{background-image:none;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);-o-box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 3px rgba(0,0,0,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.1)}.fc .fc-widget-header{background:url("../images/default-header.png") repeat-x left bottom;-moz-box-shadow:inset 1px 0 0 0 #f8f8f8;-webkit-box-shadow:inset 1px 0 0 0 #f8f8f8;-khtml-box-shadow:inset 1px 0 0 0 #f8f8f8;box-shadow:inset 1px 0 0 0 #f8f8f8}.da-panel-content .el-finder{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:0}div.pp_default .pp_social{display:none}.da-circular-stat-wrap,.da-circular-stat{margin:0;list-style:none}.da-circular-stat-wrap{text-align:center;margin-bottom:20px}.da-circular-stat{display:inline-block;width:135px;height:135px;position:relative;margin:0 5px 5px 5px;background:url("../images/circular-stat-back.png") no-repeat center center;background-size:100% 100%}.da-circular-stat .da-circular-front,.da-circular-stat .da-circular-progress{display:block;position:absolute;width:120px;height:120px;top:8px;left:8px;z-index:50}.da-circular-stat .da-circular-front{z-index:100;background:url("../images/circular-stat-front.png") no-repeat center center;background-size:100% 100%}.da-circular-stat .da-circular-front .da-circular-digit,.da-circular-stat .da-circular-front .da-circular-label{display:block;width:70px;height:30px;margin:auto;margin-top:30px;text-align:center}.da-circular-stat .da-circular-front .da-circular-digit{color:#181818;font-size:14px;color:#444;font-weight:bold}.da-circular-stat .da-circular-front .da-circular-digit span{font-size:18px}.da-circular-stat .da-circular-front .da-circular-label{margin-top:4px;color:#000;font-size:11px}ul.da-summary-stat,ul.da-summary-stat li{margin:0;list-style:none}ul.da-summary-stat li:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}ul.da-summary-stat li{display:block;clear:both}ul.da-summary-stat>li>a,ul.da-summary-stat>li>span{text-decoration:none;display:block;padding:8px;-webkit-border-radius:6px;-moz-border-radius:6px;-o-border-radius:6px;border-radius:6px;-webkit-transition:all 100ms ease-out;-o-transition:all 100ms ease-out;-moz-transition:all 100ms ease-out;transition:all 100ms ease-out}ul.da-summary-stat>li:hover>a,ul.da-summary-stat>li:hover>span{background-color:#f0f0f0}ul.da-summary-stat li .da-summary-icon{width:48px;height:48px;display:block;float:left;position:relative;text-align:center;background-color:#656565;-webkit-border-radius:6px;-moz-border-radius:6px;-o-border-radius:6px;border-radius:6px}ul.da-summary-stat li .da-summary-icon img{max-height:32px;max-width:32px;position:absolute;left:50%;top:50%;margin-left:-16px;margin-top:-16px}ul.da-summary-stat li .da-summary-text{margin-left:56px;display:block;color:#656565}ul.da-summary-stat li .da-summary-text span.value{color:#444;font-size:21px;font-weight:bold;display:block;display:inline-block}ul.da-summary-stat li .da-summary-text span.label{display:block}ul.da-summary-stat li .da-summary-text span.value.up{background:url("../images/up.png") no-repeat right center;padding-right:24px}ul.da-summary-stat li .da-summary-text span.value.down{background:url("../images/down.png") no-repeat right center;padding-right:24px}table.da-table{width:100%;margin:0;clear:both}table.da-table tr td,table.da-table tr th{vertical-align:middle}table.da-table thead tr{background:#eee url("../images/default-header.png") repeat-x left top}table.da-table tr th{padding:11px 20px;border-bottom:1px solid #cacaca;border-right:1px solid #cacaca;-moz-box-shadow:inset 1px 0 0 0 #f8f8f8;-webkit-box-shadow:inset 1px 0 0 0 #f8f8f8;-khtml-box-shadow:inset 1px 0 0 0 #f8f8f8;box-shadow:inset 1px 0 0 0 #f8f8f8}table.da-table thead tr{-moz-box-shadow:inset 0 1px 0 0 #fff;-webkit-box-shadow:inset 0 1px 0 0 #fff;-khtml-box-shadow:inset 0 1px 0 0 #fff;box-shadow:inset 0 1px 0 0 #fff}table.da-table tr th:last-child{border-right:0}table.da-table tr td{border-bottom:1px solid #dcdcdc;border-right:1px solid #e0e0e0;padding:7px 20px}table.da-table tr td:last-child{border-right:0}table.da-table tr:last-child td{border-bottom:0}table.da-table tr td.da-icon-column{text-align:center;width:80px}table.da-table tr td.da-icon-column img{margin:0 2px}table.da-table tr.odd{background-color:#f4f4f4}table.da-table tr.even{background-color:#fcfcfc}table.da-table.da-detail-view tbody th{width:120px;background:url("../images/default-header.png") repeat-x left bottom}table.da-table.da-detail-view tbody tr:last-child th{border-bottom:0}table.da-table.da-detail-view .null{color:#f2618c}.dataTables_wrapper{background-color:#fcfcfc}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{display:inline-block;padding:10px}.dataTables_wrapper .dataTables_paginate{padding:0}.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_paginate{float:right}.dataTables_wrapper .da-table{border-top:1px solid #cacaca}.dataTables_wrapper .da-table tr:last-child td{border-bottom:1px solid #cacaca}.dataTables_wrapper input[type="text"],.dataTables_wrapper select{background-color:#fcfcfc;border:1px solid #d1d1d1;padding:3px;outline:0;-webkit-border-radius:2px;-o-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:background 150ms linear;-moz-transition:background 150ms linear;-ms-transition:background 150ms linear;-o-transition:background 150ms linear;transition:background 150ms linear}.dataTables_wrapper input[type="text"]:focus,.dataTables_wrapper select:focus{border-color:#bbc1c9;background-color:#fff}.dataTables_wrapper .dataTables_paginate{padding:9px 8px}.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_previous,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_next,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_previous,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_next,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_button,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_active{display:inline-block;background-color:#f3f3f3;cursor:pointer;line-height:1;outline:0;border:1px solid #b1b1b1;text-align:center;margin-left:6px;background-repeat:no-repeat;background-position:center center;-webkit-border-radius:2px;-o-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);-o-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35);box-shadow:0 1px 1px rgba(0,0,0,0.15),inset 0 1px 1px rgba(255,255,255,0.35)}.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_previous,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_next,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_previous,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_next{width:20px;height:20px;text-indent:-9999px}.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_button,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_active{width:auto;padding:4px 7px;font-size:11px;line-height:1;color:#444}.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_button:hover,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_active:hover{text-decoration:none}.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_next,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_next{background-image:url("../images/dt-arrow-right.png")}.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_previous,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_previous{background-image:url("../images/dt-arrow-left.png")}.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_next,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_disabled_previous,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_button.paginate_button_disabled{background-color:#fefefe;border-color:#e0e0e0;cursor:default;color:#ccc;-webkit-box-shadow:inset 0 1px 1px rgba(255,255,255,0.5);-o-box-shadow:inset 0 1px 1px rgba(255,255,255,0.5);-moz-box-shadow:inset 0 1px 1px rgba(255,255,255,0.5);box-shadow:inset 0 1px 1px rgba(255,255,255,0.5)}.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_previous:active,.dataTables_wrapper .dataTables_paginate.paging_two_button .paginate_enabled_next:active,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_button:not(.paginate_button_disabled):active,.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_active:not(.paginate_button_disabled):active{background-color:#efefef;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-o-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_active:not(.paginate_button_disabled):active{background-color:#a9ca60}.dataTables_wrapper .dataTables_paginate.paging_full_numbers .paginate_active{background-color:#aed063;border-color:#a4b970;color:#374b0b}.dataTables_wrapper table thead th.sorting_asc,.dataTables_wrapper table thead th.sorting_desc,.dataTables_wrapper table thead th.sorting{background-position:right center;background-repeat:no-repeat;cursor:pointer;padding-right:19px}.dataTables_wrapper table thead th.sorting{background-image:url("../images/sort.png")}.dataTables_wrapper table thead th.sorting_asc{background-image:url("../images/sort_asc.png")}.dataTables_wrapper table thead th.sorting_desc{background-image:url("../images/sort_desc.png")}.dataTables_wrapper table tr.odd td.sorting_1{background-color:#f0f0f0}.dataTables_wrapper table tr.even td.sorting_1{background-color:#f8f8f8}pre,code{font-family:"Courier New",Courier,monospace}hr{border:0 #ccc solid;border-top-width:1px;clear:both;height:0}h1,h2,h3,h4,h5,h6{font-weight:normal}h1{font-size:21px}h2{font-size:19px}h3{font-size:17px}h4{font-size:16px}h5{font-size:15px}h6{font-size:14px}blockquote{border-left:4px solid #a6d037;font-size:14px;padding:10px;font-style:italic}ol{list-style:decimal}ul{list-style:disc}li{margin-left:30px}pre li,code li{margin-left:0}p,dl,hr,h1,h2,h3,h4,h5,h6,ol,ul,pre,table,address,fieldset,figure{margin-bottom:20px}.da-wizard-nav ul:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .da-wizard-nav ul{height:1%}.da-wizard-nav{display:block;clear:both;margin:0 16px;position:relative;padding:16px 0}.da-wizard-nav ul,.da-wizard-nav ul li{margin:0;list-style:none}.da-wizard-nav ul{position:relative}.da-wizard-nav ul li{display:block;z-index:25;position:relative;float:left;background:url("../images/wizard-nav.png") no-repeat right center;box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-webkit-box-sizing:border-box;-khtml-box-sizing:border-box}.da-wizard-nav ul li span,.da-wizard-nav ul li a{position:relative;text-align:center;display:block;width:32px;height:32px;line-height:32px;margin:4px 0;float:right;color:#545454;z-index:20;text-shadow:1px 1px 0 #fff;font-weight:bold;text-decoration:none}.da-wizard-nav ul li.active span,.da-wizard-nav ul li.active a{text-indent:-9999px;background:url("../images/icons/color/asterisk_orange.png") no-repeat center center}.da-wizard-nav ul li.done span,.da-wizard-nav ul li.done a{text-indent:-9999px;background:url("../images/icons/color/accept.png") no-repeat center center}.da-wizard-nav ul li span img,.da-wizard-nav ul li a img{max-width:16px;max-height:16px;margin:8px}.da-wizard-nav ul li.done span.da-wizard-label,.da-wizard-nav ul li.active span.da-wizard-label{color:#444}.da-wizard-nav ul li.active span.da-wizard-label{font-weight:bold}.da-wizard-nav ul li span.da-wizard-label{display:block;margin:0;line-height:16px;clear:both;width:auto;height:auto;color:#868686;font-weight:normal;margin-bottom:-16px;background:none!important;text-align:right;white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis}.da-wizard-nav .da-wizard-progress{position:absolute;left:0;top:0;bottom:0;margin:16px 0;z-index:15}.da-wizard-nav .da-wizard-progress,.da-wizard-nav.green .da-wizard-progress{background-color:#cef576}.da-wizard-nav.red .da-wizard-progress{background-color:#e67474}.da-wizard-nav.blue .da-wizard-progress{background-color:#85b8e6}.da-wizard-form fieldset{border-top:0;border-bottom:1px solid #d3d3d3}.da-wizard-form{margin-top:16px;border-top:1px solid #d3d3d3} \ No newline at end of file diff --git a/static/css/datatables.min.css b/static/css/datatables.min.css new file mode 100644 index 00000000..bc047bed --- /dev/null +++ b/static/css/datatables.min.css @@ -0,0 +1,14 @@ +/* + * This combined file was created by the DataTables downloader builder: + * https://datatables.net/download + * + * To rebuild or modify this file with the latest versions of the included + * software please visit: + * https://datatables.net/download/#dt/dt-1.10.18 + * + * Included libraries: + * DataTables 1.10.18 + */ +table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("/static/images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("/static/images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("/static/images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("/static/images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("/static/images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} + + diff --git a/static/css/fishBone.css b/static/css/fishBone.css new file mode 100644 index 00000000..0206dffc --- /dev/null +++ b/static/css/fishBone.css @@ -0,0 +1,157 @@ +.fishBone *{ + margin: 0; + padding: 0; + width: 100%; +} +.fishBone .ful{ + overflow: visible!important; +} +.fishBone .ful,.fli{ + list-style: none; + line-height: 2em +} +.fishBone{ +color: #767676; + width: 100%; + height: 100%; + white-space:nowrap; + position: relative; + font-size: 12px; +} +.fishBone .wrapper{ + padding: 0 10px; + margin: auto; + overflow: hidden; +} +.fishBone .wrapper .bd { + overflow: hidden; +} +.fishBone .item{ + position: relative; + width: 150px; + height: 350px; + display: inline-block; + margin-left:20px; +} +.fishBone .item .first{ + line-height:2em; +} +.fishBone .item .title{ + border-left:none; +} +.fishBone .item .title .title-left, +.fishBone .item .title .title-center, +.fishBone .item .title .title-right +{ + display:inline-block; + line-height: 2em; + font-size: 15px; + font-weight:bold; + font-family:'微软雅黑'; + color: white; +} +.fishBone .item .title .title-left{ + width:15px; + background: url("/static/images/title.png") no-repeat 0 0; +} +.fishBone .item .title .title-center{ + background: url("/static/images/title.png") repeat-x 0 -600px; +} +.fishBone .item .title .title-right{ + width:15px; + background: url("/static/images/title.png") no-repeat 0 -1200px; +} +.fishBone .item .title{ + display: block; + position: relative; + left: -33px; + background: url("/static/images/line-point.png") no-repeat 12px -212px; +} +.fishBone .item.top .title{ +} +.fishBone .item.bottom .title{ + bottom: 0; +} +.fishBone .item .content{ + padding-left: 13px; + position: absolute; +} +.fishBone .item.top .content{ + padding-top: 5px; + top: 10px; + padding-bottom: 13px; +} +.fishBone .item.bottom .content{ + bottom: 7px; + padding-top: 10px; + padding-bottom: 5px; +} +.fishBone .item.bottom{ +} + +.fishBone .item .content ul{} +.fishBone .item .content ul li{ + padding-left:13px; +} +.fishBone .item .content ul li.line-first{ + position: relative; + padding-left: 19px; + left: -4px; + border-left: 0!important; + background: url("/static/images/line-first.png") no-repeat 0px 0px; + font-size: 14px; + font-family:'微软雅黑'; +} +.fishBone .item .content ul li.line-last{ + border-left:none; +} +.fishBone .item.top .content ul li .name{} +.fishBone .item.top .content ul li .text{} +/**prev next*/ +.fishBone .prev { + position: absolute; + top: 154px; + left: 0; + display: inline-block; + width: 20px; + height: 40px; + background: url("/static/images/arrow.png") no-repeat -1px 13px; +} +.fishBone .prev:hover{ +cursor: pointer; +background-position-y: -28px; +} +.fishBone .next:hover{ +cursor: pointer; +background-position-y: -28px; +} +.fishBone .next { + position: absolute; + top: 154px; + right: 0; + display: inline-block; + width: 20px; + height: 40px; + background: url("/static/images/arrow.png") no-repeat -19px 13px; +} +/**line**/ +.fishBone .line { + position: absolute; + top: 175px; + height: 1px; + width: 100%; + border-bottom: 2px dashed #7E899D; + z-index: 1; +} +.fishBone .item .line-point { + position: absolute; + left: 4px; + bottom: -4px; + display: block; + height: 18px; + width: 18px; + background: url("/static/images/line-point.png") no-repeat 0px 0px; +} +.fishBone .item.bottom .line-point { + top: -6px +} \ No newline at end of file diff --git a/static/css/flatpickr.min.css b/static/css/flatpickr.min.css new file mode 100644 index 00000000..5bcd1ff8 --- /dev/null +++ b/static/css/flatpickr.min.css @@ -0,0 +1,13 @@ +.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px);}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.rightMost:after{left:auto;right:22px}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:28px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{text-decoration:none;cursor:pointer;position:absolute;top:0;line-height:16px;height:28px;padding:10px;z-index:3;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);}.flatpickr-months .flatpickr-prev-month.disabled,.flatpickr-months .flatpickr-next-month.disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/* + /*rtl:begin:ignore*/left:0;/* + /*rtl:end:ignore*/}/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,.flatpickr-months .flatpickr-next-month.flatpickr-next-month{/* + /*rtl:begin:ignore*/right:0;/* + /*rtl:end:ignore*/}/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month:hover,.flatpickr-months .flatpickr-next-month:hover{color:#959ea9;}.flatpickr-months .flatpickr-prev-month:hover svg,.flatpickr-months .flatpickr-next-month:hover svg{fill:#f64747}.flatpickr-months .flatpickr-prev-month svg,.flatpickr-months .flatpickr-next-month svg{width:14px;height:14px;}.flatpickr-months .flatpickr-prev-month svg path,.flatpickr-months .flatpickr-next-month svg path{-webkit-transition:fill .1s;transition:fill .1s;fill:inherit}.numInputWrapper{position:relative;height:auto;}.numInputWrapper input,.numInputWrapper span{display:inline-block}.numInputWrapper input{width:100%;}.numInputWrapper input::-ms-clear{display:none}.numInputWrapper span{position:absolute;right:0;width:14px;padding:0 4px 0 2px;height:50%;line-height:50%;opacity:0;cursor:pointer;border:1px solid rgba(57,57,57,0.15);-webkit-box-sizing:border-box;box-sizing:border-box;}.numInputWrapper span:hover{background:rgba(0,0,0,0.1)}.numInputWrapper span:active{background:rgba(0,0,0,0.2)}.numInputWrapper span:after{display:block;content:"";position:absolute}.numInputWrapper span.arrowUp{top:0;border-bottom:0;}.numInputWrapper span.arrowUp:after{border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:4px solid rgba(57,57,57,0.6);top:26%}.numInputWrapper span.arrowDown{top:50%;}.numInputWrapper span.arrowDown:after{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(57,57,57,0.6);top:40%}.numInputWrapper span svg{width:inherit;height:auto;}.numInputWrapper span svg path{fill:rgba(0,0,0,0.5)}.numInputWrapper:hover{background:rgba(0,0,0,0.05);}.numInputWrapper:hover span{opacity:1}.flatpickr-current-month{font-size:135%;line-height:inherit;font-weight:300;color:inherit;position:absolute;width:75%;left:12.5%;padding:6.16px 0 0 0;line-height:1;height:28px;display:inline-block;text-align:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}.flatpickr-current-month span.cur-month{font-family:inherit;font-weight:700;color:inherit;display:inline-block;margin-left:.5ch;padding:0;}.flatpickr-current-month span.cur-month:hover{background:rgba(0,0,0,0.05)}.flatpickr-current-month .numInputWrapper{width:6ch;width:7ch\0;display:inline-block;}.flatpickr-current-month .numInputWrapper span.arrowUp:after{border-bottom-color:rgba(0,0,0,0.9)}.flatpickr-current-month .numInputWrapper span.arrowDown:after{border-top-color:rgba(0,0,0,0.9)}.flatpickr-current-month input.cur-year{background:transparent;-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;cursor:text;padding:0 0 0 .5ch;margin:0;display:inline-block;font-size:inherit;font-family:inherit;font-weight:300;line-height:inherit;height:auto;border:0;border-radius:0;vertical-align:initial;}.flatpickr-current-month input.cur-year:focus{outline:0}.flatpickr-current-month input.cur-year[disabled],.flatpickr-current-month input.cur-year[disabled]:hover{font-size:100%;color:rgba(0,0,0,0.5);background:transparent;pointer-events:none}.flatpickr-weekdays{background:transparent;text-align:center;overflow:hidden;width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:28px;}.flatpickr-weekdays .flatpickr-weekdaycontainer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}span.flatpickr-weekday{cursor:default;font-size:90%;background:transparent;color:rgba(0,0,0,0.54);line-height:1;margin:0;text-align:center;display:block;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;font-weight:bolder}.dayContainer,.flatpickr-weeks{padding:1px 0 0 0}.flatpickr-days{position:relative;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;width:307.875px;}.flatpickr-days:focus{outline:0}.dayContainer{padding:0;outline:0;text-align:left;width:307.875px;min-width:307.875px;max-width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;display:-ms-flexbox;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-wrap:wrap;-ms-flex-pack:justify;-webkit-justify-content:space-around;justify-content:space-around;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1;}.dayContainer + .dayContainer{-webkit-box-shadow:-1px 0 0 #e6e6e6;box-shadow:-1px 0 0 #e6e6e6}.flatpickr-day{background:none;border:1px solid transparent;border-radius:150px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#393939;cursor:pointer;font-weight:400;width:14.2857143%;-webkit-flex-basis:14.2857143%;-ms-flex-preferred-size:14.2857143%;flex-basis:14.2857143%;max-width:39px;height:39px;line-height:39px;margin:0;display:inline-block;position:relative;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;}.flatpickr-day.inRange,.flatpickr-day.prevMonthDay.inRange,.flatpickr-day.nextMonthDay.inRange,.flatpickr-day.today.inRange,.flatpickr-day.prevMonthDay.today.inRange,.flatpickr-day.nextMonthDay.today.inRange,.flatpickr-day:hover,.flatpickr-day.prevMonthDay:hover,.flatpickr-day.nextMonthDay:hover,.flatpickr-day:focus,.flatpickr-day.prevMonthDay:focus,.flatpickr-day.nextMonthDay:focus{cursor:pointer;outline:0;background:#e6e6e6;border-color:#e6e6e6}.flatpickr-day.today{border-color:#959ea9;}.flatpickr-day.today:hover,.flatpickr-day.today:focus{border-color:#959ea9;background:#959ea9;color:#fff}.flatpickr-day.selected,.flatpickr-day.startRange,.flatpickr-day.endRange,.flatpickr-day.selected.inRange,.flatpickr-day.startRange.inRange,.flatpickr-day.endRange.inRange,.flatpickr-day.selected:focus,.flatpickr-day.startRange:focus,.flatpickr-day.endRange:focus,.flatpickr-day.selected:hover,.flatpickr-day.startRange:hover,.flatpickr-day.endRange:hover,.flatpickr-day.selected.prevMonthDay,.flatpickr-day.startRange.prevMonthDay,.flatpickr-day.endRange.prevMonthDay,.flatpickr-day.selected.nextMonthDay,.flatpickr-day.startRange.nextMonthDay,.flatpickr-day.endRange.nextMonthDay{background:#569ff7;-webkit-box-shadow:none;box-shadow:none;color:#fff;border-color:#569ff7}.flatpickr-day.selected.startRange,.flatpickr-day.startRange.startRange,.flatpickr-day.endRange.startRange{border-radius:50px 0 0 50px}.flatpickr-day.selected.endRange,.flatpickr-day.startRange.endRange,.flatpickr-day.endRange.endRange{border-radius:0 50px 50px 0}.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)){-webkit-box-shadow:-10px 0 0 #569ff7;box-shadow:-10px 0 0 #569ff7}.flatpickr-day.selected.startRange.endRange,.flatpickr-day.startRange.startRange.endRange,.flatpickr-day.endRange.startRange.endRange{border-radius:50px}.flatpickr-day.inRange{border-radius:0;-webkit-box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-day.disabled,.flatpickr-day.disabled:hover,.flatpickr-day.prevMonthDay,.flatpickr-day.nextMonthDay,.flatpickr-day.notAllowed,.flatpickr-day.notAllowed.prevMonthDay,.flatpickr-day.notAllowed.nextMonthDay{color:rgba(57,57,57,0.3);background:transparent;border-color:transparent;cursor:default}.flatpickr-day.disabled,.flatpickr-day.disabled:hover{cursor:not-allowed;color:rgba(57,57,57,0.1)}.flatpickr-day.week.selected{border-radius:0;-webkit-box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7;box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7}.flatpickr-day.hidden{visibility:hidden}.rangeMode .flatpickr-day{margin-top:1px}.flatpickr-weekwrapper{display:inline-block;float:left;}.flatpickr-weekwrapper .flatpickr-weeks{padding:0 12px;-webkit-box-shadow:1px 0 0 #e6e6e6;box-shadow:1px 0 0 #e6e6e6}.flatpickr-weekwrapper .flatpickr-weekday{float:none;width:100%;line-height:28px}.flatpickr-weekwrapper span.flatpickr-day,.flatpickr-weekwrapper span.flatpickr-day:hover{display:block;width:100%;max-width:none;color:rgba(57,57,57,0.3);background:transparent;cursor:default;border:none}.flatpickr-innerContainer{display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;}.flatpickr-rContainer{display:inline-block;padding:0;-webkit-box-sizing:border-box;box-sizing:border-box}.flatpickr-time{text-align:center;outline:0;display:block;height:0;line-height:40px;max-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-time:after{content:"";display:table;clear:both}.flatpickr-time .numInputWrapper{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;width:40%;height:40px;float:left;}.flatpickr-time .numInputWrapper span.arrowUp:after{border-bottom-color:#393939}.flatpickr-time .numInputWrapper span.arrowDown:after{border-top-color:#393939}.flatpickr-time.hasSeconds .numInputWrapper{width:26%}.flatpickr-time.time24hr .numInputWrapper{width:49%}.flatpickr-time input{background:transparent;-webkit-box-shadow:none;box-shadow:none;border:0;border-radius:0;text-align:center;margin:0;padding:0;height:inherit;line-height:inherit;color:#393939;font-size:14px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;}.flatpickr-time input.flatpickr-hour{font-weight:bold}.flatpickr-time input.flatpickr-minute,.flatpickr-time input.flatpickr-second{font-weight:400}.flatpickr-time input:focus{outline:0;border:0}.flatpickr-time .flatpickr-time-separator,.flatpickr-time .flatpickr-am-pm{height:inherit;display:inline-block;float:left;line-height:inherit;color:#393939;font-weight:bold;width:2%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.flatpickr-time .flatpickr-am-pm{outline:0;width:18%;cursor:pointer;text-align:center;font-weight:400}.flatpickr-time input:hover,.flatpickr-time .flatpickr-am-pm:hover,.flatpickr-time input:focus,.flatpickr-time .flatpickr-am-pm:focus{background:#f3f3f3}.flatpickr-input[readonly]{cursor:pointer}@-webkit-keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}} \ No newline at end of file diff --git a/static/css/font-awesome.min.css b/static/css/font-awesome.min.css new file mode 100644 index 00000000..19eb52b3 --- /dev/null +++ b/static/css/font-awesome.min.css @@ -0,0 +1,51 @@ +/*! + * Font Awesome 3.0.2 + * the iconic font designed for use with Twitter Bootstrap + * ------------------------------------------------------- + * The full suite of pictographic icons, examples, and documentation + * can be found at: http://fortawesome.github.com/Font-Awesome/ + * + * License + * ------------------------------------------------------- + * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL + * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - + * http://opensource.org/licenses/mit-license.html + * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ + * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: + * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" + + * Contact + * ------------------------------------------------------- + * Email: dave@davegandy.com + * Twitter: http://twitter.com/fortaweso_me + * Work: Lead Product Designer @ http://kyruus.com + */ + +@font-face{ + font-family:'FontAwesome'; + src:url('/static/font/fontawesome-webfont.eot?v=3.0.1'); + src:url('/static/font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), + url('/static/font/fontawesome-webfont.woff?v=3.0.1') format('woff'), + url('/static/font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); + font-weight:normal; + font-style:normal } + +[class^="icon-"],[class*=" icon-"] +{font-family:FontAwesome;font-weight:normal; + font-style:normal;text-decoration:inherit; + -webkit-font-smoothing:antialiased;display:inline; + width:auto;height:auto;line-height:normal; + vertical-align:baseline;background-image:none; + background-position:0 0;background-repeat:repeat;margin-top:0} +.icon-white,.nav-pills>.active>a>[class^="icon-"], +.nav-pills>.active>a>[class*=" icon-"], +.nav-list>.active>a>[class^="icon-"], +.nav-list>.active>a>[class*=" icon-"], +.navbar-inverse .nav>.active>a>[class^="icon-"], +.navbar-inverse .nav>.active>a>[class*=" icon-"], +.dropdown-menu>li>a:hover>[class^="icon-"], +.dropdown-menu>li>a:hover>[class*=" icon-"], +.dropdown-menu>.active>a>[class^="icon-"], +.dropdown-menu>.active>a>[class*=" icon-"], +.dropdown-submenu:hover>a>[class^="icon-"], +.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}a [class^="icon-"],a [class*=" icon-"]{display:inline-block}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}.btn [class^="icon-"],.nav [class^="icon-"],.btn [class*=" icon-"],.nav [class*=" icon-"]{display:inline}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}li [class^="icon-"],.nav li [class^="icon-"],li [class*=" icon-"],.nav li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li [class^="icon-"].icon-large,.nav li [class^="icon-"].icon-large,li [class*=" icon-"].icon-large,.nav li [class*=" icon-"].icon-large{width:1.5625em}ul.icons{list-style-type:none;text-indent:-0.75em}ul.icons li [class^="icon-"],ul.icons li [class*=" icon-"]{width:.75em}.icon-muted{color:#eee}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-moz-document url-prefix(){.icon-spin{height:.9em}.btn .icon-spin{height:auto}.icon-spin.icon-large{height:1.25em}.btn .icon-spin.icon-large{height:.75em}}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"} \ No newline at end of file diff --git a/static/css/fullscreen.min.css b/static/css/fullscreen.min.css new file mode 100644 index 00000000..0d97f10a --- /dev/null +++ b/static/css/fullscreen.min.css @@ -0,0 +1,2 @@ +.xterm.fullscreen{position:fixed;top:0;bottom:0;left:0;right:0;width:auto;height:auto;z-index:255} +/*# sourceMappingURL=fullscreen.min.css.map */ \ No newline at end of file diff --git a/static/css/material_blue.css b/static/css/material_blue.css new file mode 100644 index 00000000..b71f72cf --- /dev/null +++ b/static/css/material_blue.css @@ -0,0 +1,752 @@ +.flatpickr-calendar { + background: transparent; + opacity: 0; + display: none; + text-align: center; + visibility: hidden; + padding: 0; + -webkit-animation: none; + animation: none; + direction: ltr; + border: 0; + font-size: 14px; + line-height: 24px; + border-radius: 5px; + position: absolute; + width: 307.875px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + -ms-touch-action: manipulation; + touch-action: manipulation; + -webkit-box-shadow: 0 3px 13px rgba(0,0,0,0.08); + box-shadow: 0 3px 13px rgba(0,0,0,0.08); +} +.flatpickr-calendar.open, +.flatpickr-calendar.inline { + opacity: 1; + max-height: 640px; + visibility: visible; +} +.flatpickr-calendar.open { + display: inline-block; + z-index: 99999; +} +.flatpickr-calendar.animate.open { + -webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); + animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); +} +.flatpickr-calendar.inline { + display: block; + position: relative; + top: 2px; +} +.flatpickr-calendar.static { + position: absolute; + top: calc(100% + 2px); +} +.flatpickr-calendar.static.open { + z-index: 999; + display: block; +} +.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) { + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) { + -webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; + box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; +} +.flatpickr-calendar .hasWeeks .dayContainer, +.flatpickr-calendar .hasTime .dayContainer { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.flatpickr-calendar .hasWeeks .dayContainer { + border-left: 0; +} +.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time { + height: 40px; + border-top: 1px solid rgba(72,72,72,0.2); +} +.flatpickr-calendar.showTimeInput.hasTime .flatpickr-innerContainer { + border-bottom: 0; +} +.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time { + border: 1px solid rgba(72,72,72,0.2); +} +.flatpickr-calendar.noCalendar.hasTime .flatpickr-time { + height: auto; +} +.flatpickr-calendar:before, +.flatpickr-calendar:after { + position: absolute; + display: block; + pointer-events: none; + border: solid transparent; + content: ''; + height: 0; + width: 0; + left: 22px; +} +.flatpickr-calendar.rightMost:before, +.flatpickr-calendar.rightMost:after { + left: auto; + right: 22px; +} +.flatpickr-calendar:before { + border-width: 5px; + margin: 0 -5px; +} +.flatpickr-calendar:after { + border-width: 4px; + margin: 0 -4px; +} +.flatpickr-calendar.arrowTop:before, +.flatpickr-calendar.arrowTop:after { + bottom: 100%; +} +.flatpickr-calendar.arrowTop:before { + border-bottom-color: rgba(72,72,72,0.2); +} +.flatpickr-calendar.arrowTop:after { + border-bottom-color: #42a5f5; +} +.flatpickr-calendar.arrowBottom:before, +.flatpickr-calendar.arrowBottom:after { + top: 100%; +} +.flatpickr-calendar.arrowBottom:before { + border-top-color: rgba(72,72,72,0.2); +} +.flatpickr-calendar.arrowBottom:after { + border-top-color: #42a5f5; +} +.flatpickr-calendar:focus { + outline: 0; +} +.flatpickr-wrapper { + position: relative; + display: inline-block; +} +.flatpickr-months { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} +.flatpickr-months .flatpickr-month { + border-radius: 5px 5px 0 0; + background: #42a5f5; + color: #fff; + fill: #fff; + height: 28px; + line-height: 1; + text-align: center; + position: relative; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + overflow: hidden; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +.flatpickr-months .flatpickr-prev-month, +.flatpickr-months .flatpickr-next-month { + text-decoration: none; + cursor: pointer; + position: absolute; + top: 0px; + line-height: 16px; + height: 28px; + padding: 10px; + z-index: 3; + color: #fff; + fill: #fff; +} +.flatpickr-months .flatpickr-prev-month.disabled, +.flatpickr-months .flatpickr-next-month.disabled { + display: none; +} +.flatpickr-months .flatpickr-prev-month i, +.flatpickr-months .flatpickr-next-month i { + position: relative; +} +.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month, +.flatpickr-months .flatpickr-next-month.flatpickr-prev-month { +/* + /*rtl:begin:ignore*/ +/* + */ + left: 0; +/* + /*rtl:end:ignore*/ +/* + */ +} +/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month.flatpickr-next-month, +.flatpickr-months .flatpickr-next-month.flatpickr-next-month { +/* + /*rtl:begin:ignore*/ +/* + */ + right: 0; +/* + /*rtl:end:ignore*/ +/* + */ +} +/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month:hover, +.flatpickr-months .flatpickr-next-month:hover { + color: #bbb; +} +.flatpickr-months .flatpickr-prev-month:hover svg, +.flatpickr-months .flatpickr-next-month:hover svg { + fill: #f64747; +} +.flatpickr-months .flatpickr-prev-month svg, +.flatpickr-months .flatpickr-next-month svg { + width: 14px; + height: 14px; +} +.flatpickr-months .flatpickr-prev-month svg path, +.flatpickr-months .flatpickr-next-month svg path { + -webkit-transition: fill 0.1s; + transition: fill 0.1s; + fill: inherit; +} +.numInputWrapper { + position: relative; + height: auto; +} +.numInputWrapper input, +.numInputWrapper span { + display: inline-block; +} +.numInputWrapper input { + width: 100%; +} +.numInputWrapper input::-ms-clear { + display: none; +} +.numInputWrapper span { + position: absolute; + right: 0; + width: 14px; + padding: 0 4px 0 2px; + height: 50%; + line-height: 50%; + opacity: 0; + cursor: pointer; + border: 1px solid rgba(72,72,72,0.15); + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.numInputWrapper span:hover { + background: rgba(0,0,0,0.1); +} +.numInputWrapper span:active { + background: rgba(0,0,0,0.2); +} +.numInputWrapper span:after { + display: block; + content: ""; + position: absolute; +} +.numInputWrapper span.arrowUp { + top: 0; + border-bottom: 0; +} +.numInputWrapper span.arrowUp:after { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid rgba(72,72,72,0.6); + top: 26%; +} +.numInputWrapper span.arrowDown { + top: 50%; +} +.numInputWrapper span.arrowDown:after { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid rgba(72,72,72,0.6); + top: 40%; +} +.numInputWrapper span svg { + width: inherit; + height: auto; +} +.numInputWrapper span svg path { + fill: rgba(255,255,255,0.5); +} +.numInputWrapper:hover { + background: rgba(0,0,0,0.05); +} +.numInputWrapper:hover span { + opacity: 1; +} +.flatpickr-current-month { + font-size: 135%; + line-height: inherit; + font-weight: 300; + color: inherit; + position: absolute; + width: 75%; + left: 12.5%; + padding: 6.16px 0 0 0; + line-height: 1; + height: 28px; + display: inline-block; + text-align: center; + -webkit-transform: translate3d(0px, 0px, 0px); + transform: translate3d(0px, 0px, 0px); +} +.flatpickr-current-month span.cur-month { + font-family: inherit; + font-weight: 700; + color: inherit; + display: inline-block; + margin-left: 0.5ch; + padding: 0; +} +.flatpickr-current-month span.cur-month:hover { + background: rgba(0,0,0,0.05); +} +.flatpickr-current-month .numInputWrapper { + width: 6ch; + width: 7ch\0; + display: inline-block; +} +.flatpickr-current-month .numInputWrapper span.arrowUp:after { + border-bottom-color: #fff; +} +.flatpickr-current-month .numInputWrapper span.arrowDown:after { + border-top-color: #fff; +} +.flatpickr-current-month input.cur-year { + background: transparent; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: inherit; + cursor: text; + padding: 0 0 0 0.5ch; + margin: 0; + display: inline-block; + font-size: inherit; + font-family: inherit; + font-weight: 300; + line-height: inherit; + height: auto; + border: 0; + border-radius: 0; + vertical-align: initial; +} +.flatpickr-current-month input.cur-year:focus { + outline: 0; +} +.flatpickr-current-month input.cur-year[disabled], +.flatpickr-current-month input.cur-year[disabled]:hover { + font-size: 100%; + color: rgba(255,255,255,0.5); + background: transparent; + pointer-events: none; +} +.flatpickr-weekdays { + background: #42a5f5; + text-align: center; + overflow: hidden; + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + height: 28px; +} +.flatpickr-weekdays .flatpickr-weekdaycontainer { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +span.flatpickr-weekday { + cursor: default; + font-size: 90%; + background: #42a5f5; + color: rgba(0,0,0,0.54); + line-height: 1; + margin: 0; + text-align: center; + display: block; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + font-weight: bolder; +} +.dayContainer, +.flatpickr-weeks { + padding: 1px 0 0 0; +} +.flatpickr-days { + position: relative; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -webkit-align-items: flex-start; + -ms-flex-align: start; + align-items: flex-start; + width: 307.875px; + border-left: 1px solid rgba(72,72,72,0.2); + border-right: 1px solid rgba(72,72,72,0.2); +} +.flatpickr-days:focus { + outline: 0; +} +.dayContainer { + padding: 0; + outline: 0; + text-align: left; + width: 307.875px; + min-width: 307.875px; + max-width: 307.875px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: inline-block; + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-wrap: wrap; + -ms-flex-pack: justify; + -webkit-justify-content: space-around; + justify-content: space-around; + -webkit-transform: translate3d(0px, 0px, 0px); + transform: translate3d(0px, 0px, 0px); + opacity: 1; +} +.dayContainer + .dayContainer { + -webkit-box-shadow: -1px 0 0 rgba(72,72,72,0.2); + box-shadow: -1px 0 0 rgba(72,72,72,0.2); +} +.flatpickr-day { + background: none; + border: 1px solid transparent; + border-radius: 150px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #484848; + cursor: pointer; + font-weight: 400; + width: 14.2857143%; + -webkit-flex-basis: 14.2857143%; + -ms-flex-preferred-size: 14.2857143%; + flex-basis: 14.2857143%; + max-width: 39px; + height: 39px; + line-height: 39px; + margin: 0; + display: inline-block; + position: relative; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; +} +.flatpickr-day.inRange, +.flatpickr-day.prevMonthDay.inRange, +.flatpickr-day.nextMonthDay.inRange, +.flatpickr-day.today.inRange, +.flatpickr-day.prevMonthDay.today.inRange, +.flatpickr-day.nextMonthDay.today.inRange, +.flatpickr-day:hover, +.flatpickr-day.prevMonthDay:hover, +.flatpickr-day.nextMonthDay:hover, +.flatpickr-day:focus, +.flatpickr-day.prevMonthDay:focus, +.flatpickr-day.nextMonthDay:focus { + cursor: pointer; + outline: 0; + background: #e2e2e2; + border-color: #e2e2e2; +} +.flatpickr-day.today { + border-color: #bbb; +} +.flatpickr-day.today:hover, +.flatpickr-day.today:focus { + border-color: #bbb; + background: #bbb; + color: #fff; +} +.flatpickr-day.selected, +.flatpickr-day.startRange, +.flatpickr-day.endRange, +.flatpickr-day.selected.inRange, +.flatpickr-day.startRange.inRange, +.flatpickr-day.endRange.inRange, +.flatpickr-day.selected:focus, +.flatpickr-day.startRange:focus, +.flatpickr-day.endRange:focus, +.flatpickr-day.selected:hover, +.flatpickr-day.startRange:hover, +.flatpickr-day.endRange:hover, +.flatpickr-day.selected.prevMonthDay, +.flatpickr-day.startRange.prevMonthDay, +.flatpickr-day.endRange.prevMonthDay, +.flatpickr-day.selected.nextMonthDay, +.flatpickr-day.startRange.nextMonthDay, +.flatpickr-day.endRange.nextMonthDay { + background: #42a5f5; + -webkit-box-shadow: none; + box-shadow: none; + color: #fff; + border-color: #42a5f5; +} +.flatpickr-day.selected.startRange, +.flatpickr-day.startRange.startRange, +.flatpickr-day.endRange.startRange { + border-radius: 50px 0 0 50px; +} +.flatpickr-day.selected.endRange, +.flatpickr-day.startRange.endRange, +.flatpickr-day.endRange.endRange { + border-radius: 0 50px 50px 0; +} +.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), +.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)), +.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) { + -webkit-box-shadow: -10px 0 0 #42a5f5; + box-shadow: -10px 0 0 #42a5f5; +} +.flatpickr-day.selected.startRange.endRange, +.flatpickr-day.startRange.startRange.endRange, +.flatpickr-day.endRange.startRange.endRange { + border-radius: 50px; +} +.flatpickr-day.inRange { + border-radius: 0; + -webkit-box-shadow: -5px 0 0 #e2e2e2, 5px 0 0 #e2e2e2; + box-shadow: -5px 0 0 #e2e2e2, 5px 0 0 #e2e2e2; +} +.flatpickr-day.disabled, +.flatpickr-day.disabled:hover, +.flatpickr-day.prevMonthDay, +.flatpickr-day.nextMonthDay, +.flatpickr-day.notAllowed, +.flatpickr-day.notAllowed.prevMonthDay, +.flatpickr-day.notAllowed.nextMonthDay { + color: rgba(72,72,72,0.3); + background: transparent; + border-color: transparent; + cursor: default; +} +.flatpickr-day.disabled, +.flatpickr-day.disabled:hover { + cursor: not-allowed; + color: rgba(72,72,72,0.1); +} +.flatpickr-day.week.selected { + border-radius: 0; + -webkit-box-shadow: -5px 0 0 #42a5f5, 5px 0 0 #42a5f5; + box-shadow: -5px 0 0 #42a5f5, 5px 0 0 #42a5f5; +} +.flatpickr-day.hidden { + visibility: hidden; +} +.rangeMode .flatpickr-day { + margin-top: 1px; +} +.flatpickr-weekwrapper { + display: inline-block; + float: left; +} +.flatpickr-weekwrapper .flatpickr-weeks { + padding: 0 12px; + border-left: 1px solid rgba(72,72,72,0.2); +} +.flatpickr-weekwrapper .flatpickr-weekday { + float: none; + width: 100%; + line-height: 28px; +} +.flatpickr-weekwrapper span.flatpickr-day, +.flatpickr-weekwrapper span.flatpickr-day:hover { + display: block; + width: 100%; + max-width: none; + color: rgba(72,72,72,0.3); + background: transparent; + cursor: default; + border: none; +} +.flatpickr-innerContainer { + display: block; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; + background: #fff; + border-bottom: 1px solid rgba(72,72,72,0.2); +} +.flatpickr-rContainer { + display: inline-block; + padding: 0; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.flatpickr-time { + text-align: center; + outline: 0; + display: block; + height: 0; + line-height: 40px; + max-height: 40px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + background: #fff; + border-radius: 0 0 5px 5px; +} +.flatpickr-time:after { + content: ""; + display: table; + clear: both; +} +.flatpickr-time .numInputWrapper { + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: 40%; + height: 40px; + float: left; +} +.flatpickr-time .numInputWrapper span.arrowUp:after { + border-bottom-color: #484848; +} +.flatpickr-time .numInputWrapper span.arrowDown:after { + border-top-color: #484848; +} +.flatpickr-time.hasSeconds .numInputWrapper { + width: 26%; +} +.flatpickr-time.time24hr .numInputWrapper { + width: 49%; +} +.flatpickr-time input { + background: transparent; + -webkit-box-shadow: none; + box-shadow: none; + border: 0; + border-radius: 0; + text-align: center; + margin: 0; + padding: 0; + height: inherit; + line-height: inherit; + color: #484848; + font-size: 14px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.flatpickr-time input.flatpickr-hour { + font-weight: bold; +} +.flatpickr-time input.flatpickr-minute, +.flatpickr-time input.flatpickr-second { + font-weight: 400; +} +.flatpickr-time input:focus { + outline: 0; + border: 0; +} +.flatpickr-time .flatpickr-time-separator, +.flatpickr-time .flatpickr-am-pm { + height: inherit; + display: inline-block; + float: left; + line-height: inherit; + color: #484848; + font-weight: bold; + width: 2%; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-align-self: center; + -ms-flex-item-align: center; + align-self: center; +} +.flatpickr-time .flatpickr-am-pm { + outline: 0; + width: 18%; + cursor: pointer; + text-align: center; + font-weight: 400; +} +.flatpickr-time input:hover, +.flatpickr-time .flatpickr-am-pm:hover, +.flatpickr-time input:focus, +.flatpickr-time .flatpickr-am-pm:focus { + background: #efefef; +} +.flatpickr-input[readonly] { + cursor: pointer; +} +@-webkit-keyframes fpFadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +@keyframes fpFadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} diff --git a/static/css/menu.css b/static/css/menu.css new file mode 100644 index 00000000..4fb38eba --- /dev/null +++ b/static/css/menu.css @@ -0,0 +1,186 @@ +/*导航菜单*/ +#navMenu { + width:100%; + height: 25px; + line-height: 25px; + display:block; + overflow:hidden; + background-repeat:repeat-x; + background-position: left top; + background:#0480be; +} +#navMenu ul{ +width:95%; +margin-left:35px; +} + +#navMenu li { + width:auto; + text-align:center; + float: left; + line-height: 25px; + height: 25px; + background-repeat: no-repeat; + background-position: 0 center; + margin-left: -2px; +} + +#navMenu li a { + color:white; + padding-right: 1em; + padding-left: 1em; + margin-left: 2px; + display: block; + font-size: 13px; + font-weight: 300; +} +#navMenu li a:hover, #navMenu li.hover a { + background-repeat:repeat-x; + background-position: center top; + text-decoration: none; + color:bisque; +} +/*-------- 导航下拉菜单 --------------*/ +.dropMenu { + position:absolute; + top: 0; + z-index:100; + width:110px; + visibility: hidden; +/* filter: progid:DXImageTransform.Microsoft.Shadow(color=#000, direction=135, strength=4); +*/ margin-top: -1px; + margin-left:-3px; + margin-right:-3px; + border: 1px solid #4D5B66; + border-top: 1px solid #4D5B66; + padding-top:6px; + padding-bottom:6px; + filter: Alpha(Opacity=85); + font-size: 12px; + font-weight: 300; + background-color:#c4e3f3; +} + +.dropMenu li { + margin-top:2px; + margin-bottom:4px; +} +.dropMenu li a { + width: 100%; + display: block; + text-align:center; + padding: 4px 0 4px 0px; + color:#4f6b72; + border-bottom: 1px dashed #4D5B66; + background-color:#c4e3f3; +} +.dropMenu+li a{ +border-top:none; +} +* html .dropMenu a { + width: 100%; + color:#4f6b72; +} +.dropMenu a:hover { + text-decoration: underline; + background:#CCCCCC; + color:tomato; +} + +.hdo{ + width:100%; + height:auto; + display:block; + overflow:hidden; +} +/*侧边菜单*/ +.accordion { + width: 100%; + max-width: 250px; + margin: 10px auto 10px; + background:#0480be; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 3px 3px 3px; + -moz-box-shadow: 3px 3px 3px; + box-shadow: 3px 3px 3px; + } + +.accordion .link { + cursor: pointer; + display: block; + padding: 12px 12px 12px 30%; + color: white; + font-size: 13px; + font-weight: 300; + border-top: #CCC; + border-bottom: 1px solid #CCC; + position: relative; + -webkit-transition: all 0.4s ease; + -o-transition: all 0.4s ease; + transition: all 0.4s ease; +} + +.accordion li:last-child .link { + border-bottom: 1px solid #CCC; +} + +.accordion li i { + position: absolute; + top: 10px; + left: 12px; + font-size: 12px; + color: #0480be; + -webkit-transition: all 0.4s ease; + -o-transition: all 0.4s ease; + transition: all 0.4s ease; +} + +.accordion li i.fa-chevron-down { + right: 60px; + left: auto; + font-size: 12px; +} + +.accordion li.open .link { + color:bisque; +} + +.accordion li.open i { + color: bisque; +} +.accordion li.open i.fa-chevron-down { + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); +} + +/** + * Submenu + -----------------------------*/ + .submenu { + display: none; + background:#c4e3f3; + font-size: 13px; + } + .submenu li { + border-bottom: 1px solid #4f6b72; + } + + .submenu a { + display: block; + text-decoration: none; + color: #4f6b72; + padding: 8px; + padding-left: 25%; + -webkit-transition: all 0.25s ease; + -o-transition: all 0.25s ease; + transition: all 0.25s ease; + } + + .submenu a:hover { + background:#CCCCCC; + color:tomato; + } \ No newline at end of file diff --git a/static/css/mobile.css b/static/css/mobile.css new file mode 100644 index 00000000..25d913c5 --- /dev/null +++ b/static/css/mobile.css @@ -0,0 +1,169 @@ +html{ + +font-size:100px; + +} + +body{ + +background: rgb(239,239,239); + +} + +body,a,h3,span{ + +padding:0; + +margin:0; + +text-decoration: none; + +} + +.header{ + +background: #26A69A ; + +height: 0.44rem; + +font-size:0.14rem; + +color: white; + +padding-left:0.17rem; + +padding-right: 0.25rem; + +text-align: center; + +line-height: 0.44rem; + + +} + +.header>a{ + +color: white; + +float: left; + +} + + .header>.btn{ + + float: right; + + padding-top:0.1rem; + + } + +.btn>span{ + +border-radius: 100%; + +display: block; + +width: 0.04rem; + +height: 0.04rem; + +background: white; + +margin-top: 0.03rem; + +} + +.right{ + +font-size:0.12rem; + +float: right; + +margin-top: 0.12rem; + + + +} + +.right>a{ + +font-size: 0.12rem; + +} + +.right>.img,.right>.back{ + +display: inline-block; + +vertical-align: middle; + +} + +.address,.safe,.shezhi,.secrt,.view,.other{ + + font-size: 0.14rem; + + padding-left: 0.17rem; + + border-bottom: 1px solid #E0E0E0 ; + + height: 0.65rem; + + line-height: 0.65rem; + + padding-right: 0.12rem; + + background: white; + +} + +.safe{ + +margin-top:0.13rem; + +margin-bottom: 0.13rem; + +} + +.view{ + font-weight: bold; + font-size: 0.2rem; + margin-bottom: 0.13rem; + text-align: center; +} + +.content{ + float: left; + font-size: 0.14rem; + color: black; +} +.content-link{ + float: right; + font-weight: bold; + font-size: 0.2rem; + color: #0077b3; +} + +.footer{ + +margin-top: 0.2rem; + +height: 0.5rem; + +font-size: 0.16rem; + +border:1px solid red; + +border-radius: 0.05rem; + +width: 2.99rem; + +font-weight: bold; + +text-align: center; + +line-height: 0.5rem; + +margin-left: 0.35rem; + +} \ No newline at end of file diff --git a/static/css/mstyle.css b/static/css/mstyle.css new file mode 100644 index 00000000..2727f0a6 --- /dev/null +++ b/static/css/mstyle.css @@ -0,0 +1,160 @@ +.datalist{ + border:1px solid #0058a3; /* 表格边框 */ + font-family:Arial; + border-collapse:collapse; /* 边框重叠 */ + background-color:#c7e5ff; /* 表格背景色 */ + font-size:14px; +} +.datalist th{ + border:2px solid #0058a3; /* 行名称边框 */ + background-color:#4bacff; /* 行名称背景色 */ + color:#FFFFFF; /* 行名称颜色 */ + font-weight:bold; + padding-top:5px; padding-bottom:5px; + padding-left:5px; padding-right:5px; + text-align:center; +} +.datalist td.tdcenter{ + border:1px solid #0058a3; /* 单元格边框 */ + text-align:center; + padding-top:5px; padding-bottom:5px; + padding-left:5px; padding-right:5px; +} +.datalist td.tdleft{ + border:1px solid #0058a3; /* 单元格边框 */ + text-align:left; + padding-top:5px; padding-bottom:5px; + padding-left:5px; padding-right:5px; +} +.datalist tr.altrow{ + background-color:#eaf5ff; /* 隔行变色 */ +} +.ui-dialog{ + width: 750px;height: auto;display: none; + position: absolute;z-index: 9000; + top: 0px;left: 0px; + border: 1px solid #D5D5D5;background: #fff; +} + +.ui-dialog a{text-decoration: none;} + +.ui-dialog-title{ + height: 35px;line-height: 35px; padding:0px 20px;color: #535353;font-size: 14px; + border-bottom: 1px solid #efefef;background: #f5f5f5;font-weight: bold; + cursor: move; + user-select:none; +} + +.ui-dialog-content{ + padding: 15px 20px; +} +.ui-dialog-pt15{ + padding-top: 15px; +} +.ui-dialog-l40{ + height: 40px;line-height: 40px; + text-align: left; +} + +.ui-dialog-input{ + width: 100%;height:auto; + margin: 0px;padding:0px; + border: 1px solid #d5d5d5; + font-size: 12px; + outline: none; +} +.ui-dialog-submit{ + width: 100px;height: 35px;font-size: 14px;text-align: center;line-height: 35px;font-weight: bold; +} + +.ui-mask{ + width: 100%;height:15%;background: #000;margin-top: 6%; + position: absolute;top: 0px;height: 0px;z-index: 8000; + opacity:0.4; filter: Alpha(opacity=40); +} +.counter{ + text-align: center; + position: relative; +} +.counter .counter-content{ + width: 50px; + height: 50px; + border-radius: 50%; + background: #fff; + margin: 7px auto 7px; + z-index: 1; + position: relative; + transition: all 0.3s ease 0s; +} +.counter .counter-content:before{ + content: ""; + width: 136%; + height: 136%; + border-radius: 50%; + border: 15px solid peru; + border-bottom: 15px solid transparent; + position: absolute; + top: 60%; + left: 50%; + transform: translate(-50%,-50%); +} +.counter .counter-content:after{ + content: ""; + border-top: 16px solid peru; + border-left: 16px solid transparent; + border-right: 16px solid transparent; + position: absolute; + bottom: -14px; + left: 50%; + transform: translateX(-50%); +} +.counter .counter-value{ + font-size: 18px; + font-weight:bolder; + color: rgba(0,0,0,0.7); + line-height: 45px; +} +.counter .title{ + background: peru; + border-radius: 15px; + font-size: 13px; + color: #fff; + text-transform: capitalize; + margin: 15px; + line-height: 20px; +} +.counter.red .counter-content:before{ + border-color: #ef5f61; + border-bottom-color: transparent; +} +.counter.red .counter-content:after{ border-top-color: #ef5f61; } +.counter.red .counter-icon{ color: #ef5f61; } +.counter.red .title{ background: #ef5f61; } +.counter.blue .counter-content:before{ + border-color: #4d9fcf; + border-bottom-color: transparent; +} +.counter.blue .counter-content:after{ border-top-color: #4d9fcf; } +.counter.blue .counter-icon{ color: #4d9fcf; } +.counter.blue .title{ background: #4d9fcf; } +.counter.purple .counter-content:before{ + border-color: #a98ceb; + border-bottom-color: transparent; +} +.counter.purple .counter-content:after{ border-top-color: #a98ceb; } +.counter.purple .counter-icon{ color: #a98ceb; } +.counter.purple .title{ background: #a98ceb; } +.counter.tomato .counter-content:before{ + border-color: #4f6b72; + border-bottom-color: transparent; +} +.counter.tomato .counter-content:after{ border-top-color: #4f6b72; } +.counter.tomato .counter-icon{ color: #4f6b72; } +.counter.tomato .title{ background: #4f6b72; } +.counter.green .counter-content:before{ + border-color: darkgreen; + border-bottom-color: transparent; +} +.counter.green .counter-content:after{ border-top-color: darkgreen; } +.counter.green .counter-icon{ color: darkgreen; } +.counter.green .title{ background: darkgreen; } \ No newline at end of file diff --git a/static/css/spop.css b/static/css/spop.css new file mode 100644 index 00000000..71369440 --- /dev/null +++ b/static/css/spop.css @@ -0,0 +1,280 @@ +@charset "UTF-8"; +.spop-container { + z-index: 2000; + position: fixed; } + .spop-container, + .spop-container *, + .spop-container *:after, + .spop-container *:before { + box-sizing: border-box; } + +.spop--top-left { + top: 0; + left: 0; } + .spop--top-left .spop { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; } + +.spop--top-center { + top: 40%; + left: 50%; + -webkit-transform: translateX(-50%); + -ms-transform: translateX(-50%); + transform: translateX(-50%); } + .spop--top-center .spop { + -webkit-transform-origin: 50% 0; + -ms-transform-origin: 50% 0; + transform-origin: 50% 0; } + +.spop--top-right { + top: 0; + right: 0; } + .spop--top-right .spop { + -webkit-transform-origin: 100% 0; + -ms-transform-origin: 100% 0; + transform-origin: 100% 0; } + +.spop--center { + top: 40%; + left: 50%; + -webkit-transform: translate3d(-50%, -50%, 0); + transform: translate3d(-50%, -50%, 0); } + .spop--center .spop { + -webkit-transform-origin: 50% 0; + -ms-transform-origin: 50% 0; + transform-origin: 50% 0; } + +.spop--bottom-left { + bottom: 0; + left: 0; } + .spop--bottom-left .spop { + -webkit-transform-origin: 0 100%; + -ms-transform-origin: 0 100%; + transform-origin: 0 100%; } + +.spop--bottom-center { + bottom: 0; + left: 50%; + -webkit-transform: translateX(-50%); + -ms-transform: translateX(-50%); + transform: translateX(-50%); } + .spop--bottom-center .spop { + -webkit-transform-origin: 50% 100%; + -ms-transform-origin: 50% 100%; + transform-origin: 50% 100%; } + +.spop--bottom-right { + bottom: 0; + right: 0; } + .spop--bottom-right .spop { + -webkit-transform-origin: 100% 100%; + -ms-transform-origin: 100% 100%; + transform-origin: 100% 100%; } + +@media screen and (max-width: 50em) { + .spop--top-left, + .spop--top-center, + .spop--top-right, + .spop--bottom-left, + .spop--bottom-center, + .spop--bottom-right { + top: auto; + bottom: 0; + left: 0; + right: 0; + margin-left: 0; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); } + .spop--top-left .spop, + .spop--top-center .spop, + .spop--top-right .spop, + .spop--bottom-left .spop, + .spop--bottom-center .spop, + .spop--bottom-right .spop { + -webkit-transform-origin: 50% 100%; + -ms-transform-origin: 50% 100%; + transform-origin: 50% 100%; } + .spop { + border-bottom: 1px solid #000000; } } + +.spop { + position: relative; + min-height: 50px; + line-height: 1.25; + font-size: 13px; + -webkit-transform: translateZ(0); + transform: translateZ(0); } + @media screen and (min-width: 50em) { + .spop { + border-radius: 2px; + *display:inline; + *zoom:1; + margin: 0.7em; } } + +.spop--info, +.spop--error, +.spop--warning, +.spop--success { + color: #000000; + background-color:aliceblue; + border:solid lightgrey 1px;} + +@-webkit-keyframes spopIn { + 0% { + -webkit-transform: scale(0.2, 0.2); + transform: scale(0.2, 0.2); } + 95% { + -webkit-transform: scale(1.1, 1.1); + transform: scale(1.1, 1.1); } + 100% { + -webkit-transform: scale(1, 1); + transform: scale(1, 1); } } + +@keyframes spopIn { + 0% { + -webkit-transform: scale(0.2, 0.2); + transform: scale(0.2, 0.2); } + 95% { + -webkit-transform: scale(1.1, 1.1); + transform: scale(1.1, 1.1); } + 100% { + -webkit-transform: scale(1, 1); + transform: scale(1, 1); } } + +@-webkit-keyframes spopOut { + 0% { + opacity: 1; + -webkit-transform: scale(1, 1); + transform: scale(1, 1); } + 20% { + -webkit-transform: scale(1.1, 1.1); + transform: scale(1.1, 1.1); } + 100% { + opacity: 0; + -webkit-transform: scale(0, 0); + transform: scale(0, 0); } } + +@keyframes spopOut { + 0% { + opacity: 1; + -webkit-transform: scale(1, 1); + transform: scale(1, 1); } + 20% { + -webkit-transform: scale(1.1, 1.1); + transform: scale(1.1, 1.1); } + 100% { + opacity: 0; + -webkit-transform: scale(0, 0); + transform: scale(0, 0); } } + +.spop--out { + -webkit-animation: spopOut 0.4s ease-in-out; + animation: spopOut 0.4s ease-in-out; } + +.spop--in { + -webkit-animation: spopIn 0.4s ease-in-out; + animation: spopIn 0.4s ease-in-out; } + +.spop-body { + padding: 1.4em;} + .spop-body p { + margin: 0; } + .spop-body a { + color: #fff; + text-decoration: underline; } + .spop-body a:hover { + color: rgba(255, 255, 255, 0.8); + text-decoration: none; } + +.spop-title { + margin-top: 0; + margin-bottom: 0.25em; + color: #fff; } + +.spop-close { + position: absolute; + right: 0; + top: 0; + height: 32px; + width: 32px; + padding-top: 7px; + padding-right: 7px; + font-size: 14px; + font-weight: bold; + text-align: right; + line-height: 0.6; + color: black; + opacity: 0.5; } + .spop-close:hover { + opacity: 0.7; + cursor: pointer; } + +.spop-icon { + position: absolute; + top: 13px; + left: 16px; + width: 30px; + height: 30px; + border-radius: 50%; + -webkit-animation: spopIn 0.4s 0.4s ease-in-out; + animation: spopIn 0.4s 0.4s ease-in-out; } + .spop-icon:after, + .spop-icon:before { + content: ""; + position: absolute; + display: block; } + .spop-icon + .spop-body { + padding-left: 4.2em; } + +.spop-icon--error, +.spop-icon--info { + border: 2px solid #3a95ed; } + .spop-icon--error:before, + .spop-icon--info:before { + top: 5px; + left: 11px; + width: 4px; + height: 4px; + background-color: #3a95ed; } + .spop-icon--error:after, + .spop-icon--info:after { + top: 12px; + left: 11px; + width: 4px; + height: 9px; + background-color: #3a95ed; } + +.spop-icon--error { + border-color: #ff5656; } + .spop-icon--error:before { + top: 16px; + background-color: #ff5656; } + .spop-icon--error:after { + top: 5px; + background-color: #ff5656; } + +.spop-icon--success { + border: 2px solid #2ecc54; } + .spop-icon--success:before { + top: 7px; + left: 7px; + width: 13px; + height: 8px; + border-bottom: 3px solid #2ecc54; + border-left: 3px solid #2ecc54; + -webkit-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + transform: rotate(-45deg); } + +.spop-icon--warning { + border: 2px solid #fcd000; } + .spop-icon--warning:before { + top: 7px; + left: 7px; + width: 0; + height: 0; + border-style: solid; + border-color: transparent transparent #fcd000 transparent; + border-width: 0 6px 10px 6px; } diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 00000000..9321ad40 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,179 @@ +select { + width: auto; +} +html,body{ + height: 100%; + min-height: max-content; + background-image:url('/static/images/blueprint.png'); +} +.fl { + float:left; + width:9%; + margin-left:0.1%; +} +.Page { + text-align:center; + float:right; + width:90%; + box-sizing: border-box; + min-height:max-content; + padding-bottom: 10px; + margin-top: 0.6% +} +.header{ + height:auto; +} +footer{ + text-align:center; + font-size:7pt; + color:black; + height:max-content; + margin-top: 10px; + opacity: 0.6; + +} +.leftSide{ + width:auto; + height:auto; + text-align:center; +} +.font1{ + font-size:7pt; + color:red; +} +.font2{ + font-size:12pt; + color:orangered; +} +.font3{ + text-align:left; + font-size:10pt; + color:black; +} +.div{ + text-align:center; + color: black; + font-weight: bold; +} +.left1_div{ + width:99%; + float:left; + margin-top: 2%; +} +.left2_div{ + width:80%; + float:left; +} +.left_txt{ + margin-top: 4%; + test-align:left; + width: 50%; + margin-left: 25%; + border:1px solid #e5e5e5; +} +.center_div{ + test-align:center; + width: 50%; +} +.resource_pool{ + width:40% ; + float:left; + margin-top:1%; + margin-left:2%; + text-align: center; + border: 1px solid lightskyblue; +} +.business{ + margin-top:1%; + float:left; + width:94px; + height:94px; + line-height:94px; + border-radius:47px; + border:solid lightgrey 1px; + text-align: center; + margin-left: 1%; + background-color: #fcf8e3; +} +.assets{ + float:left;width: 10%; + margin-left: 6.5%; + height: 60px; + text-align: center; + line-height:60px; + font-size:14px; + margin-top: 1%; + padding: 8px 16px 8px 16px; +} +.index_zabbix{ + background-color:whitesmoke; + color:orangered; + float: right; + font-weight:normal; +} +.table_list{ + overflow: hidden; + text-overflow:ellipsis; + white-space: nowrap; + text-align: center; + vertical-align: middle; + max-width: 100px; +} +.progress{ + height: 3px; + background:skyblue; + border-radius: 0; + box-shadow: none; + margin-bottom: 30px; + overflow: visible; +} +.progress .progress-bar{ + position: relative; + -webkit-animation: animate-positive 3s; + animation: animate-positive 3s; +} +.progress .progress-bar:after{ + content: ""; + display: inline-block; + width: 20px; + background:skyblue; + position: absolute; + top: -5px; + bottom: -8px; + right: 0px; + z-index: 1; + transform: rotate(0deg); +} +.progress .progress-value{ + display: block; + font-size: 14px; + color: tomato; + position: absolute; + top: -30px; + right: -25px; +} +@-webkit-keyframes animate-positive{ + 0%{ width: 0; } +} +@keyframes animate-positive { + 0%{ width: 0; } +} +.style_border{border:1px solid gainsboro;border-radius:4px} +.style_hr{height:2px; border-top:1px solid lightskyblue;width: 99%;clear:both;} +.visualwrapper{width:100%;min-height:100%;margin:0 auto;padding:2rem 8rem;background-color:#fff} +.tooltip-example-right,.tooltip-example-left{position:absolute} +.tooltip-example-right{top:0;right:0} +.tooltip-example-left{top:50%;left:0} +@font-face{font-family:Source Sans Pro;src:local("Source Sans Pro"),url("/static/font/SourceSansPro-Regular.ttf") format("truetype");font-weight:normal;font-style:normal} +@font-face{font-family:Source Sans Pro;src:local("Source Sans Pro"),url("/static/font/SourceSansPro-Semibold.ttf") format("truetype");font-weight:bold;font-style:normal} +@font-face{font-family:Source Sans Pro;src:local("Source Sans Pro"),url("/static/font/SourceSansPro-SemiboldItalic.ttf") format("truetype");font-weight:bold;font-style:italic} +@font-face{font-family:Source Sans Pro;src:local("Source Sans Pro"),url("/static/font/SourceSansPro-Italic.ttf") format("truetype");font-weight:normal;font-style:italic} +.no-display{display:none} +.tooltip-container{transform:translateZ(0);position:absolute;max-width:200px;padding:8px 10px 10px;font-size:12px;background-color:#7cb342;color:snow;border-radius:4px;opacity:1} +.tooltip-container[class*=" tooltip-"]{-webkit-animation:tooltip-anim 0.8s;-moz-animation:tooltip-anim 0.8s;-o-animation:tooltip-anim 0.8s;animation:tooltip-anim 0.8s} +.tooltip-container::after{position:absolute;display:block;content:""} +.tooltip-container.tooltip-left::after{right:-8px;top:50%;transform:translateY(-50%);border-style:solid;border-width:6px 0 6px 8px;border-color:transparent transparent transparent #7cb342} +.tooltip-container.tooltip-right::after{left:-8px;top:50%;transform:translateY(-50%);border-style:solid;border-width:6px 8px 6px 0;border-color:transparent #7cb342 transparent transparent} +.tooltip-container.tooltip-center::after{top:-8px;left:50%;transform:translateX(-50%);border-style:solid;border-width:0 6px 8px 6px;border-color:transparent transparent #7cb342 transparent}[data-tooltip]{cursor:pointer;color:#7cb342;display:inline-block} +@keyframes tooltip-anim{0%{opacity:0;transform:matrix(0.5, 0, 0, 0.8, 0, 0)}20%{transform:matrix(1.1, 0, 0, 1.1, 0, 0)}40%{opacity:1}70%{transform:matrix(1, 0, 0, 1, 0, 0)}100%{transform:matrix(1, 0, 0, 1, 0, 0)}} +@-webkit-keyframes tooltip-anim{0%{opacity:0;transform:matrix(0.5, 0, 0, 0.8, 0, 0)}20%{transform:matrix(1.1, 0, 0, 1.1, 0, 0)}40%{opacity:1}70%{transform:matrix(1, 0, 0, 1, 0, 0)}100%{transform:matrix(1, 0, 0, 1, 0, 0)}} diff --git a/static/css/table_style.css b/static/css/table_style.css new file mode 100644 index 00000000..2a145c9b --- /dev/null +++ b/static/css/table_style.css @@ -0,0 +1,74 @@ +.datalist{ + border:1px solid #0058a3; /* 表格边框 */ + font-family:Arial; + border-collapse:collapse; /* 边框重叠 */ + background-color:#c7e5ff; /* 表格背景色 */ + font-size:14px; +} +.datalist th{ + border:2px solid #0058a3; /* 行名称边框 */ + background-color:#4bacff; /* 行名称背景色 */ + color:#FFFFFF; /* 行名称颜色 */ + font-weight:bold; + padding-top:5px; padding-bottom:5px; + padding-left:5px; padding-right:5px; + text-align:center; +} +.datalist td.tdcenter{ + border:1px solid #0058a3; /* 单元格边框 */ + text-align:center; + padding-top:5px; padding-bottom:5px; + padding-left:5px; padding-right:5px; +} +.datalist td.tdleft{ + border:1px solid #0058a3; /* 单元格边框 */ + text-align:left; + padding-top:5px; padding-bottom:5px; + padding-left:5px; padding-right:5px; +} +.datalist tr.altrow{ + background-color:#eaf5ff; /* 隔行变色 */ +} +.ui-dialog{ + width: 750px;height: auto;display: none; + position: absolute;z-index: 9000; + top: 0px;left: 0px; + border: 1px solid #D5D5D5;background: #fff; +} + +.ui-dialog a{text-decoration: none;} + +.ui-dialog-title{ + height: 35px;line-height: 35px; padding:0px 20px;color: #535353;font-size: 14px; + border-bottom: 1px solid #efefef;background: #f5f5f5;font-weight: bold; + cursor: move; + user-select:none; +} + +.ui-dialog-content{ + padding: 15px 20px; +} +.ui-dialog-pt15{ + padding-top: 15px; +} +.ui-dialog-l40{ + height: 40px;line-height: 40px; + text-align: left; +} + +.ui-dialog-input{ + width: 100%;height:auto; + margin: 0px;padding:0px; + border: 1px solid #d5d5d5; + font-size: 12px; + outline: none; +} +.ui-dialog-submit{ + width: 100px;height: 35px;font-size: 14px;text-align: center;line-height: 35px;font-weight: bold; +} + +.ui-mask{ + width: 100%;height:15%;background: #000;margin-top: 6%; + position: absolute;top: 0px;height: 0px;z-index: 8000; + opacity:0.4; filter: Alpha(opacity=40); +} \ No newline at end of file diff --git a/static/css/xterm.min.css b/static/css/xterm.min.css new file mode 100644 index 00000000..326de01e --- /dev/null +++ b/static/css/xterm.min.css @@ -0,0 +1,2 @@ +.xterm{font-family:courier-new,courier,monospace;font-feature-settings:"liga" 0;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:10}.xterm .xterm-helper-textarea{position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-10;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm{cursor:text}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:100;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden} +/*# sourceMappingURL=xterm.min.css.map */ \ No newline at end of file diff --git a/static/font/SourceSansPro-Italic.ttf b/static/font/SourceSansPro-Italic.ttf new file mode 100644 index 00000000..e5a1a86e Binary files /dev/null and b/static/font/SourceSansPro-Italic.ttf differ diff --git a/static/font/SourceSansPro-Regular.ttf b/static/font/SourceSansPro-Regular.ttf new file mode 100644 index 00000000..91e9ea57 Binary files /dev/null and b/static/font/SourceSansPro-Regular.ttf differ diff --git a/static/font/SourceSansPro-Semibold.ttf b/static/font/SourceSansPro-Semibold.ttf new file mode 100644 index 00000000..336956fa Binary files /dev/null and b/static/font/SourceSansPro-Semibold.ttf differ diff --git a/static/font/SourceSansPro-SemiboldItalic.ttf b/static/font/SourceSansPro-SemiboldItalic.ttf new file mode 100644 index 00000000..2c5ad300 Binary files /dev/null and b/static/font/SourceSansPro-SemiboldItalic.ttf differ diff --git a/static/font/fa-brands-400.eot b/static/font/fa-brands-400.eot new file mode 100644 index 00000000..f7accfa1 Binary files /dev/null and b/static/font/fa-brands-400.eot differ diff --git a/static/font/fa-brands-400.svg b/static/font/fa-brands-400.svg new file mode 100644 index 00000000..c94b526c --- /dev/null +++ b/static/font/fa-brands-400.svg @@ -0,0 +1,1148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/font/fa-brands-400.ttf b/static/font/fa-brands-400.ttf new file mode 100644 index 00000000..2ffa92d4 Binary files /dev/null and b/static/font/fa-brands-400.ttf differ diff --git a/static/font/fa-brands-400.woff b/static/font/fa-brands-400.woff new file mode 100644 index 00000000..34110b2e Binary files /dev/null and b/static/font/fa-brands-400.woff differ diff --git a/static/font/fa-brands-400.woff2 b/static/font/fa-brands-400.woff2 new file mode 100644 index 00000000..540dffd2 Binary files /dev/null and b/static/font/fa-brands-400.woff2 differ diff --git a/static/font/fa-regular-400.eot b/static/font/fa-regular-400.eot new file mode 100644 index 00000000..c6b1218f Binary files /dev/null and b/static/font/fa-regular-400.eot differ diff --git a/static/font/fa-regular-400.svg b/static/font/fa-regular-400.svg new file mode 100644 index 00000000..43fa6b7f --- /dev/null +++ b/static/font/fa-regular-400.svg @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/font/fa-regular-400.ttf b/static/font/fa-regular-400.ttf new file mode 100644 index 00000000..43406e85 Binary files /dev/null and b/static/font/fa-regular-400.ttf differ diff --git a/static/font/fa-regular-400.woff b/static/font/fa-regular-400.woff new file mode 100644 index 00000000..d49d4643 Binary files /dev/null and b/static/font/fa-regular-400.woff differ diff --git a/static/font/fa-regular-400.woff2 b/static/font/fa-regular-400.woff2 new file mode 100644 index 00000000..ecd00ed7 Binary files /dev/null and b/static/font/fa-regular-400.woff2 differ diff --git a/static/font/fa-solid-900.eot b/static/font/fa-solid-900.eot new file mode 100644 index 00000000..3968757e Binary files /dev/null and b/static/font/fa-solid-900.eot differ diff --git a/static/font/fa-solid-900.svg b/static/font/fa-solid-900.svg new file mode 100644 index 00000000..10cd5b09 --- /dev/null +++ b/static/font/fa-solid-900.svg @@ -0,0 +1,2312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/font/fa-solid-900.ttf b/static/font/fa-solid-900.ttf new file mode 100644 index 00000000..a1da1bbb Binary files /dev/null and b/static/font/fa-solid-900.ttf differ diff --git a/static/font/fa-solid-900.woff b/static/font/fa-solid-900.woff new file mode 100644 index 00000000..41cbd7d3 Binary files /dev/null and b/static/font/fa-solid-900.woff differ diff --git a/static/font/fa-solid-900.woff2 b/static/font/fa-solid-900.woff2 new file mode 100644 index 00000000..b307c261 Binary files /dev/null and b/static/font/fa-solid-900.woff2 differ diff --git a/static/images/01.jpg b/static/images/01.jpg new file mode 100644 index 00000000..50a528df Binary files /dev/null and b/static/images/01.jpg differ diff --git a/static/images/02.jpg b/static/images/02.jpg new file mode 100644 index 00000000..fc16ef1e Binary files /dev/null and b/static/images/02.jpg differ diff --git a/static/images/03.jpg b/static/images/03.jpg new file mode 100644 index 00000000..0db285d8 Binary files /dev/null and b/static/images/03.jpg differ diff --git a/static/images/Sorting icons.psd b/static/images/Sorting icons.psd new file mode 100644 index 00000000..53b2e068 Binary files /dev/null and b/static/images/Sorting icons.psd differ diff --git a/static/images/arrow.png b/static/images/arrow.png new file mode 100644 index 00000000..c52324b2 Binary files /dev/null and b/static/images/arrow.png differ diff --git a/static/images/favicon.png b/static/images/favicon.png new file mode 100644 index 00000000..0b281be9 Binary files /dev/null and b/static/images/favicon.png differ diff --git a/static/images/line-first.png b/static/images/line-first.png new file mode 100644 index 00000000..319ccea5 Binary files /dev/null and b/static/images/line-first.png differ diff --git a/static/images/line-point.png b/static/images/line-point.png new file mode 100644 index 00000000..9b577bad Binary files /dev/null and b/static/images/line-point.png differ diff --git a/static/images/sort_asc.png b/static/images/sort_asc.png new file mode 100644 index 00000000..e1ba61a8 Binary files /dev/null and b/static/images/sort_asc.png differ diff --git a/static/images/sort_asc_disabled.png b/static/images/sort_asc_disabled.png new file mode 100644 index 00000000..fb11dfe2 Binary files /dev/null and b/static/images/sort_asc_disabled.png differ diff --git a/static/images/sort_both.png b/static/images/sort_both.png new file mode 100644 index 00000000..af5bc7c5 Binary files /dev/null and b/static/images/sort_both.png differ diff --git a/static/images/sort_desc.png b/static/images/sort_desc.png new file mode 100644 index 00000000..0e156deb Binary files /dev/null and b/static/images/sort_desc.png differ diff --git a/static/images/sort_desc_disabled.png b/static/images/sort_desc_disabled.png new file mode 100644 index 00000000..c9fdd8a1 Binary files /dev/null and b/static/images/sort_desc_disabled.png differ diff --git a/static/images/title.png b/static/images/title.png new file mode 100644 index 00000000..a68a4a46 Binary files /dev/null and b/static/images/title.png differ diff --git a/static/js/Modal.js b/static/js/Modal.js new file mode 100644 index 00000000..f514cafa --- /dev/null +++ b/static/js/Modal.js @@ -0,0 +1,57 @@ +// 获取元素对象 +function g(id){ + return document.getElementById(id); +} +// 自动居中元素(el = Element) +function autoCenter( el ){ + var bodyW = document.documentElement.clientWidth; + var bodyH = document.documentElement.clientHeight; + var elW = el.offsetWidth; + var elH = el.offsetHeight; + el.style.left = (bodyW-elW)/2 + 'px'; + el.style.top = (bodyH-elH)/2 + 'px'; +} +// 自动扩展元素到全部显示区域 +function fillToBody( el ){ + el.style.width = document.documentElement.clientWidth +'px'; + el.style.height = document.documentElement.clientHeight + 'px'; +} +function Trim(str) { + return str.replace(/(^\s*)|(\s*$)/g, ""); + } +function showDialog(){ + g('dialogMove').style.display = 'block'; + g('mask').style.display = 'block'; + autoCenter( g('dialogMove') ); + fillToBody( g('mask') ); + var comment = $('#comment_show').text(); + $('#comment_update').val(Trim(comment)); +} +// 关闭对话框 +function hideDialog(){ + g('dialogMove').style.display = 'none'; + g('mask').style.display = 'none'; + $('#dialogDrag').text('修改备注'); + $('#dialogDrag').css('color',''); +} +function comment_modify() { + if ($('#comment_update').val()){ + var comment = $('#comment_update').val()} + else{ + var comment = ' '; + }; + var hostname = $('#hostname').text(); + var data = {'comment':comment,'hostname':hostname}; + $.ajax({ + type: "POST", + url: "/assets_info/update", + contentType: "application/json; charset=utf-8", + data: JSON.stringify(data), + dataType: "json", + success: function (){ + $('#comment_show').html(comment+'  '); + hideDialog()}, + error:function () {$('#dialogDrag').text('修改备注失败');$('#dialogDrag').css('color','red')} + }); + +}; \ No newline at end of file diff --git a/static/js/autocomplete.js b/static/js/autocomplete.js new file mode 100644 index 00000000..b97d742a --- /dev/null +++ b/static/js/autocomplete.js @@ -0,0 +1,271 @@ +(function($) { + $.input_host = function(target, data, itemSelectFunction) { + var defaulOption = { + suggestMaxHeight: '200px',//弹出框最大高度 + itemColor : '#000000',//默认字体颜色 + itemBackgroundColor:'white',//默认背景颜色 + itemOverColor : 'blue',//选中字体颜色 + itemOverBackgroundColor : 'white',//选中背景颜色 + itemPadding : 3 ,//item间距 + fontSize : 14 ,//默认字体大小 + alwaysShowALL : true //点击input是否展示所有可选项 + }; + var maxFontNumber = 0;//最大字数 + var currentItem; + var suggestContainerId = target + "-suggest"; + var suggestContainerWidth = $('#' + target).innerWidth(); + var suggestContainerLeft = $('#' + target).offset().left; + var suggestContainerTop = $('#' + target).offset().top + $('#' + target).outerHeight(); + + var showClickTextFunction = function() { + $('#' + target).val(this.innerText); + currentItem = null; + $('#' + suggestContainerId).hide(); + } + var suggestContainer; + if ($('#' + suggestContainerId)[0]) { + suggestContainer = $('#' + suggestContainerId); + suggestContainer.empty(); + } else { + suggestContainer = $('
'); //创建一个子
+ } + + suggestContainer.attr('id', suggestContainerId); + suggestContainer.attr('tabindex', '0'); + suggestContainer.hide(); + var _initItems = function(items) { + suggestContainer.empty(); + for (var i = 0; i < items.length; i++) { + if(items[i].text.length > maxFontNumber){ + maxFontNumber = items[i].text.length; + } + var suggestItem = $('
'); //创建一个子
+ suggestItem.attr('id', items[i].id); + suggestItem.append(items[i].text); + suggestItem.css({ + 'padding':defaulOption.itemPadding + 'px', + 'white-space':'nowrap', + 'cursor': 'pointer', + 'background-color': defaulOption.itemBackgroundColor, + 'color': defaulOption.itemColor + }); + suggestItem.bind("mouseover", + function() { + $(this).css({ + 'background-color': defaulOption.itemOverBackgroundColor, + 'color': defaulOption.itemOverColor + }); + currentItem = $(this); + }); + suggestItem.bind("mouseout", + function() { + $(this).css({ + 'background-color': defaulOption.itemBackgroundColor, + 'color': defaulOption.itemColor + }); + currentItem = null; + }); + suggestItem.bind("click", showClickTextFunction); + suggestItem.bind("click", itemSelectFunction); + suggestItem.appendTo(suggestContainer); + suggestContainer.appendTo(document.body); + } + } + + var inputChange = function() { + var inputValue = $('#' + target).val(); + inputValue = inputValue.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + var matcher = new RegExp(inputValue, "i"); + return $.grep(data, + function(value) { + return matcher.test(value.text); + }); + } + + $('#' + target).bind("keyup", + function() { + _initItems(inputChange()); + }); + $('#' + target).bind("blur", + function() { + if(!$('#' + suggestContainerId).is(":focus")){ + $('#' + suggestContainerId).hide(); + if (currentItem) { + currentItem.trigger("click"); + } + currentItem = null; + return; + } + }); + + $('#' + target).bind("click", + function() { + if (defaulOption.alwaysShowALL) { + _initItems(data); + } else { + _initItems(inputChange()); + } + $('#' + suggestContainerId).removeAttr("style"); + var containerWidth = Math.max(suggestContainerWidth); + $('#' + suggestContainerId).css({ + 'border': '1px solid #ccc', + 'max-height': '200px', + 'top': suggestContainerTop, + 'left': suggestContainerLeft, + 'width': containerWidth, + 'position': 'absolute', + 'font-size': defaulOption.fontSize+'px', + 'font-family':'Arial', + 'z-index': 99999, + 'background-color': '#FFFFFF', + 'overflow-y': 'auto', + 'overflow-x': 'hidden', + 'white-space':'nowrap' + + }); + $('#' + suggestContainerId).show(); + }); + _initItems(data); + + $('#' + suggestContainerId).bind("blur", + function() { + $('#' + suggestContainerId).hide(); + }); + + } + + $.input_uri = function(target, data, itemSelectFunction) { + var defaulOption = { + suggestMaxHeight: '200px',//弹出框最大高度 + itemColor : '#000000',//默认字体颜色 + itemBackgroundColor:'white',//默认背景颜色 + itemOverColor : 'blue',//选中字体颜色 + itemOverBackgroundColor : 'white',//选中背景颜色 + itemPadding : 3 ,//item间距 + fontSize : 14 ,//默认字体大小 + alwaysShowALL : true //点击input是否展示所有可选项 + }; + var maxFontNumber = 0;//最大字数 + var currentItem; + var suggestContainerId = target + "-suggest"; + var suggestContainerWidth = $('#' + target).innerWidth(); + var suggestContainerLeft = $('#' + target).offset().left; + var suggestContainerTop = $('#' + target).offset().top + $('#' + target).outerHeight(); + + var showClickTextFunction = function() { + $('#' + target).val(this.innerText); + currentItem = null; + $('#' + suggestContainerId).hide(); + set_cookies(target); + } + var suggestContainer; + if ($('#' + suggestContainerId)[0]) { + suggestContainer = $('#' + suggestContainerId); + suggestContainer.empty(); + } else { + suggestContainer = $('
'); //创建一个子
+ } + + suggestContainer.attr('id', suggestContainerId); + suggestContainer.attr('tabindex', '0'); + suggestContainer.hide(); + var _initItems = function(items) { + suggestContainer.empty(); + for (var i = 0; i < items.length; i++) { + if(items[i].text.length > maxFontNumber){ + maxFontNumber = items[i].text.length; + } + var suggestItem = $('
'); //创建一个子
+ suggestItem.attr('id', items[i].id); + suggestItem.append(items[i].text); + suggestItem.css({ + 'padding':defaulOption.itemPadding + 'px', + 'white-space':'nowrap', + 'cursor': 'pointer', + 'background-color': defaulOption.itemBackgroundColor, + 'color': defaulOption.itemColor + }); + suggestItem.bind("mouseover", + function() { + $(this).css({ + 'background-color': defaulOption.itemOverBackgroundColor, + 'color': defaulOption.itemOverColor + }); + currentItem = $(this); + }); + suggestItem.bind("mouseout", + function() { + $(this).css({ + 'background-color': defaulOption.itemBackgroundColor, + 'color': defaulOption.itemColor + }); + currentItem = null; + }); + suggestItem.bind("click", showClickTextFunction); + suggestItem.bind("click", itemSelectFunction); + suggestItem.appendTo(suggestContainer); + suggestContainer.appendTo(document.body); + } + } + + var inputChange = function() { + var inputValue = $('#' + target).val(); + inputValue = inputValue.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + var matcher = new RegExp(inputValue, "i"); + return $.grep(data, + function(value) { + return matcher.test(value.text); + }); + } + + $('#' + target).bind("keyup", + function() { + _initItems(inputChange()); + }); + $('#' + target).bind("blur", + function() { + if(!$('#' + suggestContainerId).is(":focus")){ + $('#' + suggestContainerId).hide(); + if (currentItem) { + currentItem.trigger("click"); + } + currentItem = null; + return; + } + }); + + $('#' + target).bind("click", + function() { + if (defaulOption.alwaysShowALL) { + _initItems(data); + } else { + _initItems(inputChange()); + } + $('#' + suggestContainerId).removeAttr("style"); + $('#' + suggestContainerId).css({ + 'border': '1px solid #ccc', + 'max-height': '200px', + 'top': suggestContainerTop, + 'left': suggestContainerLeft, + 'width': 'auto', + 'position': 'absolute', + 'font-size': defaulOption.fontSize+'px', + 'font-family':'Arial', + 'z-index': 99999, + 'background-color': '#FFFFFF', + 'overflow-y': 'auto', + 'overflow-x': 'hidden', + 'white-space':'nowrap' + + }); + $('#' + suggestContainerId).show(); + }); + _initItems(data); + + $('#' + suggestContainerId).bind("blur", + function() { + $('#' + suggestContainerId).hide(); + }); + + } +})(jQuery); diff --git a/static/js/bootstrap.min.js b/static/js/bootstrap.min.js new file mode 100644 index 00000000..848258d3 --- /dev/null +++ b/static/js/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! +* Bootstrap.js by @fat & @mdo +* Copyright 2013 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('