[
  {
    "path": ".autod.conf.js",
    "content": "'use strict';\n\nmodule.exports = {\n  write: true,\n  prefix: '^',\n  plugin: 'autod-egg',\n  test: [\n    'test',\n    'benchmark',\n  ],\n  dep: [\n    'egg',\n    'egg-scripts',\n  ],\n  devdep: [\n    'egg-ci',\n    'egg-bin',\n    'egg-mock',\n    'autod',\n    'autod-egg',\n    'eslint',\n    'eslint-config-egg',\n    'webstorm-disable-index',\n  ],\n  exclude: [\n    './test/fixtures',\n    './dist',\n  ],\n};\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": ".eslintignore",
    "content": "\ncoverage\n\n\n\n\n\n\n\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n    \"extends\": \"eslint-config-egg\",\n    \"rules\": {\n        \"indent\": [\n            \"error\",\n            4\n        ]\n    },\n    \"plugins\": [\n        \"html\"\n    ],\n    \"settings\": {\n        \"html/html-extensions\": [\n            \".html\"\n        ]\n    }\n}"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n\nlogs/\nnpm-debug.log\nyarn-error.log\nnode_modules/\npackage-lock.json\nyarn.lock\ncoverage/\n.idea/\nrun/\nD/\nbuildlogs/\n.DS_Store\n*.sw*\n*.un~\n\nconfig/config.prod_1.js\nbuildlogs/\n\n\n\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: node_js\nnode_js:\n  - '8'\ninstall:\n  - npm i npminstall && npminstall\nscript:\n  - npm run ci\nafter_script:\n  - npminstall codecov && codecov\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-2019 zane\n- zane ([@wangweianger](https://github.com/wangweianger))\n\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# APubPlat - [开发文档](http://apub-wiki.seosiwei.com/)\n[![Node](https://img.shields.io/badge/node-8.9.0~10.15.1-green.svg?style=plastic)](https://nodejs.org/en/)\n[![Vue](https://img.shields.io/badge/vue-2.0+-blue.svg?style=plastic)](https://cn.vuejs.org/)\n[![Egg](https://img.shields.io/badge/egg-2.0-green.svg?style=plastic)](https://eggjs.org/)\n[![Mogodb](https://img.shields.io/badge/mogodb-4.0+-brightgreen.svg?style=plastic)](https://www.mongodb.com/)\n[![Redis](https://img.shields.io/badge/redis-5.0+-green.svg?style=plastic)](https://redis.io/)\n[![Ssh2](https://img.shields.io/badge/ssh2-blue.svg?style=plastic)](https://www.npmjs.com/package/ssh2)\n[![Xterm](https://img.shields.io/badge/xterm-green.svg?style=plastic)](https://xtermjs.org/)\n[![Monaco-editor](https://img.shields.io/badge/monaco-editor-green.svg?style=plastic)](https://microsoft.github.io/monaco-editor/)\n[![Socket.io](https://img.shields.io/badge/socket.io-brightgreen.svg?style=plastic)](https://socket.io/)\n\nAPubPlat是一款开源免费的自动化部署、运维平台，开源堡垒机。\n\n实现了Web Terminal，跟xshell一样的体验。可开启多窗口和批量命令的运行。\n\n友好的持续集成，支持web前端、node、java、php等后端servers的发布，并支持单机和多机的同时发布能力。\n\n### 开发文档：http://apub-wiki.seosiwei.com\n\n# Preview\n![](https://github.com/wangweianger/APubPlat/blob/master/demo/01.png \"\")\n![](https://github.com/wangweianger/APubPlat/blob/master/demo/02.png \"\")\n### 发布、备份、生成配置日志\n![](https://github.com/wangweianger/APubPlat/blob/master/demo/03.png \"\")\n### build servers\n![](https://github.com/wangweianger/APubPlat/blob/master/demo/04.png \"\")\n### Web Terminal\n![](https://github.com/wangweianger/APubPlat/blob/master/demo/05.png \"\")\n\n\n\n\n"
  },
  {
    "path": "app/controller/api/application.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass ApplicationController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo || 1;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n        const team_code = query.team_code;\n        const environ_code = query.environ_code;\n        const net_type = query.net_type;\n        const status = query.status;\n        const name = query.app_name;\n\n        const result = await this.ctx.service.application.list(pageNo, pageSize, team_code, environ_code, net_type, status, name);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    async all() {\n        const { ctx } = this;\n        const result = await this.ctx.service.application.all();\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        let status = query.status || 1;\n        status = status * 1;\n        const name = query.name;\n        const code = query.code;\n        const team_code = query.team_code;\n        const environ_code = query.environ_code;\n        const _id = query._id;\n        const user_name = query.user_name;\n        if (type === 2 && !_id) throw new Error('id参数不能为空!');\n        if (!team_code) throw new Error('请选择应用所属团队!');\n        if (!environ_code) throw new Error('请选择应用所属环境!');\n        if (!name) throw new Error('应用名称不能为空!');\n        if (!code) throw new Error('应用编码不能为空!');\n\n        const result = await this.ctx.service.application.handle({ type, name, code, status, _id, team_code, environ_code, user_name });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 禁用 | 启用\n    async setStatus() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        const status = query.status || 1;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.application.setStatus({ _id, status });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.application.delete(_id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 分配资产\n    async distribution() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        const net_type = query.net_type || 1;\n        const assets_list = query.assets_list || [];\n\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.application.distribution(_id, assets_list, net_type);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 获得单个应用详情\n    async itemdetail() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const id = query.id;\n\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.application.itemdetail(id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 更新应用构建配置\n    async updateConfigs() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id;\n        const tasklist = query.tasklist || [];\n\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.application.updateConfigs(id, tasklist);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 绑定|取消 应该绑定的邮箱\n    async handleEmail() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id;\n        const emaillist = query.emaillist || [];\n\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.application.handleEmail(id, emaillist);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = ApplicationController;\n"
  },
  {
    "path": "app/controller/api/assets.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass AssetsController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo || 1;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n        const team_code = query.team_code;\n        const assets_name = query.assets_name;\n        const status = query.status || '';\n\n        const result = await this.ctx.service.assets.list(pageNo, pageSize, team_code, assets_name, status);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    async all() {\n        const { ctx } = this;\n        const result = await this.ctx.service.assets.all();\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        let status = query.status || 1;\n        status = status * 1;\n        const name = query.name;\n        const code = query.code;\n        const _id = query._id;\n        const team_code = query.team_code;\n        const outer_ip = query.outer_ip;\n        const lan_ip = query.lan_ip;\n        const user = query.user;\n        const port = query.port;\n        const password = query.password;\n        const user_name = query.user_name;\n\n        if (type === 2 && !_id) throw new Error('id参数不能为空!');\n        if (!team_code) throw new Error('请选择资产所属团队!');\n        if (!name) throw new Error('资产名称不能为空!');\n        if (!code) throw new Error('资产编码不能为空!');\n        if (!outer_ip) throw new Error('外网IP不能为空!');\n        if (!lan_ip) throw new Error('外网IP不能为空!');\n        if (!user) throw new Error('登录用户名不能为空!');\n        if (!port) throw new Error('登录端口号不能为空!');\n        if (!password) throw new Error('登录密码不能为空!');\n\n        const result = await this.ctx.service.assets.handle({ type, name, code, status, _id, team_code, outer_ip, lan_ip, user, port, password, user_name });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 禁用 | 启用\n    async setStatus() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        const status = query.status || 1;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.assets.setStatus({ _id, status });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.assets.delete(_id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n\n}\n\nmodule.exports = AssetsController;\n"
  },
  {
    "path": "app/controller/api/build.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass BuildController extends Controller {\n\n    // 生成构建配置\n    async generateBuildConfig() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id || '';\n        const taskItem = query.taskItem || {};\n        const assetsList = query.assetsList || [];\n        const user_name = query.user_name || '';\n\n        if (!taskItem.shell_path) throw new Error('shell脚本地址不能为空!');\n        if (!taskItem.shell_body) throw new Error('shell脚本内容不能为空!');\n\n        const result = await this.ctx.service.build.generateBuildConfig(id, taskItem, assetsList, user_name);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 服务备份\n    async backupApplications() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const taskItem = query.taskItem || {};\n        const assetsList = query.assetsList || [];\n        const id = query.id || '';\n        const user_name = query.user_name || '';\n\n        if (!taskItem.project_path) throw new Error('应用所在位置不能为空!');\n        if (!taskItem.backups_path) throw new Error('应用备份的路径不能为空!');\n\n        const result = await this.ctx.service.build.backupApplications(id, taskItem, assetsList, user_name);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 构建应用\n    async buildApplicationed() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const result = await this.ctx.service.logs.addLogs(query);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 应用还原\n    async reductionApplications() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const taskItem = query.taskItem || {};\n        const assetsList = query.assetsList || [];\n        const id = query.id || '';\n        const user_name = query.user_name || '';\n\n        if (!taskItem.reduction_shell_path) throw new Error('备份shell脚本路径不能为空!');\n        if (!taskItem.reduction_shell_body) throw new Error('备份shell脚本内容不能为空!');\n\n        const result = await this.ctx.service.build.reductionApplications(id, taskItem, assetsList, user_name);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = BuildController;\n"
  },
  {
    "path": "app/controller/api/commtask.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass CommtaskController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const result = await this.ctx.service.commtask.list();\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const name = query.name;\n        const handletype = query.handletype || 1;\n        const _id = query._id;\n        const shell_body = query.shell_body;\n\n        if (parseInt(handletype) === 2 && !_id) throw new Error('id参数不能为空!');\n        if (!name) throw new Error('脚本任务名称不能为空!');\n        if (!shell_body) throw new Error('脚本任务脚本内容不能为空!');\n\n        const result = await this.ctx.service.commtask.handle(query);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id;\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.commtask.delete(id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = CommtaskController;\n"
  },
  {
    "path": "app/controller/api/console.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass ConsoleController extends Controller {\n\n    // 获得服务器ssh key\n    async getAssetSshKey() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const { host, port, username, password } = query;\n        if (!host) throw new Error('资产host不能为空!');\n        if (!port) throw new Error('资产port不能为空!');\n        if (!username) throw new Error('资产username不能为空!');\n        if (!password) throw new Error('资产password不能为空!');\n\n        const result = await this.ctx.service.util.getAssetSshKey(query);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 执行shell任务\n    async handleShellTasks() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const { host, port, username, password, shell_type, shell_body, shell_path } = query;\n        if (!host) throw new Error('资产host不能为空!');\n        if (!port) throw new Error('资产port不能为空!');\n        if (!username) throw new Error('资产username不能为空!');\n        if (!password) throw new Error('资产password不能为空!');\n        if (!shell_type) throw new Error('执行脚本的类型不能为空!');\n        if (!shell_body) throw new Error('执行脚本的内容不能为空!');\n        if (parseInt(shell_type) === 2 && !shell_path) throw new Error('执行脚本的脚本路径不能为空!');\n\n        const result = await this.ctx.service.util.handleShellTasks(query);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = ConsoleController;\n"
  },
  {
    "path": "app/controller/api/email.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass EmailController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo || 1;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n        const status = query.status;\n\n        const result = await this.ctx.service.email.list(pageNo, pageSize, status);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        let status = query.status || 1;\n        status = status * 1;\n        const name = query.name;\n        const email = query.email;\n        const id = query.id;\n        if (type === 2 && !id) throw new Error('id参数不能为空!');\n        if (!email) throw new Error('邮件地址不能为空!');\n        if (!name) throw new Error('邮件所属人不能为空!');\n\n        const result = await this.ctx.service.email.handle({ type, name, email, status, id });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 禁用 | 启用\n    async setStatus() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id;\n        const status = query.status || 1;\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.email.setStatus({ id, status });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id;\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.email.delete(id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n\n}\n\nmodule.exports = EmailController;\n"
  },
  {
    "path": "app/controller/api/environment.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass TeamController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo || 1;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n\n        const result = await this.ctx.service.environment.list(pageNo, pageSize);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        const name = query.name;\n        const code = query.code;\n        const id = query.id;\n        if (type === 2 && !id) throw new Error('id参数不能为空!');\n        if (!name) throw new Error('环境名称不能为空!');\n        if (!code) throw new Error('环境编码不能为空!');\n\n        const result = await this.ctx.service.environment.handle({ type, name, code, id });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id;\n        if (!id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.environment.delete(id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n\n}\n\nmodule.exports = TeamController;\n"
  },
  {
    "path": "app/controller/api/files.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass FilesController extends Controller {\n\n    // update file\n    async updatefile() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const content = query.content;\n        const filename = query.filename;\n        if (!filename) throw new Error('需要修改的文件不能为空!');\n        if (!content) throw new Error('修改文件的内容不能为空!');\n\n        const result = await this.ctx.service.files.updatefile(filename, content) || {};\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add file\n    async addfile() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const filename = query.filename.trim();\n        const content = query.content;\n        const remotePath = query.remotePath;\n        if (!filename) throw new Error('文件名称不能为空!');\n        if (!content) throw new Error('新增文件的内容不能为空!');\n        if (!remotePath) throw new Error('新增文件的远程目录不能为空!');\n\n        const result = await this.ctx.service.files.addfile(filename, content, remotePath) || {};\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = FilesController;\n"
  },
  {
    "path": "app/controller/api/logs.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass LogsController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo || 1;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n        const type = query.type || 1;\n        const name = query.name || '';\n        const application_id = query.application_id || '';\n\n        const result = await this.ctx.service.logs.list(pageNo, pageSize, type, name, application_id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        let status = query.status || 1;\n        status = status * 1;\n        const name = query.name;\n        const code = query.code;\n        const _id = query._id;\n        if (type === 2 && !_id) throw new Error('id参数不能为空!');\n        if (!name) throw new Error('团队名称不能为空!');\n        if (!code) throw new Error('团队编码不能为空!');\n\n        const result = await this.ctx.service.team.handle({ type, name, code, status, _id });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 禁用 | 启用\n    async setStatus() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        const status = query.status || 1;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.team.setStatus({ _id, status });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.team.delete(_id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n\n}\n\nmodule.exports = LogsController;\n"
  },
  {
    "path": "app/controller/api/qiniu.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass QiniuController extends Controller {\n\n    async getToken() {\n        const { ctx } = this;\n        const result = await ctx.service.qiniu.getToken();\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = QiniuController;\n"
  },
  {
    "path": "app/controller/api/team.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass TeamController extends Controller {\n\n    async list() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo || 1;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n        const status = query.status;\n\n        const result = await this.ctx.service.team.list(pageNo, pageSize, status);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        let status = query.status || 1;\n        status = status * 1;\n        const name = query.name;\n        const code = query.code;\n        const _id = query._id;\n        if (type === 2 && !_id) throw new Error('id参数不能为空!');\n        if (!name) throw new Error('团队名称不能为空!');\n        if (!code) throw new Error('团队编码不能为空!');\n\n        const result = await this.ctx.service.team.handle({ type, name, code, status, _id });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 禁用 | 启用\n    async setStatus() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        const status = query.status || 1;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.team.setStatus({ _id, status });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.team.delete(_id);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n\n}\n\nmodule.exports = TeamController;\n"
  },
  {
    "path": "app/controller/api/user.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass UserController extends Controller {\n\n    // 用户登录\n    async login() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const userName = query.userName;\n        const passWord = query.passWord;\n\n        if (!userName) throw new Error('用户登录：userName不能为空');\n        if (!passWord) throw new Error('用户登录：passWord不能为空');\n\n        const result = await ctx.service.user.login(userName, passWord);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 用户注册\n    async register() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const userName = query.userName;\n        const passWord = query.passWord;\n\n        if (!userName) throw new Error('用户登录：userName不能为空');\n        if (!passWord) throw new Error('用户登录：passWord不能为空');\n\n        const result = await ctx.service.user.register(userName, passWord);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 退出登录\n    async logout() {\n        const { ctx } = this;\n        const usertoken = ctx.cookies.get('usertoken', {\n            encrypt: true,\n            signed: true,\n        }) || '';\n        if (!usertoken) throw new Error('退出登录：token不能为空');\n\n        await ctx.service.user.logout(usertoken);\n        this.ctx.body = this.app.result({\n            data: {},\n        });\n    }\n\n    // 获得用户列表\n    async getUserList() {\n        const { ctx } = this;\n        const query = ctx.request.query;\n        const pageNo = query.pageNo;\n        const pageSize = query.pageSize || this.app.config.pageSize;\n        const username = query.username;\n\n        const result = await ctx.service.user.getUserList(pageNo, pageSize, username);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // add | update\n    async handle() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        let type = query.type || 1;\n        type = type * 1;\n        let status = query.status || 1;\n        status = status * 1;\n        const user_name = query.user_name;\n        const pass_word = query.pass_word;\n        const _id = query._id;\n\n        if ((type === 2 || type === 3) && !_id) throw new Error('id参数不能为空!');\n        if (type === 1 && !user_name) throw new Error('用户名称不能为空!');\n        if ((type === 1 || type === 3) && !pass_word) throw new Error('密码不能为空!');\n\n        const result = await this.ctx.service.user.handle({ type, user_name, pass_word, status, _id });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 禁用 | 启用\n    async setStatus() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const _id = query._id;\n        const status = query.status || 1;\n        const usertoken = query.usertoken || '';\n        if (!_id) throw new Error('id参数不能为空!');\n\n        const result = await this.ctx.service.user.setStatus({ _id, status, usertoken });\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 删除用户\n    async delete() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const id = query.id || '';\n        const usertoken = query.usertoken || '';\n\n        if (!id) throw new Error('id不能为空');\n\n        const result = await ctx.service.user.delete(id, usertoken);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    async setToken() {\n        this.ctx.body = this.app.result({\n            data: 'success',\n        });\n    }\n}\n\nmodule.exports = UserController;\n"
  },
  {
    "path": "app/controller/api/util.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass UtilController extends Controller {\n\n    // 获得服务器ssh key\n    async getAssetSshKey() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const { host, port, username, password } = query;\n        if (!host) throw new Error('资产host不能为空!');\n        if (!port) throw new Error('资产port不能为空!');\n        if (!username) throw new Error('资产username不能为空!');\n        if (!password) throw new Error('资产password不能为空!');\n\n        const result = await this.ctx.service.util.getAssetSshKey(query);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n    // 执行shell任务\n    async handleShellTasks() {\n        const { ctx } = this;\n        const query = ctx.request.body;\n        const { host, port, username, password, shell_type, shell_body, shell_path } = query;\n        if (!host) throw new Error('资产host不能为空!');\n        if (!port) throw new Error('资产port不能为空!');\n        if (!username) throw new Error('资产username不能为空!');\n        if (!password) throw new Error('资产password不能为空!');\n        if (!shell_type) throw new Error('执行脚本的类型不能为空!');\n        if (!shell_body) throw new Error('执行脚本的内容不能为空!');\n        if (parseInt(shell_type) === 2 && !shell_path) throw new Error('执行脚本的脚本路径不能为空!');\n\n        const result = await this.ctx.service.util.handleShellTasks(query);\n\n        ctx.body = this.app.result({\n            data: result,\n        });\n    }\n\n}\n\nmodule.exports = UtilController;\n"
  },
  {
    "path": "app/controller/home.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\n\nclass HomeController extends Controller {\n  async index() {\n    this.ctx.body = 'hi, egg';\n  }\n}\n\nmodule.exports = HomeController;\n"
  },
  {
    "path": "app/controller/web.js",
    "content": "'use strict';\n// const fs = require('fs');\n// const path = require('path');\nconst Controller = require('egg').Controller;\n\nclass WebController extends Controller {\n\n    async home() {\n        const { ctx } = this;\n\n        // this.ctx.service.ssh2.shell();\n        // this.ctx.service.ssh2.exec('bash /data/down/app.sh');\n        // const result = await this.ctx.service.sftp.list('/data/down');\n        // console.log(result);\n        // const result = await this.ctx.service.sftp.get('/data/down/miao.sh');\n\n        await ctx.render('home', {\n            data: {},\n        });\n    }\n\n    // 团队管理\n    async team() {\n        const { ctx } = this;\n        await ctx.render('team', {\n            data: {},\n        });\n    }\n\n    // 应用管理\n    async application() {\n        const { ctx } = this;\n        await ctx.render('application', {\n            data: {},\n        });\n    }\n\n    // 构建配置\n    async appconfig() {\n        const { ctx } = this;\n        await ctx.render('appconfig', {\n            data: {},\n        });\n    }\n\n    // 应用管理\n    async assets() {\n        const { ctx } = this;\n        await ctx.render('assets', {\n            data: {},\n        });\n    }\n\n    // 环境配置\n    async environment() {\n        const { ctx } = this;\n        await ctx.render('environment', {\n            data: {},\n        });\n    }\n\n    // 邮件管理\n    async emails() {\n        const { ctx } = this;\n        await ctx.render('email', {\n            data: {},\n        });\n    }\n\n    // 应用构建\n    async build() {\n        const { ctx } = this;\n        await ctx.render('build', {\n            data: {},\n        });\n    }\n\n    // 开始构建\n    async buildprocess() {\n        const { ctx } = this;\n        await ctx.render('buildprocess', {\n            data: {},\n        });\n    }\n\n    // 资产构建\n    async assetsconfig() {\n        const { ctx } = this;\n        await ctx.render('assetsconfig', {\n            data: {},\n        });\n    }\n\n    // 脚本任务\n    async commtask() {\n        const { ctx } = this;\n        await ctx.render('commtask', {\n            data: {},\n        });\n    }\n\n    // 控制台\n    async console() {\n        const { ctx } = this;\n        await ctx.render('console', {\n            data: {},\n        });\n    }\n\n    // 构建日志\n    async logs() {\n        const { ctx } = this;\n        await ctx.render('logs', {\n            data: {},\n        });\n    }\n\n    // 构建还原\n    async reduction() {\n        const { ctx } = this;\n        await ctx.render('reduction', {\n            data: {},\n        });\n    }\n\n    // 用户管理\n    async user() {\n        const { ctx } = this;\n        await ctx.render('user', {\n            data: {},\n        });\n    }\n\n    // 用户登录\n    async login() {\n        const { ctx } = this;\n        await ctx.render('login', {\n            data: {},\n        });\n    }\n\n}\n\nmodule.exports = WebController;\n"
  },
  {
    "path": "app/extend/application.js",
    "content": "'use strict';\nconst md5 = require('md5');\n\nmodule.exports = {\n    /* 生成随机字符串 */\n    randomString(len) {\n        len = len || 7;\n        const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';\n        const maxPos = $chars.length;\n        let pwd = '';\n        for (let i = 0; i < len; i++) {\n            pwd += $chars.charAt(Math.floor(Math.random() * maxPos));\n        }\n        return pwd + Date.now();\n    },\n\n    /* 本地加密算法 */\n    signwx(json) {\n        const wxkey = 'ZANEWANGWEI123456AGETEAMABmiliH';\n        /* 对json的key值排序 */\n        const arr = [];\n        const sortJson = {};\n        const newJson = json;\n        for (const key in json) {\n            if (json[key]) {\n                arr.push(key);\n            }\n        }\n        arr.sort((a, b) => {\n            return a.localeCompare(b);\n        });\n        for (let i = 0, len = arr.length; i < len; i++) {\n            sortJson[arr[i]] = json[arr[i]];\n        }\n        /* 拼接json为key=val形式 */\n        let str = '';\n        for (const key in sortJson) {\n            str += key + '=' + sortJson[key] + '&';\n        }\n        str += 'key=' + wxkey;\n        /* md5 */\n        const md5Str = md5(str);\n        const signstr = md5Str.toUpperCase();\n        /* 获得有sign参数的json */\n        newJson.paySign = signstr;\n        return newJson;\n    },\n    // 返回结果json\n    result(jn = {}) {\n        return Object.assign({\n            code: 1000,\n            desc: '成功',\n            data: '',\n        }, jn);\n    },\n    format(date, fmt) {\n        const o = {\n            'M+': date.getMonth() + 1, // 月份\n            'd+': date.getDate(), // 日\n            'h+': date.getHours(), // 小时\n            'H+': date.getHours() > 12 ? date.getHours() - 12 : date.getHours(),\n            'm+': date.getMinutes(), // 分\n            's+': date.getSeconds(), // 秒\n        };\n        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));\n        for (const k in o) {\n            if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));\n        }\n        return fmt;\n    },\n};\n\n"
  },
  {
    "path": "app/io/controller/nsp.js",
    "content": "'use strict';\n\nconst Controller = require('egg').Controller;\nconst socket = require('../../util/socket');\n\nclass NspController extends Controller {\n\n    async socket() {\n        const { ctx } = this;\n        const query = ctx.args[0];\n\n        const buildType = query.buildType;\n        switch (buildType) {\n        case 'buildprocess': case 'buildtasks':\n            this.buildProcess(query);\n            break;\n        case 'sshonline':\n            this.sshOnline(query);\n            break;\n        default:\n        }\n    }\n\n    // common ssh2 servers\n    ssh2Client(json = {}) {\n        const { ctx } = this;\n        let { data, taskItem, shell, id, cols, rows } = json;\n        data = data || [];\n        taskItem = taskItem || {};\n\n        const tasklist = [];\n        const date = new Date();\n        const { project_path, backups_path, is_backups } = taskItem;\n        let backupPath = '';\n        let backupDir = '';\n        if (taskItem && is_backups && project_path && backups_path) {\n            backupDir = 'bak_' + this.app.format(new Date(), 'yyyy-MM-dd:hh:mm:ss');\n            const projectName = project_path ? project_path.split('/').splice(-1).join() : '';\n            backupPath = `${backups_path}/${backupDir}/${projectName}`;\n        }\n\n        for (let i = 0; i < data.length; i++) {\n            const datas = data[i] || {};\n            const assitsItem = datas.assitsItem || {};\n            const item = Promise.resolve(\n                this.backUpProject(taskItem, assitsItem, backupPath, backupDir).then(data => {\n                    socket({\n                        id,\n                        date,\n                        taskName: taskItem.task_name ? `${taskItem.task_name}任务-构建应用服务` : '',\n                        assetsName: assitsItem.name,\n                        lanip: assitsItem.lan_ip,\n                        host: assitsItem.outer_ip,\n                        port: assitsItem.port,\n                        username: assitsItem.user,\n                        password: assitsItem.password,\n                        cols: cols || 138,\n                        rows: rows || 46,\n                        term: 'xterm-color',\n                        taskType: taskItem.task_type || 'command',\n                        socket: {\n                            socket: ctx.socket,\n                            geometry: datas.geometry,\n                            close: datas.close,\n                            data: datas.data,\n                            end: datas.end,\n                            resize: datas.resize,\n                        },\n                        initialTask: shell,\n                    });\n                    return data;\n                })\n            );\n            tasklist.push(item);\n        }\n        return Promise.all(tasklist);\n    }\n\n    // 构建task\n    async sshOnline(query = {}) {\n        if (!query.data) return;\n        const { data, cols, rows } = query;\n        this.ssh2Client({ data, cols, rows });\n    }\n\n    // 应用构建\n    async buildProcess(query = {}) {\n        if (!query.data) return;\n        const { taskItem, data, id, user_name } = query;\n\n        let shell = '';\n        if (taskItem && taskItem.shell_path) {\n            shell = taskItem.shell_path ?\n                `sh ${taskItem.shell_path} ${taskItem.shell_opction || ''} \\n` :\n                taskItem.shell_body + '\\n';\n        } else if (taskItem && taskItem.shell_body) {\n            shell = taskItem.shell_body + '\\n';\n        }\n\n        const result = await this.ssh2Client({ data, taskItem, shell, id });\n        // 保存备份日志\n        taskItem.is_backups && this.ctx.service.logs.addLogs({\n            name: `${taskItem.task_name}任务-服务备份`,\n            type: 2,\n            user_name,\n            application_id: id,\n            content: result || [],\n        });\n    }\n\n    // 备份\n    backUpProject(taskItem = {}, assitsItem = {}, backupPath, backupDir) {\n        const { is_backups, project_path, backups_path } = taskItem;\n        let promise = null;\n        if (taskItem && is_backups && project_path && backups_path) {\n            promise = this.ctx.service.build.backUpProject(taskItem, assitsItem, backupPath, backupDir);\n        } else {\n            promise = new Promise(resolve => { resolve(1); });\n        }\n        return promise;\n    }\n\n}\n\nmodule.exports = NspController;\n"
  },
  {
    "path": "app/io/middleware/auth.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n    return async (ctx, next) => {\n        // ctx.socket.emit('response', 'connected!');\n        await next();\n    };\n};\n"
  },
  {
    "path": "app/middleware/token_required.js",
    "content": "'use strict';\nconst { URL } = require('url');\n\n// 校验用户是否登录\nmodule.exports = () => {\n    return async (ctx, next) => {\n        const referer = ctx.request.header.referer || '';\n        const url = new URL(referer);\n        if (ctx.app.config.origin && ctx.app.config.origin.indexOf(url.origin) === -1) {\n            ctx.body = {\n                code: 1004,\n                desc: '域名来源有误,请检查config的origin配置',\n            };\n            return;\n        }\n        const usertoken = ctx.cookies.get('usertoken', {\n            encrypt: true,\n            signed: true,\n        }) || '';\n        if (!usertoken) {\n            ctx.body = {\n                code: 1004,\n                desc: '用户未登录,请重新登录',\n            };\n            return;\n        }\n\n        const data = await ctx.service.user.finUserForToken(usertoken);\n        if (!data || !data.user_name) {\n            ctx.cookies.set('usertoken', '');\n            const descr = data && !data.user_name ? data.desc : '登录用户无效,请重新登录！';\n            ctx.body = {\n                code: 1004,\n                desc: descr,\n            };\n            return;\n        }\n        await next();\n    };\n};\n"
  },
  {
    "path": "app/model/application.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const ApplicationSchema = new Schema({\n        name: { type: String }, // 应用名称\n        code: { type: String }, // 应用编码\n        user_name: { type: String }, // 操作人\n        team_code: { type: String }, // 所属公司编码\n        environ_code: { type: String }, // 所属环境编码\n        net_type: { type: Number, default: 1 }, // 网络部署IP说明 1：外网IP  2：内网IP\n        assets_list: { type: Array }, // 拥有资产列表\n        task_list: { type: Array }, // 任务list\n        email_list: { type: Array }, // 绑定邮箱列表\n        status: { type: Number, default: 1 }, // 可用装填 1：可用  0：禁用\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Application', ApplicationSchema);\n};\n"
  },
  {
    "path": "app/model/assets.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const AssetsSchema = new Schema({\n        name: { type: String }, // 资产名称\n        code: { type: String }, // 资产编码\n        user_name: { type: String }, // 操作人\n        outer_ip: { type: String }, // 外网IP\n        lan_ip: { type: String }, // 内网IP\n        user: { type: String }, // 登录用户\n        port: { type: Number }, // 登录端口号\n        password: { type: String }, // 登录密码\n        team_code: { type: String }, // 所属团队编码\n        status: { type: Number, default: 1 }, // 可用装填 1：可用  0：禁用\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Assets', AssetsSchema);\n};\n"
  },
  {
    "path": "app/model/commtask.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const CommtaskSchema = new Schema({\n        name: { type: String }, // 任务名称\n        type: { type: Number, default: 1 }, // 任务类型 1：窗口运行命令  2：shell脚本命令\n        user_name: { type: String }, // 操作人\n        shell_body: { type: String }, // 运行内容\n        shell_opction: { type: String }, // shell 参数\n        shell_path: { type: String }, // shell存放路径\n        shell_write_type: { type: String }, // 脚本写入服务器方式  1：新建文件并上传方式 2：shell窗口命令行创建方式\n        btn_color: { type: String, default: 'primary' }, // 按钮颜色 可选类型 ：primary|success|info|warning|danger|default\n        is_plain: { type: Number, default: 2 }, // 是:1 否：2\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Commtask', CommtaskSchema);\n};\n"
  },
  {
    "path": "app/model/email.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const TeamSchema = new Schema({\n        email: { type: String }, // 邮件地址\n        name: { type: String }, // 邮件所属人员\n        status: { type: Number, default: 1 }, // 可用装填 1：可用  0：禁用\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Email', TeamSchema);\n};\n"
  },
  {
    "path": "app/model/environment.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const EnvironmentSchema = new Schema({\n        name: { type: String }, // 团队名称\n        code: { type: String }, // 团队编码\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Environment', EnvironmentSchema);\n};\n"
  },
  {
    "path": "app/model/logs.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const LogsSchema = new Schema({\n        name: { type: String }, // 日志名称\n        user_name: { type: String }, // 操作人\n        application_id: { type: String }, // 所属应用code\n        commtask_id: { type: String }, // 所属脚本任务code\n        type: { type: Number, default: 1 }, // 日志类型 1：发布服务  2：应用服务备份  3：应用生成构建配置 4：服务资产生成构建配置 5:备份还原记录\n        delete: { type: Boolean, default: false }, // 是否删除过\n        content: { type: Object }, // 日志item内容项目\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Logs', LogsSchema);\n};\n"
  },
  {
    "path": "app/model/team.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const TeamSchema = new Schema({\n        name: { type: String }, // 团队名称\n        code: { type: String }, // 团队编码\n        status: { type: Number, default: 1 }, // 可用装填 1：可用  0：禁用\n        create_time: { type: Date, default: Date.now }, // 创建时间\n    });\n\n    return mongoose.model('Team', TeamSchema);\n};\n"
  },
  {
    "path": "app/model/user.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const mongoose = app.mongoose;\n    const Schema = mongoose.Schema;\n\n    const UserSchema = new Schema({\n        user_name: { type: String }, // 用户名称\n        pass_word: { type: String }, // 用户密码\n        status: { type: Number, default: 1 }, // 可用装填 1：可用  0：禁用\n        token: { type: String }, // 用户秘钥\n        usertoken: { type: String }, // 用户登录态秘钥\n        create_time: { type: Date, default: Date.now }, // 用户访问时间\n    });\n\n    UserSchema.index({ user_name: -1, token: -1 });\n\n    return mongoose.model('User', UserSchema);\n};\n"
  },
  {
    "path": "app/public/css/base.css",
    "content": "/*CSS Reset*/\n*, *:before, *:after { -webkit-box-sizing: border-box; box-sizing: border-box; }\n/*设置字体*/\n@font-face{font-family: OpenSans;src: url('../font/OpenSans-Regular.ttf'),}\nbody{background: #f9fafb;font-family: 'OpenSans', sans-serif!important;}\n/*字体结束*/\nbody, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, header, hgroup, nav, section, article, aside, footer, figure, figcaption, menu, button { \n  margin: 0; padding: 0;};\nbody { line-height: 1.5; font-size: 14px; color: #70727C; -webkit-text-size-adjust: none; -webkit-tap-highlight-color: rgba(255, 255, 255, 0); outline: 0; }\nh1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: normal; }\ntable { border-collapse: collapse; border-spacing: 0; }\nfieldset, img { border: 0; }\nli { list-style: none; }\ninput, textarea, select { font-family: inherit; font-size: inherit; font-weight: inherit; outline: none; -webkit-appearence: none; -ms-appearence: none; }\nbutton, html input[type=\"button\"], input[type=\"reset\"], input[type=\"submit\"] { border: none; background: none; -webkit-appearance: none; outline: none; }\na { -webkit-touch-callout: none; text-decoration: none; outline: 0; color:#8776f7;text-decoration:none;}\nem, i { font-style: normal; }\niframe { display: none; }\n\n[v-cloak] {display: none; }\n\n.mt10{margin-top:10px;}\n.mt15{margin-top:15px;}\n.mt20{margin-top:20px;}\n.mt30{margin-top:30px;}\n.mt50{margin-top:50px;}\n.mt90{margin-top:90px;}\n.mt200{margin-top:200px!important;}\n.mb10{margin-bottom:10px;}\n.mb20{margin-bottom:20px;}\n.mb30{margin-bottom:30px;}\n.mb50{margin-bottom:50px;}\n.mb100{margin-bottom:100px;}\n.mb200{margin-bottom:200px;}\n.ml10{margin-left:10px;}\n.ml20{margin-left:20px;}\n.ml30{margin-left:30px;}\n.ml50{margin-left:50px;}\n.ml100{margin-left:100px;}\n.mr5{margin-right:5px;}\n.mr10{margin-right:10px;}\n.mr20{margin-right:20px;}\n\n.pb100{padding-bottom:100px;}\n.pdl20{padding-left:20px!important;}\n\n.lightcolor{color:#999;}\n.primary{color:#409eff;}\n.success{color:#67c23a;}\n.warning{color:#e6a23c;}\n.tipscolor{color:#e15f63;font-size:14px;}\n.light{color:#999;font-size:12px;}\n.red{color:red!important;}\n.tc{text-align:center;}\n.bold{font-weight:bold;}\n\n.fs-12{font-size:12px;}\n.fs-14{font-size:14px;}\n.fs-16{font-size:16px;}\n.fs-18{font-size:18px;}\n.fs-22{font-size:22px;}\n\n.fl{float:left;}\n.fr{float:right;}\n\n.w-50{width:50px!important;}\n.w-80{width:80px!important;}\n.w-100{width:100px!important;}\n.w-150{width:150px!important;}\n.w-200{width:200px!important;}\n.w-300{width:300px!important;}\n.w-500{width:500px!important;}\n\n.common-width{\n    width:1200px;\n    margin:0 auto;\n}\n\n/* 等待加载样式 */\n#loading{\n    display:none;\n    background: url(/public/img/loading.gif) rgba(12,12,12,.2) no-repeat center center;\n    position: fixed;\n    width: 100%;\n    height: 100%;\n    top:0;\n    left:0;\n    z-index:10000;\n}\n\n/****阿里icon*****/\n@font-face {\n  font-family: 'iconfont';  /* project id 769227 */\n  src: url('//at.alicdn.com/t/font_769227_f7de0h0gpza.eot');\n  src: url('//at.alicdn.com/t/font_769227_f7de0h0gpza.eot?#iefix') format('embedded-opentype'),\n  url('//at.alicdn.com/t/font_769227_f7de0h0gpza.woff2') format('woff2'),\n  url('//at.alicdn.com/t/font_769227_f7de0h0gpza.woff') format('woff'),\n  url('//at.alicdn.com/t/font_769227_f7de0h0gpza.ttf') format('truetype'),\n  url('//at.alicdn.com/t/font_769227_f7de0h0gpza.svg#iconfont') format('svg');\n}\n.iconfont {\n    font-family:\"iconfont\";\n\tfont-size:16px;\n\tfont-style:normal;\n\tvertical-align: middle;\n}\n\n/* body content widht */\n#body_content{\n    overflow-y: auto;\n    font-size:14px;\n    margin-left:180px;\n    padding:50px 0 30px;\n}\n\n.content{\n    padding:20px;\n}\n.com_title{\n    font-size:30px;\n    font-weight: 300;\n}\n/* common table block */\n.com_table_block{\n\toverflow:hidden;\n    background:#fff;\n    box-shadow: 5px 5px 10px #eee\n}\n.com_table_block .table_header{\n    height:50px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: space-between;\n    padding:20px;\n    border-bottom:solid 1px #eee;\n}\n.com_table_block .l{\n    font-size:15px;\n}\n.common_pages{\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    padding:20px 0 30px;\n}\n\n.com_model_main{\n    overflow:hidden;\n    padding:0 10px;\n}\n.com_model_block{\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    margin-bottom:20px;\n    min-height:40px;\n}\n.com_model_block .left{\n    width:100px;\n}\n.com_model_block .inp{\n    width:250px;\n}\n.com_model_main .btns{\n    margin-top:30px;\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n}\n\n.com_top{\n    background: #fff;\n    padding:20px;\n    margin:20px 0;\n    box-shadow: 5px 5px 10px #eee\n}\n.com_top .inp{\n    width:250px;\n}\n\n.common_tops{\n    font-size:12px;\n    color:#999;\n}\n.common_tops .el-icon-warning{\n    color:#409eff;\n    margin-right:6px;\n}\n\n/* 公共输入框 */\n.text_comm_tarea{\n    width:100%;\n    height:300px;\n    background:#000;\n    padding:20px;\n    border:solid 1px #000;\n    color:#fff;\n    font-size:16px;\n}\n/*滚动条样式*/\n.text_comm_tarea::-webkit-scrollbar {\n    width: 4px;     \n    height: 4px;\n}\n.text_comm_tarea::-webkit-scrollbar-thumb {\n    border-radius: 5px;\n    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\n    background: rgba(0,0,0,0.6);\n}\n.text_comm_tarea::-webkit-scrollbar-track {\n    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\n    border-radius: 0;\n    background: rgba(0,0,0,.1);\n}\n\n/* build model */\n.comm_shell_model{}\n.comm_shell_model .mask {\n    position: fixed;\n    left:0;\n    top:0;\n    width:100%;\n    height:100%;\n    background: rgba(0,0,0,.1);\n    z-index:3000;\n}\n.comm_shell_model .comm_shell_model_content{\n    position: fixed;\n    left:0;\n    top:0;\n    width:calc(70%);\n    height: calc(80%);\n    margin-left:calc(15%);\n    margin-top:calc(5%);\n    background:#000;\n    border-radius:3px;\n    overflow:hidden;\n    z-index:3002;\n}\n.comm_shell_model .close{\n    position: absolute;\n    right:10px;\n    top:3px;\n    font-size:20px;\n    cursor:pointer;\n    color:#c7c7c7;\n    z-index:3001;\n}\n.comm_shell_model .menu{\n    right:150px;\n}\n.comm_shell_model .plus{\n    right:100px;\n}\n.comm_shell_model .minus{\n    right:50px;\n}\n.comm_shell_model .comm_shell_navs{\n    position: absolute;\n    left:0;\n    top:0;\n    width:100%;\n    background:#383838;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    height:25px;\n    z-index:110;\n}\n.comm_shell_model .comm_shell_navs .item{\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    border-right:solid 1px #aaa;\n    white-space: nowrap;\n    padding:0 10px;\n    height:100%;\n    font-size:12px;\n    font-weight:300;\n    cursor:pointer;\n}\n.comm_shell_model .comm_shell_navs .active{\n    color:#000;\n    background:#e0e0e0;\n}\n.comm_shell_model .terminal{\n    position: absolute;\n    left:8px;\n    top:10px;\n    width:calc(100% - 30px);\n    height:calc(100% - 60px);\n    z-index:1;\n}\n.comm_shell_model .comm_content_body{\n    display: flex;\n    flex-direction: row;\n    flex-wrap: wrap;\n    overflow:auto;\n    padding: 10px;\n    height:calc(95%);\n    overflow:auto;\n    margin-top:20px;\n}\n.comm_shell_model .comm_content_body::-webkit-scrollbar {\n    width: 4px;     \n    height: 4px;\n}\n.comm_shell_model .comm_content_body::-webkit-scrollbar-thumb {\n    border-radius: 5px;\n    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\n    background: rgba(0,0,0,0.6);\n}\n.comm_shell_model .comm_content_body::-webkit-scrollbar-track {\n    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\n    border-radius: 0;\n    background: rgba(0,0,0,.1);\n}\n.comm_shell_model .com_content_list{\n    background:#000;\n}\n\n.xterm.fullscreen {\n    position: fixed!important;\n    top: 0!important;\n    bottom: 0!important;\n    left: 0!important;\n    right: 0!important;\n    width: auto!important;\n    height: auto!important;\n    z-index: 255!important;\n    padding:10px!important;\n}\n\n/* element-ui-common */\n.el-button{\n    padding: 9px 20px!important;\n    border-radius: 0px!important;\n}\n.el-table td, .el-table th {\n    padding: 8px 0!important;\n}\n.el-button--medium {\n    padding: 8px 10px!important;\n    font-size:12px!important;\n}\n.el-button--small, .el-button--small.is-round {\n    padding: 6px 10px!important;\n}\n.el-input__inner {\n    height: 35px!important;\n    line-height: 35px!important;\n    border-radius:0px!important;\n}\n.el-input__icon{\n    line-height: 35px!important;\n}\n\n"
  },
  {
    "path": "app/public/css/home.css",
    "content": ""
  },
  {
    "path": "app/public/iconfont/iconfont.css",
    "content": "@font-face {font-family: \"iconfont\";\n  src: url('iconfont.eot?t=1551324590500'); /* IE9 */\n  src: url('iconfont.eot?t=1551324590500#iefix') format('embedded-opentype'), /* IE6-IE8 */\n  url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAMEAAsAAAAABtgAAAK4AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCcAqBZIFrATYCJAMICwYABCAFhG0HLxsQBhHVk6NkPwvsZuoVJmhyDRpllUYjUD422PchiFCXzUrri0JYJU+Msv2AXAFw/TUhV8BlOtrcrLpGRSiW5Dz4X56Rjk0IS31baHSmCoCLQ9VMoIDMB0mc5tyUC2A4QAEca9FoC1ZvQQrgHcYueIH7IUAKFaiHdBv6eRyKPUwAmWWnjscVHCjKwg4h0TBXkZ0EOLPJPBawI/p9+SuaiANDYLGzBk3pPolOPwqcKLuyfCWjAgTM5XQAWwQWqAcoiG8MDMAiDGdJoVSqAIq6f1BeHqiQo/7zkAR2ahoAhZDxTEyP9ahFBgDl2UNdgGUYq18QpKVluLzxb39E737Gb76Hr7+teP3av3kTvnsXv327PIrbdpC49YTeh2NSojFMLZw1LZJISmoowjiK4hF9h/swLP9/UKikHxwZVPI5WVb64UtXtKREIfcKpUEO/iZ6asjWbb3Kuhwa/2LquSNxxTAcUa/FdZr/YuO/2oVPrut8sX3bicV37o6eMXBxpy17505cvqzzrKZT9vaZfnxE9qlJ9cPtM5r1PXjqP0Pg3DMfjc1sV+xSFYD3nUejAeUrFbsScHdiFPijzIENarBtpVGN2RgxdCG407IUUoBysCWHr1Ml+bevE1yBycCQRBFYHNWQiq0HAak0gwSOjpBCXXouTiWXBVhEk4E6rAMgZHEMDBlcA0sWT5CK/QIBBfyHBFliIYVIctdMpYZgVnYlnBhXmB+wS07KVbOM01+ojzBRnh3hf0i7dUNbNeXwiAlpiQn7qTtmBYpyhAEchiFkKJQdLlwZ5tLXtWp6UrXkKCRdCSfGFeYH7JKTClqzrPz8C/URJuqoq1P/kHbbObRVMwA5CtKgukd5ZD91x6xAUY4wgF4YQobS3MvhwpWZ4Ct97TRTQwXV9qL4d9uAFGypEaeLJZhkN3sIAQ==') format('woff2'),\n  url('iconfont.woff?t=1551324590500') format('woff'),\n  url('iconfont.ttf?t=1551324590500') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */\n  url('iconfont.svg?t=1551324590500#iconfont') format('svg'); /* iOS 4.1- */\n}\n\n.iconfont {\n  font-family: \"iconfont\" !important;\n  font-size: 16px;\n  font-style: normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-diqiu:before {\n  content: \"\\e616\";\n}\n\n"
  },
  {
    "path": "app/public/js/config.js",
    "content": "\n'use strict';\n\nconst config = {\n    // 登陆页面\n    loginUrl: '/login',\n\n    // 登陆成功后需要跳转到的页面\n    homeUrl: '/',\n\n    // 根接口\n    baseApi: '/',\n\n    // ajax 请求超时时间\n    ajaxtimeout: 15000,\n\n    // 发送验证码时间间隔\n    msgTime: 60,\n\n    // 七牛图片根地址\n    imgBaseUrl: 'http://ormfcl92t.bkt.clouddn.com/',\n\n    // 隐藏显示时间\n    containerShowTime: 10,\n\n    // pagesize 分页数量\n    pageSize: 50,\n};\n\nwindow.config = config; // eslint-disable-line\nwindow.baseUrl = \"\"//\"http://test-pc.juxingyi.com\"\n"
  },
  {
    "path": "app/public/js/util.js",
    "content": "/* eslint-disable */\n\n'use strict';\n\n// 时间格式化\nif (!new Date().format) {\n    Date.prototype.format = function (fmt) { //author: meizz \n        var o = {\n            \"M+\": this.getMonth() + 1, //月份 \n            \"d+\": this.getDate(), //日 \n            \"h+\": this.getHours(), //小时 \n            \"H+\": this.getHours() > 12 ? this.getHours() - 12 : this.getHours(),\n            \"m+\": this.getMinutes(), //分 \n            \"s+\": this.getSeconds(), //秒 \n            \"q+\": Math.floor((this.getMonth() + 3) / 3), //季度 \n            \"S\": this.getMilliseconds() //毫秒 \n        };\n        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + \"\").substr(4 - RegExp.$1.length));\n        for (var k in o)\n            if (new RegExp(\"(\" + k + \")\").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((\"00\" + o[k]).substr((\"\" + o[k]).length)));\n        return fmt;\n    }\n};\n\nlet XTEAMLIST = [];\nlet MODELTYPE = '';\nlet SOCKET = null;\nlet CONSOLEXTEAM = [];\n\n// util 公共对象函数\nclass utilfn {\n    // 初始化对象\n    constructor() {\n        this.win = window.top;\n        this.UA = navigator.userAgent;\n        this.isPC = this.UA.indexOf('Windows NT') > -1;\n        this.isAndroid = this.UA.indexOf('Android') > -1;\n        this.isIos = this.UA.indexOf('Mac OS X') > -1;\n        this.isIphone = this.UA.indexOf('iPhone;') > -1;\n        this.isIpad = this.UA.indexOf('iPad;') > -1;\n        this.isIE7 = this.UA.indexOf('MSIE 7.0;') > -1;\n        this.isIE8 = this.UA.indexOf('MSIE 8.0;') > -1;\n        this.isIE9 = this.UA.indexOf('MSIE 9.0;') > -1;\n        this.isIE10 = this.UA.indexOf('MSIE 10.0;') > -1;\n        this.isIE11 = this.UA.indexOf('Trident') > -1;\n    };\n\n    /*封装的ajax函数\n        *type           \t类型  get|post\n        *url            \tapi地址\n        *data           \t请求的json数据\n        *noLoading  \t\tajax执行时是否显示遮罩\n        *nohideloading  \tajax执行完成之后是否隐藏遮罩\n        *notimeout\t\t\t是否有请求超时\n        *complete       \tajax完成后执行（失败成功后都会执行）\n        *beforeSend        \t请求发送前执行\n        *success        \t成功之后执行\n        *error          \t失败之后执行\n        *goingError     \t是否执行自定义error回调\n        *timeout        \tajax超时时间\n        *isGoingLogin   \t是否跳转到登录界面\n        */\n    ajax(json) {\n        let This = this;\n        let noError = true;\n        let url = null;\n        let asyncVal = typeof(json.async) == 'boolean' ? json.async : true;\n        if (!json.nohideloading) {\n            This.showLoading();\n        };\n\n        //是否有请求超时\n        if (!json.notimeout) {\n            var timeout = setTimeout(function() {\n                This.hideLoading();\n                // 请求超时\n                noError = false;\n                asyncVal && popup.alert({\n                    type: 'msg',\n                    title: '您的网络太慢了哦,请刷新重试!'\n                });\n            }, json.timeout || config.ajaxtimeout);\n        }\n        // 增加时间戳参数\n        if (json.url.indexOf('?') != -1) {\n            url = json.url + '&_=' + this.time();\n        } else {\n            url = json.url + '?_=' + this.time();\n        };\n\n        return $.ajax({\n            type: json.type || \"post\",\n            url: url,\n            data: json.data || \"\",\n            dataType: \"json\",\n            async: asyncVal,\n            beforeSend: function(xhr) {\n                xhr.setRequestHeader(\"x-csrf-token\", $.cookie('csrfToken')||'');\n                json.beforeSend && json.beforeSend(xhr);\n            },\n            success: function(data) {\n                if (!json.nohideloading) {\n                    This.hideLoading();\n                };\n                clearTimeout(timeout);\n                if (typeof(data) == 'string') {\n                    This.error(JSON.parse(data), json);\n                } else {\n                    This.error(data, json);\n                }\n            },\n            complete: function(XMLHttpRequest) {\n                if (!json.nohideloading) {\n                    This.hideLoading();\n                };\n                clearTimeout(timeout);\n                if (json.complete) {\n                    json.complete(XMLHttpRequest);\n                }\n            },\n            error: function(XMLHttpRequest) {\n                This.hideLoading();\n                clearTimeout(timeout);\n                if (noError) {\n                    This._error(XMLHttpRequest, json);\n                };\n            }\n        });\n    };\n\n    //error 处理函数\n    error(data, json) {\n        //判断code 并处理\n        var dataCode = parseInt(data.code);\n        if (!json.isGoingLogin && dataCode == 1004) {\n            // 判断app或者web\n            if (window.location.href.indexOf(config.loginUrl) == -1) {\n                $('#main').html('');\n                function login(){\n                    sessionStorage.setItem(\"weixin-url\", window.location.href); //记录没有登陆前的访问页面\n                    location.href = config.loginUrl + '?redirecturl=' + encodeURIComponent(location.href);\n                }\n                popup.confirm({ maskHide: false, title: data.desc || '登录失败,请重新登录!', yes: () => { login() }, no: () => { login() } });\n            } else {\n                popup.alert({\n                    type: 'msg',\n                    title: '用户未登陆,请登录!'\n                });\n            }\n        } else {\n            switch (dataCode) {\n                case 1000:\n                    json.success && json.success(data);\n                    break;\n                default:\n                    if (json.goingError) {\n                        //走error回调\n                        json.error && json.error(data);\n                    } else {\n                        //直接弹出错误信息\n                        popup.alert({\n                            type: 'msg',\n                            title: data.desc\n                        });\n                    };\n            }\n        };\n    }\n\n    // _error 处理函数\n    _error(XMLHttpRequest, json) {\n        this.hideLoading();\n        if (json.code) {\n            json.error(JSON.parse(XMLHttpRequest.responseText));\n        } else {\n            switch (XMLHttpRequest.status) {\n                case 401:\n                    if (window.location.href.indexOf(config.loginUrl) == -1) {\n                        sessionStorage.setItem(\"weixin-url\", window.location.href); //记录没有登陆前的访问页面\n                        window.location.href = config.loginUrl;\n                    } else {\n                        popup.alert({\n                            type: 'msg',\n                            title: \"你需要登录哦\"\n                        });\n                    };\n                    break;\n                case 400:\n                    popup.alert({\n                        type: 'msg',\n                        title: \"您的请求不合法呢\"\n                    });\n                    break;\n                case 404:\n                    popup.alert({\n                        type: 'msg',\n                        title: \"访问的地址可能不存在哦\"\n                    });\n                    break;\n                case 500:\n                case 502:\n                    popup.alert({\n                        type: 'msg',\n                        title: \"服务器内部错误\"\n                    });\n                    break;\n                    // default:\n                    // \tpopup.alert({type:'msg',title:\"未知错误。程序员欧巴正在赶来修改哦\"});\n            }\n        }\n    }\n\n    // 获取当前时间毫秒\n    time() {\n        return new Date().getTime();\n    }\n\n    /*根据参数生成常用的正则表达式\n        *string    type 生成的正则表达式类型\n        *array     numArr 生成正则的条件数组 例如:[6,16] 也可省略\n        */\n    regCombination(type, numArr) {\n        var reg = \"\";\n        switch (type) {\n            case \"*\": //\"*\":\"不能为空！\"\n                if (numArr) {\n                    reg = new RegExp(\"^[\\\\w\\\\W]{\" + numArr[0] + \",\" + numArr[1] + \"}$\");\n                } else {\n                    reg = new RegExp(\"^[\\\\w\\\\W]+$\");\n                }\n                break;\n            case \"n\": //\"number\":\"请填写数字！\n                if (numArr) {\n                    reg = new RegExp(\"^\\\\d{\" + numArr[0] + \",\" + numArr[1] + \"}$\");\n                } else {\n                    reg = new RegExp(\"^\\\\d+$\");\n                }\n                break;\n            case \"s\": //\"s\":\"不能输入特殊字符！\"\n                if (numArr) {\n                    reg = new RegExp(\"^[\\\\u4E00-\\\\u9FA5\\\\uf900-\\\\ufa2d\\\\w\\\\.\\\\s]{\" + numArr[0] + \",\" + numArr[1] + \"}$\");\n                } else {\n                    reg = new RegExp(\"^[\\\\u4E00-\\\\u9FA5\\\\uf900-\\\\ufa2d\\\\w\\\\.\\\\s]+$\");\n                }\n                break;\n            case \"c\": //\"z\":\"中文验证\"\n                reg = new RegExp(\"^[\\\\u4E00-\\\\u9FA5\\\\uf900-\\\\ufa2d]{\" + numArr[0] + \",\" + numArr[1] + \"}$\");\n                break;\n            case \"p\": //\"p\":\"邮政编码！\n                reg = new RegExp(\"^[0-9]{6}$\");\n                break;\n            case \"m\": //\"m\":\"写手机号码！\"\n                reg = new RegExp(\"^13[0-9]{9}$|14[0-9]{9}$|15[0-9]{9}$|17[0-9]{9}$|18[0-9]{9}$\");\n                break;\n            case \"e\": //\"e\":\"邮箱地址格式\n                reg = new RegExp(\"^\\\\w+([-+.']\\\\w+)*@\\\\w+([-.]\\\\w+)*\\\\.\\\\w+([-.]\\\\w+)*$\");\n                break;\n            case \"id\": //\"id\":验证身份证\n                reg = new RegExp(\"^\\\\d{17}[\\\\dXx]|\\\\d{14}[\\\\dXx]$\");\n                break;\n            case \"money\": //钱\n                reg = new RegExp(\"^[\\\\d\\\\.]+$\");\n                break;\n            case \"url\": //\"url\":\"网址\"\n                reg = new RegExp(\"^(\\\\w+:\\\\/\\\\/)?\\\\w+(\\\\.\\\\w+)+.*$\");\n                break;\n            case \"u\": //\n                reg = new RegExp(\"^[A-Z\\\\d]+$\");\n                break;\n            case \"numLimitTo2\": //保留2位小数点正整数\n                reg = new RegExp(\"^-{0,0}\\\\d+(.\\\\d{0,2})?$\");\n                break;\n            case \"spec\": //校验特殊字符\n                reg = new RegExp(\"[`~!@#$^&*()=|{}':;',\\\\[\\\\].<>/?~！@#￥……&*（）——|{}【】‘；：”“'。，、？%+_]\");\n                break;\n        }\n        return reg;\n    }\n\n\t/*extent json函数\n\t *json1  原始数据\n\t *json2  新数据\n\t */\n\textend(json1, json2) {\n\t\tvar newJson = json1;\n\t\tfor (var j in json2) {\n\t\t\tnewJson[j] = json2[j];\n\t\t}\n\t\treturn newJson;\n\t}\n\n\t//showLoading\n\tshowLoading() {\n\t\t$('#loading').stop().show();\n\t}\n\n\t//hideLoading\n\thideLoading() {\n\t\t$('#loading').stop().hide();\n    }\n    \n    /*生成随机字符串*/\n    randomString(len) {\n        len = len || 15;\n        var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';\n        var maxPos = $chars.length;\n        var pwd = '';\n        for (let i = 0; i < len; i++) {\n            pwd += $chars.charAt(Math.floor(Math.random() * maxPos));\n        }\n        return pwd;\n    }\n\t\n\t/*获取url hash*/\n\tgetQueryString(name, hash) {\n\t\tvar reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');\n\t\tif (hash) {\n\t\t\tif (!window.location.hash) {\n\t\t\t\treturn '';\n\t\t\t};\n\t\t\tvar r = decodeURIComponent(window.location.hash).substr(1).match(reg);\n\t\t} else {\n\t\t\tvar r = decodeURIComponent(window.location.search).substr(1).match(reg);\n\t\t}\n\t\tif (r != null) {\n\t\t\treturn r[2];\n\t\t}\n\t\treturn null;\n\t}\n\n\t/*获取 storage 缓存数据\n\t * type  类型   local：localStorage   session：sessionStorage\n\t * name  缓存数据name名\n\t */\n\tgetStorage(type, name) {\n\t\tvar type = type || 'local';\n\t\tif (type == 'local') {\n\t\t\tvar result = localStorage.getItem(name) ? localStorage.getItem(name) : \"\";\n\t\t} else if (type == 'session') {\n\t\t\tvar result = sessionStorage.getItem(name) ? sessionStorage.getItem(name) : \"\";\n\t\t}\n\t\treturn result;\n\t}\n\n\t/*设置 storage 缓存数据\n\t *type  类型   local：localStorage   session：sessionStorage\n\t *name  缓存数据name名\n\t *content  缓存的数据内容\n\t */\n\tsetStorage(type, name, content) {\n\t\tvar type = type || 'local';\n\t\tvar data = content;\n\t\tif (typeof(data) == 'object') {\n\t\t\tdata = JSON.stringify(content)\n\t\t};\n\t\tif (type == 'local') {\n\t\t\tlocalStorage.setItem(name, data);\n\t\t} else if (type == 'session') {\n\t\t\tsessionStorage.setItem(name, data);\n\t\t}\n\t}\n\n\t/*vue获得checkbox的值*/\n\tgetCheckBoxVal(arr){\n\t\tlet result=\"\"\n\t\tif(!arr.length) return result;\n\t\tarr.forEach((item)=>{\n\t\t\tif(item.checked){\n\t\t\t\tresult+=item.value+','\n\t\t\t}\n\t\t})\n\t\treturn result.slice(0,-1);\n\t}\n\n\t/*vue转换checkbox的值*/\n\tsetCheckBoxVal(arr,str){\n\t\tlet copyarr = arr;\n\t\tif(!str) return copyarr;\n\t\tlet newArr=str.split(',')\n\t\tcopyarr.forEach((itemp)=>{\n\t\t\tnewArr.forEach((item)=>{\n\t\t\t\tif(itemp.value == item){\n\t\t\t\t\titemp.checked=true;\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t\treturn copyarr;\n\t}\n\n\tgoBack(){\n\t\twindow.history.go(-1);\n    }\n    \n    // 获得查询时间\n    getSearchTime() {\n        let json = {\n            beginTime: '',\n            endTime: ''\n        }\n        let selecttimes = util.getStorage('local', 'userselectTime') || 60000\n        selecttimes = selecttimes * 1\n        if (selecttimes) {\n            let endTime = new Date().getTime()\n            let beginTime = endTime - selecttimes\n            json.beginTime = new Date(beginTime).format('yyyy/MM/dd hh:mm:ss')\n            json.endTime = new Date(endTime).format('yyyy/MM/dd hh:mm:ss')\n        }\n\n        return json\n    }\n\n    // 应用构建socket、xtram\n    startSocketXteam(json = {}) {\n        const assetsList = json.assetsList || [];\n        const taskItem = json.taskItem || {};\n        const buildType = json.buildType || '';\n        const startType = json.startType || 'new';\n        if (!Array.isArray(assetsList) && !assetsList.length) return;\n\n        const buildLogs = {}\n\n        const result = [];\n        if (startType === 'switch') {\n            for (let i = 0; i < XTEAMLIST.length; i++) { XTEAMLIST[i] && XTEAMLIST[i].destroy() }\n            XTEAMLIST = [];\n        } else if (startType === 'agein') {\n            return {\n                xteamList: XTEAMLIST,\n                socket: SOCKET,\n            };\n        } else if (startType === 'new') {\n            XTEAMLIST = [];\n        }\n\n        // socket\n        const socket = SOCKET = io.connect('/');\n        Terminal.applyAddon(fit);\n        Terminal.applyAddon(fullscreen); \n        Terminal.applyAddon(attach);\n        // list\n        assetsList.forEach((item, index) => {\n            // 先执行 xteam\n            let xteam = new Terminal({\n                cursorBlink:true,\n                fontSize:14,\n                fontFamily: '\"Monaco\", \"Consolas\", \"monospace\"',\n            });\n            xteam.open(document.getElementById('terminal'+index));\n            xteam.focus()\n            xteam.fit()\n            xteam.attach(socket)\n\n            const data = 'process_data_' + index;\n            const resize = 'process_resize_' + index;\n            const end = 'process_end_' + index;\n            const close = 'process_close_' + index;\n\n            xteam.on('data', function (res) {\n                socket.emit(data, res)\n            })\n            \n            // socket\n            socket.on(data, function (res) {\n                xteam.write(res);\n            });\n\n            // end\n            socket.on(end, function (res) {\n                json.callback && json.callback(res)\n            });\n\n            XTEAMLIST.push(xteam);\n\n            result.push({\n                assitsItem:{\n                    name: item.name,\n                    lan_ip: item.lan_ip,\n                    outer_ip: item.outer_ip,\n                    port: item.port,\n                    user: item.user,\n                    password: item.password,\n                },\n                data, resize, close, end\n            })\n        })\n        const userMsg = util.getStorage('local', 'userMsg');\n        const user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n        socket.emit('socket', { \n            taskItem,\n            data: result, buildType,\n            id: json.id || '',\n            user_name,\n            cols: XTEAMLIST[0].cols,\n            rows: XTEAMLIST[0].rows} || 'begin'\n        );\n\n        window.addEventListener('resize', this.resizeScreen.bind(this, XTEAMLIST, socket), false)\n\n        socket.on('close', msg => { close(msg); });\n        socket.on('disconnect', msg => { close(msg); });\n        socket.on('disconnecting', () => { close(); });\n        socket.on('error', (err) => { close(err); });\n\n        function close(msg){\n            XTEAMLIST.forEach(item => {\n                item.write(msg || '服务器close,请刷新重试。');\n            })\n        }\n\n        return {\n            xteamList: XTEAMLIST,\n            socket,\n        };\n        // xteam.clear()  xteam.reset()  xteam.destroy()\n    }\n\n    resizeScreen(XTEAMLIST, socket) {\n        for (let i = 0; i < XTEAMLIST.length; i++) { XTEAMLIST[i] && XTEAMLIST[i].fit() }\n        socket.emit('resize', { cols: XTEAMLIST[0].cols, rows: XTEAMLIST[0].rows })\n    }\n\n    // 设置xteam窗口大小\n    setSocketXteam(type, xteamlist = [], socket) {\n        if (MODELTYPE === type) return;\n        MODELTYPE = type;\n        const comm_mocel = document.querySelector('.comm_shell_model_content');\n        const terminal_item = document.querySelectorAll('.terminal');\n        const content_list = document.querySelectorAll('.com_content_list');\n        if(type === 1){\n            // 放大\n            comm_mocel.style.width = 'calc(100% - 30px)'\n            comm_mocel.style.height = 'calc(100% - 20px)'\n            comm_mocel.style.marginLeft = '15px'\n            comm_mocel.style.marginTop = '10px'\n        }else if(type === 2){\n            // 缩小\n            comm_mocel.style.width = 'calc(70%)'\n            comm_mocel.style.height = 'calc(80%)'\n            comm_mocel.style.marginLeft = 'calc(15%)'\n            comm_mocel.style.marginTop = 'calc(5%)'\n        } else if (type === 3){\n            for (let i = 0; i < terminal_item.length;i++) {\n                terminal_item[i].style.top = '0'\n            }\n            for (let i = 0; i < content_list.length; i++) {\n                content_list[i].style.marginTop = '10px'\n                content_list[i].style.position = 'relative'\n                content_list[i].style.width = 'calc(49%)'\n                if (i % 2 === 0) content_list[i].style.marginRight = '2%'\n                content_list[i].style.height = '500px'\n            }\n            comm_mocel.style.background = '#3e3e3e'\n            comm_mocel.style.overflow = 'auto'\n        } else if (type === 4) {\n            for (let i = 0; i < terminal_item.length; i++) {\n                terminal_item[i].style.top = '10px'\n            }\n            for (let i = 0; i < content_list.length; i++) {\n                content_list[i].style.position = 'static'\n            }\n            comm_mocel.style.background = '#000'\n            comm_mocel.style.overflow = 'hidden'\n        }\n        setTimeout(()=>{\n            this.resizeScreen(xteamlist, socket)\n        })\n    }\n\n    /* socket.io实现\n    *  fn 信息回调触发\n    *  query socket 参数\n    */\n    socket(json = {}) {\n        const socket = io.connect('/');\n        socket.on('close', msg => { close() });\n        socket.on('disconnect', msg => { close() });\n        socket.on('disconnecting', () => { close() });\n        socket.on('error', (err) => { close() });\n\n        function close(msg){\n            CONSOLEXTEAM.forEach(item => {\n                item.write(msg || '服务器close.');\n            })\n        }\n\n        return socket;\n    }\n\n    /* xteam 实现\n    * id\n    */\n    xteam(json = {}){\n        const { socket, index, assetsItem} = json;\n        const result = [];\n\n        const data = 'console_data_' + index;\n        const resize = 'console_resize_' + index;\n        const close = 'console_close_' + index;\n        const end = 'console_end_' + index;\n\n        Terminal.applyAddon(fit);\n        Terminal.applyAddon(fullscreen); \n        Terminal.applyAddon(attach);\n\n        let xteam = new Terminal({\n            cursorBlink: true,\n            fontSize: 14,\n            fontFamily: '\"Monaco\", \"Consolas\", \"monospace\"',\n        });\n        xteam.open(document.getElementById('console_terminal_' + index));\n        xteam.focus()\n        xteam.fit()\n        xteam.attach(socket)\n\n        xteam.on('data', function (res) {\n            socket.emit(data, res)\n        })\n\n        xteam.write(`开始连接到 ${assetsItem.user}@${assetsItem.name}-${assetsItem.outer_ip}-${assetsItem.lan_ip}\\r\\n`)\n\n        // socket\n        socket.on(data, function (res) {\n            xteam.write(res);\n        });\n\n        result.push({\n            assitsItem: {\n                name: assetsItem.name,\n                lan_ip: assetsItem.lan_ip,\n                outer_ip: assetsItem.outer_ip,\n                port: assetsItem.port,\n                user: assetsItem.user,\n                password: assetsItem.password,\n            },\n            data, resize, close, end\n        })\n\n        socket.emit('socket', { data: result, buildType: 'sshonline', cols: xteam.cols, rows: xteam.rows } || 'begin');\n\n        window.addEventListener('resize', this.resizeScreen.bind(this, [xteam], socket), false)\n        this.resizeScreen([xteam], socket)\n\n        CONSOLEXTEAM.push(xteam)\n\n        return xteam;\n    }\n}\n\n//初始化util对象\nwindow.util = new utilfn();\n\n\n\n"
  },
  {
    "path": "app/public/js/vue-components.js",
    "content": "/* eslint-disable */\n\nlet Component = {\n    commonsearch:{\n        template: `<div class=\"component_search mr20\">\n            <a href=\"/selectype\"><button class=\"btn btn-main\">+添加应用</button></a>\n            <div class=\"select\">\n                <span class=\"times\"><span class=\"iconfont\">&#xe60b;</span>{{timeText}}<span class=\"iconfont\">&#xe63b;</span></span>\n                <div class=\"select-time\">\n                    <li data-time=\"60000\" data-text=\"最近1分钟\">1分钟</li>\n                    <li data-time=\"300000\" data-text=\"最近5分钟\">5分钟</li>\n                    <li data-time=\"600000\" data-text=\"最近10分钟\">10分钟</li>\n                    <li data-time=\"1800000\" data-text=\"最近30分钟\">30分钟</li>\n                    <li data-time=\"3600000\" data-text=\"最近1小时\">1小时</li>\n                    <li data-time=\"21600000\" data-text=\"最近6小时\">6小时</li>\n                    <li data-time=\"43200000\" data-text=\"最近12小时\">12小时</li>\n                    <li data-time=\"86400000\" data-text=\"最近1天\">1天</li>\n                    <button @click=\"timeSure\" class=\"btn\">确定</button>\n                </div>\n            </div>\n        </div>`,\n        props:{\n            done:{\n                type:Function,\n                default:()=>{}\n            },\n        },\n        data:function(){\n            return{\n                timeText:'全部'\n            }\n        },\n        mounted(){\n            let _this=this;\n            // 添加active样式\n            let selecttimes = util.getStorage('local', 'userselectTime') || 60000\n            let objs = $('.select-time li')\n            for(let i=0,len=objs.length;i<len;i++){\n                let times = $(objs[i]).attr('data-time')\n                let text = $(objs[i]).attr('data-text')\n                if(times == selecttimes){\n                    _this.timeText = text\n                    $(objs[i]).addClass('active')\n                }\n            }\n            // active样式\n            $('.times').on('click',(e) => {\n                e.stopPropagation();\n                $('.select-time').show();\n            });\n            $(document).on('click',function(e){\n                $('.select-time').hide();\n            });\n            $('.select-time').click(function(e){\n                e.stopPropagation();\n            })\n            $('.select-time li').on('click',function(e){\n                $('.select-time li').removeClass('active')\n                $(this).addClass('active')\n                let time = $(this).attr('data-time')\n                let text = $(this).attr('data-text')\n                _this.timeText = text\n                util.setStorage('local','userselectTime',time)\n            })\n        },\n        methods:{\n            timeSure(){\n                $('.select-time').hide();\n                this.done&&this.done()\n            }\n        },\n    }\n}\nfor(let n in Component){\n    Vue.component(n, Component[n])\n}\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "app/public/js/vue-filters.js",
    "content": "/* eslint-disable */\n// 时间格式化\nif(!new Date().format){\n    Date.prototype.format = function (fmt) { //author: meizz \n        var o = {\n            \"M+\": this.getMonth() + 1, //月份 \n            \"d+\": this.getDate(), //日 \n            \"h+\": this.getHours(), //小时 \n            \"H+\":this.getHours()>12?this.getHours()-12:this.getHours(),\n            \"m+\": this.getMinutes(), //分 \n            \"s+\": this.getSeconds(), //秒 \n            \"q+\": Math.floor((this.getMonth() + 3) / 3), //季度 \n            \"S\": this.getMilliseconds() //毫秒 \n        };\n        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + \"\").substr(4 - RegExp.$1.length));\n        for (var k in o)\n        if (new RegExp(\"(\" + k + \")\").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((\"00\" + o[k]).substr((\"\" + o[k]).length)));\n        return fmt;\n    }\n};\n\nlet Filter = {\n    // 图片地址过滤器\n    imgBaseUrl:function(img) {\n        if (!img) return '../images/index/bg-0.png';\n        if (img.indexOf('http:') !== -1 || img.indexOf('HTTP:') !== -1 || img.indexOf('https:') !== -1 || img.indexOf('HTTPS:') !== -1) {\n            return img + '?imageslim';\n        } else {\n            return config.imgBaseUrl + img + '?imageslim';\n        }\n    },\n    toFixed(val,type=false){\n        val = parseFloat(val)\n        if(type){\n            val = val/1000\n            return val>0?val.toFixed(3)+' s':val.toFixed(2);\n        }else{\n            return val.toFixed(2)+' ms';\n        }\n    },\n    toSize(val){\n        val=val*1\n        if(val>=1024){\n            return (val/1024).toFixed(2)+' KB'\n        }else if(val>0){\n            return val.toFixed(2)+' B'\n        }else{\n            return 0\n        }\n    },\n    // 时间过滤器\n    date(value, gengefu, full) {\n        if (!value) return;\n        let ty = gengefu || '-';\n        if (full) {\n            return new Date(value).format('yyyy' + ty + 'MM' + ty + 'dd hh:mm:ss');\n        } else {\n            return new Date(value).format('yyyy' + ty + 'MM' + ty + 'dd');\n        };\n    },\n    //limitTo过滤器\n    limitTo(value, num) {\n        if (!value) return;\n        var text = \"\";\n        if (value.length < num) {\n            text = value;\n        } else {\n            text = value.substring(0, num) + '···';\n        }\n        return text;\n    },\n    // 应用类型过滤器\n    systemType(val) {\n        let result = '';\n        switch (val) {\n            case 'web':\n                result = 'WEB浏览器';\n                break;\n            case 'wx':\n                result = '微信小程序';\n                break;    \n        }\n        return result;\n    },\n    // 流量单位\n    flow(val = 0) {\n        let result = 0;\n        let value = val;\n        let index = 0;\n        while (value >= 1024) {\n            value = value / 1024\n            index++;\n        }\n        value = value.toFixed(2);\n        if (index >= 4) {\n            value = value + 'T'\n        } else if (index >= 3) {\n            value = value + 'G'\n        } else if (index >= 2) {\n            value = value + 'M'\n        } else if (index >= 1) {\n            value = value + 'KB'\n        } else {\n            value = value + 'B'\n        }\n        return value;\n    },\n}\n\nwindow.Filter = {};\n\nfor(let n in Filter){\n    window['Filter'][n] = Filter[n];\n}\n"
  },
  {
    "path": "app/public/lib/bootstrap/css/bootstrap-theme.css",
    "content": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n  text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n  text-shadow: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n}\n.btn-default {\n  text-shadow: 0 1px 0 #fff;\n  background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n  background-image:      -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));\n  background-image:         linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #dbdbdb;\n  border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n  background-color: #e0e0e0;\n  background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n  background-color: #e0e0e0;\n  border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n  background-color: #e0e0e0;\n  background-image: none;\n}\n.btn-primary {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n  background-color: #265a88;\n  background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n  background-color: #265a88;\n  border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n  background-color: #265a88;\n  background-image: none;\n}\n.btn-success {\n  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n  background-image:      -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));\n  background-image:         linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n  background-color: #419641;\n  background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n  background-color: #419641;\n  border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n  background-color: #419641;\n  background-image: none;\n}\n.btn-info {\n  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n  background-image:      -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));\n  background-image:         linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n  background-color: #2aabd2;\n  background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n  background-color: #2aabd2;\n  border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n  background-color: #2aabd2;\n  background-image: none;\n}\n.btn-warning {\n  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n  background-image:      -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));\n  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n  background-color: #eb9316;\n  background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n  background-color: #eb9316;\n  border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n  background-color: #eb9316;\n  background-image: none;\n}\n.btn-danger {\n  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n  background-image:      -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));\n  background-image:         linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n  background-color: #c12e2a;\n  background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n  background-color: #c12e2a;\n  border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n  background-color: #c12e2a;\n  background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  background-color: #e8e8e8;\n  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n  background-repeat: repeat-x;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  background-color: #2e6da4;\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n  background-repeat: repeat-x;\n}\n.navbar-default {\n  background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);\n  background-image:      -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));\n  background-image:         linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n  background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n  background-image:      -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));\n  background-image:         linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n  background-repeat: repeat-x;\n  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n  text-shadow: 0 1px 0 rgba(255, 255, 255, .25);\n}\n.navbar-inverse {\n  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n  background-image:      -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));\n  background-image:         linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n  background-repeat: repeat-x;\n  border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n  background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n  background-image:      -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));\n  background-image:         linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n  background-repeat: repeat-x;\n  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);\n          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n  text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  border-radius: 0;\n}\n@media (max-width: 767px) {\n  .navbar .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n    background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n    background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n    background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n    background-repeat: repeat-x;\n  }\n}\n.alert {\n  text-shadow: 0 1px 0 rgba(255, 255, 255, .2);\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);\n}\n.alert-success {\n  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n  background-image:      -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));\n  background-image:         linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #b2dba1;\n}\n.alert-info {\n  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n  background-image:      -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));\n  background-image:         linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #9acfea;\n}\n.alert-warning {\n  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n  background-image:      -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));\n  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #f5e79e;\n}\n.alert-danger {\n  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n  background-image:      -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));\n  background-image:         linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #dca7a7;\n}\n.progress {\n  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n  background-image:      -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));\n  background-image:         linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-success {\n  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n  background-image:      -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));\n  background-image:         linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-info {\n  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n  background-image:      -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));\n  background-image:         linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-warning {\n  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n  background-image:      -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));\n  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-danger {\n  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n  background-image:      -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));\n  background-image:         linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n  background-repeat: repeat-x;\n}\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.list-group {\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  text-shadow: 0 -1px 0 #286090;\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n  text-shadow: none;\n}\n.panel {\n  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);\n          box-shadow: 0 1px 2px rgba(0, 0, 0, .05);\n}\n.panel-default > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image:      -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-primary > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image:      -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n  background-image:         linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-success > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n  background-image:      -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));\n  background-image:         linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-info > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n  background-image:      -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));\n  background-image:         linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-warning > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n  background-image:      -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));\n  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n  background-repeat: repeat-x;\n}\n.panel-danger > .panel-heading {\n  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n  background-image:      -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));\n  background-image:         linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n  background-repeat: repeat-x;\n}\n.well {\n  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n  background-image:      -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n  background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));\n  background-image:         linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n  background-repeat: repeat-x;\n  border-color: #dcdcdc;\n  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */\n"
  },
  {
    "path": "app/public/lib/bootstrap/css/bootstrap.css",
    "content": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n  font-family: sans-serif;\n  -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block;\n  vertical-align: baseline;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden],\ntemplate {\n  display: none;\n}\na {\n  background-color: transparent;\n}\na:active,\na:hover {\n  outline: 0;\n}\nabbr[title] {\n  border-bottom: 1px dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nh1 {\n  margin: .67em 0;\n  font-size: 2em;\n}\nmark {\n  color: #000;\n  background: #ff0;\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  position: relative;\n  font-size: 75%;\n  line-height: 0;\n  vertical-align: baseline;\n}\nsup {\n  top: -.5em;\n}\nsub {\n  bottom: -.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 1em 40px;\n}\nhr {\n  height: 0;\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n}\npre {\n  overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  margin: 0;\n  font: inherit;\n  color: inherit;\n}\nbutton {\n  overflow: visible;\n}\nbutton,\nselect {\n  text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  padding: 0;\n  border: 0;\n}\ninput {\n  line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n  -webkit-appearance: textfield;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nfieldset {\n  padding: .35em .625em .75em;\n  margin: 0 2px;\n  border: 1px solid #c0c0c0;\n}\nlegend {\n  padding: 0;\n  border: 0;\n}\ntextarea {\n  overflow: auto;\n}\noptgroup {\n  font-weight: bold;\n}\ntable {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\ntd,\nth {\n  padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n  *,\n  *:before,\n  *:after {\n    color: #000 !important;\n    text-shadow: none !important;\n    background: transparent !important;\n    -webkit-box-shadow: none !important;\n            box-shadow: none !important;\n  }\n  a,\n  a:visited {\n    text-decoration: underline;\n  }\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n  abbr[title]:after {\n    content: \" (\" attr(title) \")\";\n  }\n  a[href^=\"#\"]:after,\n  a[href^=\"javascript:\"]:after {\n    content: \"\";\n  }\n  pre,\n  blockquote {\n    border: 1px solid #999;\n\n    page-break-inside: avoid;\n  }\n  thead {\n    display: table-header-group;\n  }\n  tr,\n  img {\n    page-break-inside: avoid;\n  }\n  img {\n    max-width: 100% !important;\n  }\n  p,\n  h2,\n  h3 {\n    orphans: 3;\n    widows: 3;\n  }\n  h2,\n  h3 {\n    page-break-after: avoid;\n  }\n  .navbar {\n    display: none;\n  }\n  .btn > .caret,\n  .dropup > .btn > .caret {\n    border-top-color: #000 !important;\n  }\n  .label {\n    border: 1px solid #000;\n  }\n  .table {\n    border-collapse: collapse !important;\n  }\n  .table td,\n  .table th {\n    background-color: #fff !important;\n  }\n  .table-bordered th,\n  .table-bordered td {\n    border: 1px solid #ddd !important;\n  }\n}\n@font-face {\n  font-family: 'Glyphicons Halflings';\n  src: url('../fonts/glyphicons-halflings-regular.eot');\n  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: 'Glyphicons Halflings';\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n  content: \"\\002a\";\n}\n.glyphicon-plus:before {\n  content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n  content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n  content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n  content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n  content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n  content: \"\\270f\";\n}\n.glyphicon-glass:before {\n  content: \"\\e001\";\n}\n.glyphicon-music:before {\n  content: \"\\e002\";\n}\n.glyphicon-search:before {\n  content: \"\\e003\";\n}\n.glyphicon-heart:before {\n  content: \"\\e005\";\n}\n.glyphicon-star:before {\n  content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n  content: \"\\e007\";\n}\n.glyphicon-user:before {\n  content: \"\\e008\";\n}\n.glyphicon-film:before {\n  content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n  content: \"\\e010\";\n}\n.glyphicon-th:before {\n  content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n  content: \"\\e012\";\n}\n.glyphicon-ok:before {\n  content: \"\\e013\";\n}\n.glyphicon-remove:before {\n  content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n  content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n  content: \"\\e016\";\n}\n.glyphicon-off:before {\n  content: \"\\e017\";\n}\n.glyphicon-signal:before {\n  content: \"\\e018\";\n}\n.glyphicon-cog:before {\n  content: \"\\e019\";\n}\n.glyphicon-trash:before {\n  content: \"\\e020\";\n}\n.glyphicon-home:before {\n  content: \"\\e021\";\n}\n.glyphicon-file:before {\n  content: \"\\e022\";\n}\n.glyphicon-time:before {\n  content: \"\\e023\";\n}\n.glyphicon-road:before {\n  content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n  content: \"\\e025\";\n}\n.glyphicon-download:before {\n  content: \"\\e026\";\n}\n.glyphicon-upload:before {\n  content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n  content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n  content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n  content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n  content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n  content: \"\\e032\";\n}\n.glyphicon-lock:before {\n  content: \"\\e033\";\n}\n.glyphicon-flag:before {\n  content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n  content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n  content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n  content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n  content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n  content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n  content: \"\\e040\";\n}\n.glyphicon-tag:before {\n  content: \"\\e041\";\n}\n.glyphicon-tags:before {\n  content: \"\\e042\";\n}\n.glyphicon-book:before {\n  content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n  content: \"\\e044\";\n}\n.glyphicon-print:before {\n  content: \"\\e045\";\n}\n.glyphicon-camera:before {\n  content: \"\\e046\";\n}\n.glyphicon-font:before {\n  content: \"\\e047\";\n}\n.glyphicon-bold:before {\n  content: \"\\e048\";\n}\n.glyphicon-italic:before {\n  content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n  content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n  content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n  content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n  content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n  content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n  content: \"\\e055\";\n}\n.glyphicon-list:before {\n  content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n  content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n  content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n  content: \"\\e059\";\n}\n.glyphicon-picture:before {\n  content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n  content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n  content: \"\\e063\";\n}\n.glyphicon-tint:before {\n  content: \"\\e064\";\n}\n.glyphicon-edit:before {\n  content: \"\\e065\";\n}\n.glyphicon-share:before {\n  content: \"\\e066\";\n}\n.glyphicon-check:before {\n  content: \"\\e067\";\n}\n.glyphicon-move:before {\n  content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n  content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n  content: \"\\e070\";\n}\n.glyphicon-backward:before {\n  content: \"\\e071\";\n}\n.glyphicon-play:before {\n  content: \"\\e072\";\n}\n.glyphicon-pause:before {\n  content: \"\\e073\";\n}\n.glyphicon-stop:before {\n  content: \"\\e074\";\n}\n.glyphicon-forward:before {\n  content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n  content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n  content: \"\\e077\";\n}\n.glyphicon-eject:before {\n  content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n  content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n  content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n  content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n  content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n  content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n  content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n  content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n  content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n  content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n  content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n  content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n  content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n  content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n  content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n  content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n  content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n  content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n  content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n  content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n  content: \"\\e101\";\n}\n.glyphicon-gift:before {\n  content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n  content: \"\\e103\";\n}\n.glyphicon-fire:before {\n  content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n  content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n  content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n  content: \"\\e107\";\n}\n.glyphicon-plane:before {\n  content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n  content: \"\\e109\";\n}\n.glyphicon-random:before {\n  content: \"\\e110\";\n}\n.glyphicon-comment:before {\n  content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n  content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n  content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n  content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n  content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n  content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n  content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n  content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n  content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n  content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n  content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n  content: \"\\e122\";\n}\n.glyphicon-bell:before {\n  content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n  content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n  content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n  content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n  content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n  content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n  content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n  content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n  content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n  content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n  content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n  content: \"\\e134\";\n}\n.glyphicon-globe:before {\n  content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n  content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n  content: \"\\e137\";\n}\n.glyphicon-filter:before {\n  content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n  content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n  content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n  content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n  content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n  content: \"\\e143\";\n}\n.glyphicon-link:before {\n  content: \"\\e144\";\n}\n.glyphicon-phone:before {\n  content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n  content: \"\\e146\";\n}\n.glyphicon-usd:before {\n  content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n  content: \"\\e149\";\n}\n.glyphicon-sort:before {\n  content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n  content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n  content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n  content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n  content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n  content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n  content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n  content: \"\\e157\";\n}\n.glyphicon-expand:before {\n  content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n  content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n  content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n  content: \"\\e161\";\n}\n.glyphicon-flash:before {\n  content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n  content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n  content: \"\\e164\";\n}\n.glyphicon-record:before {\n  content: \"\\e165\";\n}\n.glyphicon-save:before {\n  content: \"\\e166\";\n}\n.glyphicon-open:before {\n  content: \"\\e167\";\n}\n.glyphicon-saved:before {\n  content: \"\\e168\";\n}\n.glyphicon-import:before {\n  content: \"\\e169\";\n}\n.glyphicon-export:before {\n  content: \"\\e170\";\n}\n.glyphicon-send:before {\n  content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n  content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n  content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n  content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n  content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n  content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n  content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n  content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n  content: \"\\e179\";\n}\n.glyphicon-header:before {\n  content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n  content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n  content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n  content: \"\\e183\";\n}\n.glyphicon-tower:before {\n  content: \"\\e184\";\n}\n.glyphicon-stats:before {\n  content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n  content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n  content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n  content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n  content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n  content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n  content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n  content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n  content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n  content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n  content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n  content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n  content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n  content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n  content: \"\\e200\";\n}\n.glyphicon-cd:before {\n  content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n  content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n  content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n  content: \"\\e204\";\n}\n.glyphicon-copy:before {\n  content: \"\\e205\";\n}\n.glyphicon-paste:before {\n  content: \"\\e206\";\n}\n.glyphicon-alert:before {\n  content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n  content: \"\\e210\";\n}\n.glyphicon-king:before {\n  content: \"\\e211\";\n}\n.glyphicon-queen:before {\n  content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n  content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n  content: \"\\e214\";\n}\n.glyphicon-knight:before {\n  content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n  content: \"\\e216\";\n}\n.glyphicon-tent:before {\n  content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n  content: \"\\e218\";\n}\n.glyphicon-bed:before {\n  content: \"\\e219\";\n}\n.glyphicon-apple:before {\n  content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n  content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n  content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n  content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n  content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n  content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n  content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n  content: \"\\e227\";\n}\n.glyphicon-btc:before {\n  content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n  content: \"\\e227\";\n}\n.glyphicon-yen:before {\n  content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n  content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n  content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n  content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n  content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n  content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n  content: \"\\e232\";\n}\n.glyphicon-education:before {\n  content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n  content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n  content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n  content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n  content: \"\\e237\";\n}\n.glyphicon-oil:before {\n  content: \"\\e238\";\n}\n.glyphicon-grain:before {\n  content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n  content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n  content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n  content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n  content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n  content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n  content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n  content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n  content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n  content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n  content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n  content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n  content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n  content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n  content: \"\\e253\";\n}\n.glyphicon-console:before {\n  content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n  content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n  content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n  content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n  content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n  content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n  content: \"\\e260\";\n}\n* {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\n*:before,\n*:after {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\nhtml {\n  font-size: 10px;\n\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #333;\n  background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\na {\n  color: #337ab7;\n  text-decoration: none;\n}\na:hover,\na:focus {\n  color: #23527c;\n  text-decoration: underline;\n}\na:focus {\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\nfigure {\n  margin: 0;\n}\nimg {\n  vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n}\n.img-rounded {\n  border-radius: 6px;\n}\n.img-thumbnail {\n  display: inline-block;\n  max-width: 100%;\n  height: auto;\n  padding: 4px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: all .2s ease-in-out;\n       -o-transition: all .2s ease-in-out;\n          transition: all .2s ease-in-out;\n}\n.img-circle {\n  border-radius: 50%;\n}\nhr {\n  margin-top: 20px;\n  margin-bottom: 20px;\n  border: 0;\n  border-top: 1px solid #eee;\n}\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n  position: static;\n  width: auto;\n  height: auto;\n  margin: 0;\n  overflow: visible;\n  clip: auto;\n}\n[role=\"button\"] {\n  cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  font-family: inherit;\n  font-weight: 500;\n  line-height: 1.1;\n  color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n  font-weight: normal;\n  line-height: 1;\n  color: #777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n  font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n  font-size: 75%;\n}\nh1,\n.h1 {\n  font-size: 36px;\n}\nh2,\n.h2 {\n  font-size: 30px;\n}\nh3,\n.h3 {\n  font-size: 24px;\n}\nh4,\n.h4 {\n  font-size: 18px;\n}\nh5,\n.h5 {\n  font-size: 14px;\n}\nh6,\n.h6 {\n  font-size: 12px;\n}\np {\n  margin: 0 0 10px;\n}\n.lead {\n  margin-bottom: 20px;\n  font-size: 16px;\n  font-weight: 300;\n  line-height: 1.4;\n}\n@media (min-width: 768px) {\n  .lead {\n    font-size: 21px;\n  }\n}\nsmall,\n.small {\n  font-size: 85%;\n}\nmark,\n.mark {\n  padding: .2em;\n  background-color: #fcf8e3;\n}\n.text-left {\n  text-align: left;\n}\n.text-right {\n  text-align: right;\n}\n.text-center {\n  text-align: center;\n}\n.text-justify {\n  text-align: justify;\n}\n.text-nowrap {\n  white-space: nowrap;\n}\n.text-lowercase {\n  text-transform: lowercase;\n}\n.text-uppercase {\n  text-transform: uppercase;\n}\n.text-capitalize {\n  text-transform: capitalize;\n}\n.text-muted {\n  color: #777;\n}\n.text-primary {\n  color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n  color: #286090;\n}\n.text-success {\n  color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n  color: #2b542c;\n}\n.text-info {\n  color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n  color: #245269;\n}\n.text-warning {\n  color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n  color: #66512c;\n}\n.text-danger {\n  color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n  color: #843534;\n}\n.bg-primary {\n  color: #fff;\n  background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n  background-color: #286090;\n}\n.bg-success {\n  background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n  background-color: #c1e2b3;\n}\n.bg-info {\n  background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n  background-color: #afd9ee;\n}\n.bg-warning {\n  background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n  background-color: #f7ecb5;\n}\n.bg-danger {\n  background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n  background-color: #e4b9b9;\n}\n.page-header {\n  padding-bottom: 9px;\n  margin: 40px 0 20px;\n  border-bottom: 1px solid #eee;\n}\nul,\nol {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n  margin-bottom: 0;\n}\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n.list-inline {\n  padding-left: 0;\n  margin-left: -5px;\n  list-style: none;\n}\n.list-inline > li {\n  display: inline-block;\n  padding-right: 5px;\n  padding-left: 5px;\n}\ndl {\n  margin-top: 0;\n  margin-bottom: 20px;\n}\ndt,\ndd {\n  line-height: 1.42857143;\n}\ndt {\n  font-weight: bold;\n}\ndd {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .dl-horizontal dt {\n    float: left;\n    width: 160px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .dl-horizontal dd {\n    margin-left: 180px;\n  }\n}\nabbr[title],\nabbr[data-original-title] {\n  cursor: help;\n  border-bottom: 1px dotted #777;\n}\n.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\nblockquote {\n  padding: 10px 20px;\n  margin: 0 0 20px;\n  font-size: 17.5px;\n  border-left: 5px solid #eee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n  margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n  display: block;\n  font-size: 80%;\n  line-height: 1.42857143;\n  color: #777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n  content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n  padding-right: 15px;\n  padding-left: 0;\n  text-align: right;\n  border-right: 5px solid #eee;\n  border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n  content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n  content: '\\00A0 \\2014';\n}\naddress {\n  margin-bottom: 20px;\n  font-style: normal;\n  line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #c7254e;\n  background-color: #f9f2f4;\n  border-radius: 4px;\n}\nkbd {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #fff;\n  background-color: #333;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n}\nkbd kbd {\n  padding: 0;\n  font-size: 100%;\n  font-weight: bold;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\npre {\n  display: block;\n  padding: 9.5px;\n  margin: 0 0 10px;\n  font-size: 13px;\n  line-height: 1.42857143;\n  color: #333;\n  word-break: break-all;\n  word-wrap: break-word;\n  background-color: #f5f5f5;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\npre code {\n  padding: 0;\n  font-size: inherit;\n  color: inherit;\n  white-space: pre-wrap;\n  background-color: transparent;\n  border-radius: 0;\n}\n.pre-scrollable {\n  max-height: 340px;\n  overflow-y: scroll;\n}\n.container {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n@media (min-width: 768px) {\n  .container {\n    width: 750px;\n  }\n}\n@media (min-width: 992px) {\n  .container {\n    width: 970px;\n  }\n}\n@media (min-width: 1200px) {\n  .container {\n    width: 1170px;\n  }\n}\n.container-fluid {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n.row {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n  position: relative;\n  min-height: 1px;\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n  float: left;\n}\n.col-xs-12 {\n  width: 100%;\n}\n.col-xs-11 {\n  width: 91.66666667%;\n}\n.col-xs-10 {\n  width: 83.33333333%;\n}\n.col-xs-9 {\n  width: 75%;\n}\n.col-xs-8 {\n  width: 66.66666667%;\n}\n.col-xs-7 {\n  width: 58.33333333%;\n}\n.col-xs-6 {\n  width: 50%;\n}\n.col-xs-5 {\n  width: 41.66666667%;\n}\n.col-xs-4 {\n  width: 33.33333333%;\n}\n.col-xs-3 {\n  width: 25%;\n}\n.col-xs-2 {\n  width: 16.66666667%;\n}\n.col-xs-1 {\n  width: 8.33333333%;\n}\n.col-xs-pull-12 {\n  right: 100%;\n}\n.col-xs-pull-11 {\n  right: 91.66666667%;\n}\n.col-xs-pull-10 {\n  right: 83.33333333%;\n}\n.col-xs-pull-9 {\n  right: 75%;\n}\n.col-xs-pull-8 {\n  right: 66.66666667%;\n}\n.col-xs-pull-7 {\n  right: 58.33333333%;\n}\n.col-xs-pull-6 {\n  right: 50%;\n}\n.col-xs-pull-5 {\n  right: 41.66666667%;\n}\n.col-xs-pull-4 {\n  right: 33.33333333%;\n}\n.col-xs-pull-3 {\n  right: 25%;\n}\n.col-xs-pull-2 {\n  right: 16.66666667%;\n}\n.col-xs-pull-1 {\n  right: 8.33333333%;\n}\n.col-xs-pull-0 {\n  right: auto;\n}\n.col-xs-push-12 {\n  left: 100%;\n}\n.col-xs-push-11 {\n  left: 91.66666667%;\n}\n.col-xs-push-10 {\n  left: 83.33333333%;\n}\n.col-xs-push-9 {\n  left: 75%;\n}\n.col-xs-push-8 {\n  left: 66.66666667%;\n}\n.col-xs-push-7 {\n  left: 58.33333333%;\n}\n.col-xs-push-6 {\n  left: 50%;\n}\n.col-xs-push-5 {\n  left: 41.66666667%;\n}\n.col-xs-push-4 {\n  left: 33.33333333%;\n}\n.col-xs-push-3 {\n  left: 25%;\n}\n.col-xs-push-2 {\n  left: 16.66666667%;\n}\n.col-xs-push-1 {\n  left: 8.33333333%;\n}\n.col-xs-push-0 {\n  left: auto;\n}\n.col-xs-offset-12 {\n  margin-left: 100%;\n}\n.col-xs-offset-11 {\n  margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n  margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n  margin-left: 75%;\n}\n.col-xs-offset-8 {\n  margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n  margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n  margin-left: 50%;\n}\n.col-xs-offset-5 {\n  margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n  margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n  margin-left: 25%;\n}\n.col-xs-offset-2 {\n  margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n  margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n    float: left;\n  }\n  .col-sm-12 {\n    width: 100%;\n  }\n  .col-sm-11 {\n    width: 91.66666667%;\n  }\n  .col-sm-10 {\n    width: 83.33333333%;\n  }\n  .col-sm-9 {\n    width: 75%;\n  }\n  .col-sm-8 {\n    width: 66.66666667%;\n  }\n  .col-sm-7 {\n    width: 58.33333333%;\n  }\n  .col-sm-6 {\n    width: 50%;\n  }\n  .col-sm-5 {\n    width: 41.66666667%;\n  }\n  .col-sm-4 {\n    width: 33.33333333%;\n  }\n  .col-sm-3 {\n    width: 25%;\n  }\n  .col-sm-2 {\n    width: 16.66666667%;\n  }\n  .col-sm-1 {\n    width: 8.33333333%;\n  }\n  .col-sm-pull-12 {\n    right: 100%;\n  }\n  .col-sm-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-sm-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-sm-pull-9 {\n    right: 75%;\n  }\n  .col-sm-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-sm-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-sm-pull-6 {\n    right: 50%;\n  }\n  .col-sm-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-sm-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-sm-pull-3 {\n    right: 25%;\n  }\n  .col-sm-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-sm-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-sm-pull-0 {\n    right: auto;\n  }\n  .col-sm-push-12 {\n    left: 100%;\n  }\n  .col-sm-push-11 {\n    left: 91.66666667%;\n  }\n  .col-sm-push-10 {\n    left: 83.33333333%;\n  }\n  .col-sm-push-9 {\n    left: 75%;\n  }\n  .col-sm-push-8 {\n    left: 66.66666667%;\n  }\n  .col-sm-push-7 {\n    left: 58.33333333%;\n  }\n  .col-sm-push-6 {\n    left: 50%;\n  }\n  .col-sm-push-5 {\n    left: 41.66666667%;\n  }\n  .col-sm-push-4 {\n    left: 33.33333333%;\n  }\n  .col-sm-push-3 {\n    left: 25%;\n  }\n  .col-sm-push-2 {\n    left: 16.66666667%;\n  }\n  .col-sm-push-1 {\n    left: 8.33333333%;\n  }\n  .col-sm-push-0 {\n    left: auto;\n  }\n  .col-sm-offset-12 {\n    margin-left: 100%;\n  }\n  .col-sm-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-sm-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-sm-offset-9 {\n    margin-left: 75%;\n  }\n  .col-sm-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-sm-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-sm-offset-6 {\n    margin-left: 50%;\n  }\n  .col-sm-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-sm-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-sm-offset-3 {\n    margin-left: 25%;\n  }\n  .col-sm-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-sm-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-sm-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 992px) {\n  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n    float: left;\n  }\n  .col-md-12 {\n    width: 100%;\n  }\n  .col-md-11 {\n    width: 91.66666667%;\n  }\n  .col-md-10 {\n    width: 83.33333333%;\n  }\n  .col-md-9 {\n    width: 75%;\n  }\n  .col-md-8 {\n    width: 66.66666667%;\n  }\n  .col-md-7 {\n    width: 58.33333333%;\n  }\n  .col-md-6 {\n    width: 50%;\n  }\n  .col-md-5 {\n    width: 41.66666667%;\n  }\n  .col-md-4 {\n    width: 33.33333333%;\n  }\n  .col-md-3 {\n    width: 25%;\n  }\n  .col-md-2 {\n    width: 16.66666667%;\n  }\n  .col-md-1 {\n    width: 8.33333333%;\n  }\n  .col-md-pull-12 {\n    right: 100%;\n  }\n  .col-md-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-md-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-md-pull-9 {\n    right: 75%;\n  }\n  .col-md-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-md-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-md-pull-6 {\n    right: 50%;\n  }\n  .col-md-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-md-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-md-pull-3 {\n    right: 25%;\n  }\n  .col-md-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-md-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-md-pull-0 {\n    right: auto;\n  }\n  .col-md-push-12 {\n    left: 100%;\n  }\n  .col-md-push-11 {\n    left: 91.66666667%;\n  }\n  .col-md-push-10 {\n    left: 83.33333333%;\n  }\n  .col-md-push-9 {\n    left: 75%;\n  }\n  .col-md-push-8 {\n    left: 66.66666667%;\n  }\n  .col-md-push-7 {\n    left: 58.33333333%;\n  }\n  .col-md-push-6 {\n    left: 50%;\n  }\n  .col-md-push-5 {\n    left: 41.66666667%;\n  }\n  .col-md-push-4 {\n    left: 33.33333333%;\n  }\n  .col-md-push-3 {\n    left: 25%;\n  }\n  .col-md-push-2 {\n    left: 16.66666667%;\n  }\n  .col-md-push-1 {\n    left: 8.33333333%;\n  }\n  .col-md-push-0 {\n    left: auto;\n  }\n  .col-md-offset-12 {\n    margin-left: 100%;\n  }\n  .col-md-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-md-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-md-offset-9 {\n    margin-left: 75%;\n  }\n  .col-md-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-md-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-md-offset-6 {\n    margin-left: 50%;\n  }\n  .col-md-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-md-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-md-offset-3 {\n    margin-left: 25%;\n  }\n  .col-md-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-md-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-md-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 1200px) {\n  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n    float: left;\n  }\n  .col-lg-12 {\n    width: 100%;\n  }\n  .col-lg-11 {\n    width: 91.66666667%;\n  }\n  .col-lg-10 {\n    width: 83.33333333%;\n  }\n  .col-lg-9 {\n    width: 75%;\n  }\n  .col-lg-8 {\n    width: 66.66666667%;\n  }\n  .col-lg-7 {\n    width: 58.33333333%;\n  }\n  .col-lg-6 {\n    width: 50%;\n  }\n  .col-lg-5 {\n    width: 41.66666667%;\n  }\n  .col-lg-4 {\n    width: 33.33333333%;\n  }\n  .col-lg-3 {\n    width: 25%;\n  }\n  .col-lg-2 {\n    width: 16.66666667%;\n  }\n  .col-lg-1 {\n    width: 8.33333333%;\n  }\n  .col-lg-pull-12 {\n    right: 100%;\n  }\n  .col-lg-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-lg-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-lg-pull-9 {\n    right: 75%;\n  }\n  .col-lg-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-lg-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-lg-pull-6 {\n    right: 50%;\n  }\n  .col-lg-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-lg-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-lg-pull-3 {\n    right: 25%;\n  }\n  .col-lg-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-lg-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-lg-pull-0 {\n    right: auto;\n  }\n  .col-lg-push-12 {\n    left: 100%;\n  }\n  .col-lg-push-11 {\n    left: 91.66666667%;\n  }\n  .col-lg-push-10 {\n    left: 83.33333333%;\n  }\n  .col-lg-push-9 {\n    left: 75%;\n  }\n  .col-lg-push-8 {\n    left: 66.66666667%;\n  }\n  .col-lg-push-7 {\n    left: 58.33333333%;\n  }\n  .col-lg-push-6 {\n    left: 50%;\n  }\n  .col-lg-push-5 {\n    left: 41.66666667%;\n  }\n  .col-lg-push-4 {\n    left: 33.33333333%;\n  }\n  .col-lg-push-3 {\n    left: 25%;\n  }\n  .col-lg-push-2 {\n    left: 16.66666667%;\n  }\n  .col-lg-push-1 {\n    left: 8.33333333%;\n  }\n  .col-lg-push-0 {\n    left: auto;\n  }\n  .col-lg-offset-12 {\n    margin-left: 100%;\n  }\n  .col-lg-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-lg-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-lg-offset-9 {\n    margin-left: 75%;\n  }\n  .col-lg-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-lg-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-lg-offset-6 {\n    margin-left: 50%;\n  }\n  .col-lg-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-lg-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-lg-offset-3 {\n    margin-left: 25%;\n  }\n  .col-lg-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-lg-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-lg-offset-0 {\n    margin-left: 0;\n  }\n}\ntable {\n  background-color: transparent;\n}\ncaption {\n  padding-top: 8px;\n  padding-bottom: 8px;\n  color: #777;\n  text-align: left;\n}\nth {\n  text-align: left;\n}\n.table {\n  width: 100%;\n  max-width: 100%;\n  margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n  padding: 8px;\n  line-height: 1.42857143;\n  vertical-align: top;\n  border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n  vertical-align: bottom;\n  border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n  border-top: 0;\n}\n.table > tbody + tbody {\n  border-top: 2px solid #ddd;\n}\n.table .table {\n  background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n  padding: 5px;\n}\n.table-bordered {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n  border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n  background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n  background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n  position: static;\n  display: table-column;\n  float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n  position: static;\n  display: table-cell;\n  float: none;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n  background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n  background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n  background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n  background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n  background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n  background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n  background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n  background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n  background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n  background-color: #ebcccc;\n}\n.table-responsive {\n  min-height: .01%;\n  overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: 15px;\n    overflow-y: hidden;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid #ddd;\n  }\n  .table-responsive > .table {\n    margin-bottom: 0;\n  }\n  .table-responsive > .table > thead > tr > th,\n  .table-responsive > .table > tbody > tr > th,\n  .table-responsive > .table > tfoot > tr > th,\n  .table-responsive > .table > thead > tr > td,\n  .table-responsive > .table > tbody > tr > td,\n  .table-responsive > .table > tfoot > tr > td {\n    white-space: nowrap;\n  }\n  .table-responsive > .table-bordered {\n    border: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:first-child,\n  .table-responsive > .table-bordered > tbody > tr > th:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n  .table-responsive > .table-bordered > thead > tr > td:first-child,\n  .table-responsive > .table-bordered > tbody > tr > td:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n    border-left: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:last-child,\n  .table-responsive > .table-bordered > tbody > tr > th:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n  .table-responsive > .table-bordered > thead > tr > td:last-child,\n  .table-responsive > .table-bordered > tbody > tr > td:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n    border-right: 0;\n  }\n  .table-responsive > .table-bordered > tbody > tr:last-child > th,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n  .table-responsive > .table-bordered > tbody > tr:last-child > td,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n    border-bottom: 0;\n  }\n}\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 20px;\n  font-size: 21px;\n  line-height: inherit;\n  color: #333;\n  border: 0;\n  border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n  display: inline-block;\n  max-width: 100%;\n  margin-bottom: 5px;\n  font-weight: bold;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9;\n  line-height: normal;\n}\ninput[type=\"file\"] {\n  display: block;\n}\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\nselect[multiple],\nselect[size] {\n  height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\noutput {\n  display: block;\n  padding-top: 7px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n}\n.form-control {\n  display: block;\n  width: 100%;\n  height: 34px;\n  padding: 6px 12px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n  background-color: #fff;\n  background-image: none;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n       -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n          transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n  border-color: #66afe9;\n  outline: 0;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n          box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n}\n.form-control::-moz-placeholder {\n  color: #999;\n  opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n  color: #999;\n}\n.form-control::-webkit-input-placeholder {\n  color: #999;\n}\n.form-control::-ms-expand {\n  background-color: transparent;\n  border: 0;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n  background-color: #eee;\n  opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n  cursor: not-allowed;\n}\ntextarea.form-control {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n  input[type=\"date\"].form-control,\n  input[type=\"time\"].form-control,\n  input[type=\"datetime-local\"].form-control,\n  input[type=\"month\"].form-control {\n    line-height: 34px;\n  }\n  input[type=\"date\"].input-sm,\n  input[type=\"time\"].input-sm,\n  input[type=\"datetime-local\"].input-sm,\n  input[type=\"month\"].input-sm,\n  .input-group-sm input[type=\"date\"],\n  .input-group-sm input[type=\"time\"],\n  .input-group-sm input[type=\"datetime-local\"],\n  .input-group-sm input[type=\"month\"] {\n    line-height: 30px;\n  }\n  input[type=\"date\"].input-lg,\n  input[type=\"time\"].input-lg,\n  input[type=\"datetime-local\"].input-lg,\n  input[type=\"month\"].input-lg,\n  .input-group-lg input[type=\"date\"],\n  .input-group-lg input[type=\"time\"],\n  .input-group-lg input[type=\"datetime-local\"],\n  .input-group-lg input[type=\"month\"] {\n    line-height: 46px;\n  }\n}\n.form-group {\n  margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n  min-height: 20px;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-top: 4px \\9;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n  position: relative;\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  vertical-align: middle;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n  cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n  cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n  cursor: not-allowed;\n}\n.form-control-static {\n  min-height: 34px;\n  padding-top: 7px;\n  padding-bottom: 7px;\n  margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-sm {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-sm {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n  height: auto;\n}\n.form-group-sm .form-control {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.form-group-sm select.form-control {\n  height: 30px;\n  line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n  height: auto;\n}\n.form-group-sm .form-control-static {\n  height: 30px;\n  min-height: 32px;\n  padding: 6px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.input-lg {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-lg {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n  height: auto;\n}\n.form-group-lg .form-control {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.form-group-lg select.form-control {\n  height: 46px;\n  line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n  height: auto;\n}\n.form-group-lg .form-control-static {\n  height: 46px;\n  min-height: 38px;\n  padding: 11px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.has-feedback {\n  position: relative;\n}\n.has-feedback .form-control {\n  padding-right: 42.5px;\n}\n.form-control-feedback {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  display: block;\n  width: 34px;\n  height: 34px;\n  line-height: 34px;\n  text-align: center;\n  pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n  width: 46px;\n  height: 46px;\n  line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n  width: 30px;\n  height: 30px;\n  line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n  color: #3c763d;\n}\n.has-success .form-control {\n  border-color: #3c763d;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-success .form-control:focus {\n  border-color: #2b542c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n  color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n  color: #8a6d3b;\n}\n.has-warning .form-control {\n  border-color: #8a6d3b;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-warning .form-control:focus {\n  border-color: #66512c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n  color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n  color: #a94442;\n}\n.has-error .form-control {\n  border-color: #a94442;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-error .form-control:focus {\n  border-color: #843534;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #a94442;\n}\n.has-error .form-control-feedback {\n  color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n  top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n  top: 0;\n}\n.help-block {\n  display: block;\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: #737373;\n}\n@media (min-width: 768px) {\n  .form-inline .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .form-inline .form-control-static {\n    display: inline-block;\n  }\n  .form-inline .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .form-inline .input-group .input-group-addon,\n  .form-inline .input-group .input-group-btn,\n  .form-inline .input-group .form-control {\n    width: auto;\n  }\n  .form-inline .input-group > .form-control {\n    width: 100%;\n  }\n  .form-inline .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio,\n  .form-inline .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio label,\n  .form-inline .checkbox label {\n    padding-left: 0;\n  }\n  .form-inline .radio input[type=\"radio\"],\n  .form-inline .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .form-inline .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n  padding-top: 7px;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n  min-height: 27px;\n}\n.form-horizontal .form-group {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .control-label {\n    padding-top: 7px;\n    margin-bottom: 0;\n    text-align: right;\n  }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n  right: 15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-lg .control-label {\n    padding-top: 11px;\n    font-size: 18px;\n  }\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-sm .control-label {\n    padding-top: 6px;\n    font-size: 12px;\n  }\n}\n.btn {\n  display: inline-block;\n  padding: 6px 12px;\n  margin-bottom: 0;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  -ms-touch-action: manipulation;\n      touch-action: manipulation;\n  cursor: pointer;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n  color: #333;\n  text-decoration: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n  outline: 0;\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n  cursor: not-allowed;\n  filter: alpha(opacity=65);\n  -webkit-box-shadow: none;\n          box-shadow: none;\n  opacity: .65;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n  pointer-events: none;\n}\n.btn-default {\n  color: #333;\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #8c8c8c;\n}\n.btn-default:hover {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n  color: #333;\n  background-color: #d4d4d4;\n  border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default .badge {\n  color: #fff;\n  background-color: #333;\n}\n.btn-primary {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n  color: #fff;\n  background-color: #286090;\n  border-color: #122b40;\n}\n.btn-primary:hover {\n  color: #fff;\n  background-color: #286090;\n  border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  color: #fff;\n  background-color: #286090;\n  border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n  color: #fff;\n  background-color: #204d74;\n  border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.btn-success {\n  color: #fff;\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #255625;\n}\n.btn-success:hover {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n  color: #fff;\n  background-color: #398439;\n  border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success .badge {\n  color: #5cb85c;\n  background-color: #fff;\n}\n.btn-info {\n  color: #fff;\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #1b6d85;\n}\n.btn-info:hover {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n  color: #fff;\n  background-color: #269abc;\n  border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info .badge {\n  color: #5bc0de;\n  background-color: #fff;\n}\n.btn-warning {\n  color: #fff;\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #985f0d;\n}\n.btn-warning:hover {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n  color: #fff;\n  background-color: #d58512;\n  border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning .badge {\n  color: #f0ad4e;\n  background-color: #fff;\n}\n.btn-danger {\n  color: #fff;\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #761c19;\n}\n.btn-danger:hover {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n  color: #fff;\n  background-color: #ac2925;\n  border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger .badge {\n  color: #d9534f;\n  background-color: #fff;\n}\n.btn-link {\n  font-weight: normal;\n  color: #337ab7;\n  border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n  border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n  color: #23527c;\n  text-decoration: underline;\n  background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n  color: #777;\n  text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n  padding: 1px 5px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-block {\n  display: block;\n  width: 100%;\n}\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n  width: 100%;\n}\n.fade {\n  opacity: 0;\n  -webkit-transition: opacity .15s linear;\n       -o-transition: opacity .15s linear;\n          transition: opacity .15s linear;\n}\n.fade.in {\n  opacity: 1;\n}\n.collapse {\n  display: none;\n}\n.collapse.in {\n  display: block;\n}\ntr.collapse.in {\n  display: table-row;\n}\ntbody.collapse.in {\n  display: table-row-group;\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  -webkit-transition-timing-function: ease;\n       -o-transition-timing-function: ease;\n          transition-timing-function: ease;\n  -webkit-transition-duration: .35s;\n       -o-transition-duration: .35s;\n          transition-duration: .35s;\n  -webkit-transition-property: height, visibility;\n       -o-transition-property: height, visibility;\n          transition-property: height, visibility;\n}\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top: 4px dashed;\n  border-top: 4px solid \\9;\n  border-right: 4px solid transparent;\n  border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n  position: relative;\n}\n.dropdown-toggle:focus {\n  outline: 0;\n}\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: 1000;\n  display: none;\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0;\n  font-size: 14px;\n  text-align: left;\n  list-style: none;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .15);\n  border-radius: 4px;\n  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n          box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n}\n.dropdown-menu.pull-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu .divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n  display: block;\n  padding: 3px 20px;\n  clear: both;\n  font-weight: normal;\n  line-height: 1.42857143;\n  color: #333;\n  white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  color: #262626;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  color: #fff;\n  text-decoration: none;\n  background-color: #337ab7;\n  outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  color: #777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n  background-image: none;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n  display: block;\n}\n.open > a {\n  outline: 0;\n}\n.dropdown-menu-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu-left {\n  right: auto;\n  left: 0;\n}\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: 12px;\n  line-height: 1.42857143;\n  color: #777;\n  white-space: nowrap;\n}\n.dropdown-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 990;\n}\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n  content: \"\";\n  border-top: 0;\n  border-bottom: 4px dashed;\n  border-bottom: 4px solid \\9;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n  top: auto;\n  bottom: 100%;\n  margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n  .navbar-right .dropdown-menu {\n    right: 0;\n    left: auto;\n  }\n  .navbar-right .dropdown-menu-left {\n    right: auto;\n    left: 0;\n  }\n}\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n  z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n  margin-left: -1px;\n}\n.btn-toolbar {\n  margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n  float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n  margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n.btn-group > .btn:first-child {\n  margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n  padding-right: 8px;\n  padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-right: 12px;\n  padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn .caret {\n  margin-left: 0;\n}\n.btn-lg .caret {\n  border-width: 5px 5px 0;\n  border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n  border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n  display: block;\n  float: none;\n  width: 100%;\n  max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n  float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n  margin-top: -1px;\n  margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n  display: table-cell;\n  float: none;\n  width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n  width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n  left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.input-group {\n  position: relative;\n  display: table;\n  border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n  float: none;\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-group .form-control {\n  position: relative;\n  z-index: 2;\n  float: left;\n  width: 100%;\n  margin-bottom: 0;\n}\n.input-group .form-control:focus {\n  z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle;\n}\n.input-group-addon {\n  padding: 6px 12px;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1;\n  color: #555;\n  text-align: center;\n  background-color: #eee;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n.input-group-addon.input-sm {\n  padding: 5px 10px;\n  font-size: 12px;\n  border-radius: 3px;\n}\n.input-group-addon.input-lg {\n  padding: 10px 16px;\n  font-size: 18px;\n  border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n  margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n.input-group-btn {\n  position: relative;\n  font-size: 0;\n  white-space: nowrap;\n}\n.input-group-btn > .btn {\n  position: relative;\n}\n.input-group-btn > .btn + .btn {\n  margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n  z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n  margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n  z-index: 2;\n  margin-left: -1px;\n}\n.nav {\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.nav > li {\n  position: relative;\n  display: block;\n}\n.nav > li > a {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.nav > li.disabled > a {\n  color: #777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n  color: #777;\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n  background-color: #eee;\n  border-color: #337ab7;\n}\n.nav .nav-divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.nav > li > a > img {\n  max-width: none;\n}\n.nav-tabs {\n  border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n  float: left;\n  margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n  margin-right: 2px;\n  line-height: 1.42857143;\n  border: 1px solid transparent;\n  border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n  border-color: #eee #eee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n  color: #555;\n  cursor: default;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n  width: 100%;\n  border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n  float: none;\n}\n.nav-tabs.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-tabs.nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs.nav-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs.nav-justified > .active > a,\n  .nav-tabs.nav-justified > .active > a:hover,\n  .nav-tabs.nav-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.nav-pills > li {\n  float: left;\n}\n.nav-pills > li > a {\n  border-radius: 4px;\n}\n.nav-pills > li + li {\n  margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n  color: #fff;\n  background-color: #337ab7;\n}\n.nav-stacked > li {\n  float: none;\n}\n.nav-stacked > li + li {\n  margin-top: 2px;\n  margin-left: 0;\n}\n.nav-justified {\n  width: 100%;\n}\n.nav-justified > li {\n  float: none;\n}\n.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs-justified {\n  border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs-justified > .active > a,\n  .nav-tabs-justified > .active > a:hover,\n  .nav-tabs-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.tab-content > .tab-pane {\n  display: none;\n}\n.tab-content > .active {\n  display: block;\n}\n.nav-tabs .dropdown-menu {\n  margin-top: -1px;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar {\n  position: relative;\n  min-height: 50px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n  .navbar {\n    border-radius: 4px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-header {\n    float: left;\n  }\n}\n.navbar-collapse {\n  padding-right: 15px;\n  padding-left: 15px;\n  overflow-x: visible;\n  -webkit-overflow-scrolling: touch;\n  border-top: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n}\n.navbar-collapse.in {\n  overflow-y: auto;\n}\n@media (min-width: 768px) {\n  .navbar-collapse {\n    width: auto;\n    border-top: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-collapse.collapse {\n    display: block !important;\n    height: auto !important;\n    padding-bottom: 0;\n    overflow: visible !important;\n  }\n  .navbar-collapse.in {\n    overflow-y: visible;\n  }\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-static-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    padding-right: 0;\n    padding-left: 0;\n  }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n  max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    max-height: 200px;\n  }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .container > .navbar-header,\n  .container-fluid > .navbar-header,\n  .container > .navbar-collapse,\n  .container-fluid > .navbar-collapse {\n    margin-right: 0;\n    margin-left: 0;\n  }\n}\n.navbar-static-top {\n  z-index: 1000;\n  border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n  .navbar-static-top {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n@media (min-width: 768px) {\n  .navbar-fixed-top,\n  .navbar-fixed-bottom {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  top: 0;\n  border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0;\n  border-width: 1px 0 0;\n}\n.navbar-brand {\n  float: left;\n  height: 50px;\n  padding: 15px 15px;\n  font-size: 18px;\n  line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n  text-decoration: none;\n}\n.navbar-brand > img {\n  display: block;\n}\n@media (min-width: 768px) {\n  .navbar > .container .navbar-brand,\n  .navbar > .container-fluid .navbar-brand {\n    margin-left: -15px;\n  }\n}\n.navbar-toggle {\n  position: relative;\n  float: right;\n  padding: 9px 10px;\n  margin-top: 8px;\n  margin-right: 15px;\n  margin-bottom: 8px;\n  background-color: transparent;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.navbar-toggle:focus {\n  outline: 0;\n}\n.navbar-toggle .icon-bar {\n  display: block;\n  width: 22px;\n  height: 2px;\n  border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 4px;\n}\n@media (min-width: 768px) {\n  .navbar-toggle {\n    display: none;\n  }\n}\n.navbar-nav {\n  margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n  padding-top: 10px;\n  padding-bottom: 10px;\n  line-height: 20px;\n}\n@media (max-width: 767px) {\n  .navbar-nav .open .dropdown-menu {\n    position: static;\n    float: none;\n    width: auto;\n    margin-top: 0;\n    background-color: transparent;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-nav .open .dropdown-menu > li > a,\n  .navbar-nav .open .dropdown-menu .dropdown-header {\n    padding: 5px 15px 5px 25px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a {\n    line-height: 20px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-nav .open .dropdown-menu > li > a:focus {\n    background-image: none;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-nav {\n    float: left;\n    margin: 0;\n  }\n  .navbar-nav > li {\n    float: left;\n  }\n  .navbar-nav > li > a {\n    padding-top: 15px;\n    padding-bottom: 15px;\n  }\n}\n.navbar-form {\n  padding: 10px 15px;\n  margin-top: 8px;\n  margin-right: -15px;\n  margin-bottom: 8px;\n  margin-left: -15px;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n}\n@media (min-width: 768px) {\n  .navbar-form .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control-static {\n    display: inline-block;\n  }\n  .navbar-form .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .navbar-form .input-group .input-group-addon,\n  .navbar-form .input-group .input-group-btn,\n  .navbar-form .input-group .form-control {\n    width: auto;\n  }\n  .navbar-form .input-group > .form-control {\n    width: 100%;\n  }\n  .navbar-form .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio,\n  .navbar-form .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio label,\n  .navbar-form .checkbox label {\n    padding-left: 0;\n  }\n  .navbar-form .radio input[type=\"radio\"],\n  .navbar-form .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .navbar-form .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n@media (max-width: 767px) {\n  .navbar-form .form-group {\n    margin-bottom: 5px;\n  }\n  .navbar-form .form-group:last-child {\n    margin-bottom: 0;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-form {\n    width: auto;\n    padding-top: 0;\n    padding-bottom: 0;\n    margin-right: 0;\n    margin-left: 0;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n}\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  margin-bottom: 0;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.navbar-btn {\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n  margin-top: 14px;\n  margin-bottom: 14px;\n}\n.navbar-text {\n  margin-top: 15px;\n  margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n  .navbar-text {\n    float: left;\n    margin-right: 15px;\n    margin-left: 15px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-left {\n    float: left !important;\n  }\n  .navbar-right {\n    float: right !important;\n    margin-right: -15px;\n  }\n  .navbar-right ~ .navbar-right {\n    margin-right: 0;\n  }\n}\n.navbar-default {\n  background-color: #f8f8f8;\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n  color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n  color: #5e5e5e;\n  background-color: transparent;\n}\n.navbar-default .navbar-text {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n  color: #333;\n  background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n  color: #ccc;\n  background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n  border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n  background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n    color: #777;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #333;\n    background-color: transparent;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #555;\n    background-color: #e7e7e7;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #ccc;\n    background-color: transparent;\n  }\n}\n.navbar-default .navbar-link {\n  color: #777;\n}\n.navbar-default .navbar-link:hover {\n  color: #333;\n}\n.navbar-default .btn-link {\n  color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n  color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n  color: #ccc;\n}\n.navbar-inverse {\n  background-color: #222;\n  border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n  color: #444;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n  border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n  background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n  background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n  border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n@media (max-width: 767px) {\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n    border-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n    color: #9d9d9d;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #fff;\n    background-color: transparent;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #444;\n    background-color: transparent;\n  }\n}\n.navbar-inverse .navbar-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n  color: #fff;\n}\n.navbar-inverse .btn-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n  color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n  color: #444;\n}\n.breadcrumb {\n  padding: 8px 15px;\n  margin-bottom: 20px;\n  list-style: none;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n}\n.breadcrumb > li {\n  display: inline-block;\n}\n.breadcrumb > li + li:before {\n  padding: 0 5px;\n  color: #ccc;\n  content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n  color: #777;\n}\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: 20px 0;\n  border-radius: 4px;\n}\n.pagination > li {\n  display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n  position: relative;\n  float: left;\n  padding: 6px 12px;\n  margin-left: -1px;\n  line-height: 1.42857143;\n  color: #337ab7;\n  text-decoration: none;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n  margin-left: 0;\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n  z-index: 2;\n  color: #23527c;\n  background-color: #eee;\n  border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n  z-index: 3;\n  color: #fff;\n  cursor: default;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n  border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n  border-top-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n  border-top-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n.pager {\n  padding-left: 0;\n  margin: 20px 0;\n  text-align: center;\n  list-style: none;\n}\n.pager li {\n  display: inline;\n}\n.pager li > a,\n.pager li > span {\n  display: inline-block;\n  padding: 5px 14px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.pager .next > a,\n.pager .next > span {\n  float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n  float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n}\n.label {\n  display: inline;\n  padding: .2em .6em .3em;\n  font-size: 75%;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.label:empty {\n  display: none;\n}\n.btn .label {\n  position: relative;\n  top: -1px;\n}\n.label-default {\n  background-color: #777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n  background-color: #5e5e5e;\n}\n.label-primary {\n  background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n  background-color: #286090;\n}\n.label-success {\n  background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n  background-color: #449d44;\n}\n.label-info {\n  background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n  background-color: #31b0d5;\n}\n.label-warning {\n  background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n  background-color: #ec971f;\n}\n.label-danger {\n  background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n  background-color: #c9302c;\n}\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: 12px;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  background-color: #777;\n  border-radius: 10px;\n}\n.badge:empty {\n  display: none;\n}\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n  top: 0;\n  padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.list-group-item > .badge {\n  float: right;\n}\n.list-group-item > .badge + .badge {\n  margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n.jumbotron {\n  padding-top: 30px;\n  padding-bottom: 30px;\n  margin-bottom: 30px;\n  color: inherit;\n  background-color: #eee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n  color: inherit;\n}\n.jumbotron p {\n  margin-bottom: 15px;\n  font-size: 21px;\n  font-weight: 200;\n}\n.jumbotron > hr {\n  border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n  padding-right: 15px;\n  padding-left: 15px;\n  border-radius: 6px;\n}\n.jumbotron .container {\n  max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n  .jumbotron {\n    padding-top: 48px;\n    padding-bottom: 48px;\n  }\n  .container .jumbotron,\n  .container-fluid .jumbotron {\n    padding-right: 60px;\n    padding-left: 60px;\n  }\n  .jumbotron h1,\n  .jumbotron .h1 {\n    font-size: 63px;\n  }\n}\n.thumbnail {\n  display: block;\n  padding: 4px;\n  margin-bottom: 20px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: border .2s ease-in-out;\n       -o-transition: border .2s ease-in-out;\n          transition: border .2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n  margin-right: auto;\n  margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n  border-color: #337ab7;\n}\n.thumbnail .caption {\n  padding: 9px;\n  color: #333;\n}\n.alert {\n  padding: 15px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.alert h4 {\n  margin-top: 0;\n  color: inherit;\n}\n.alert .alert-link {\n  font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n  margin-bottom: 0;\n}\n.alert > p + p {\n  margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n  padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n  position: relative;\n  top: -2px;\n  right: -21px;\n  color: inherit;\n}\n.alert-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.alert-success hr {\n  border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n  color: #2b542c;\n}\n.alert-info {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.alert-info hr {\n  border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n  color: #245269;\n}\n.alert-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.alert-warning hr {\n  border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n  color: #66512c;\n}\n.alert-danger {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.alert-danger hr {\n  border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n  color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@-o-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n.progress {\n  height: 20px;\n  margin-bottom: 20px;\n  overflow: hidden;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n          box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n}\n.progress-bar {\n  float: left;\n  width: 0;\n  height: 100%;\n  font-size: 12px;\n  line-height: 20px;\n  color: #fff;\n  text-align: center;\n  background-color: #337ab7;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n  -webkit-transition: width .6s ease;\n       -o-transition: width .6s ease;\n          transition: width .6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  -webkit-background-size: 40px 40px;\n          background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n  -webkit-animation: progress-bar-stripes 2s linear infinite;\n       -o-animation: progress-bar-stripes 2s linear infinite;\n          animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n  background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n  background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n  background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n  background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n.media-body {\n  width: 10000px;\n}\n.media-object {\n  display: block;\n}\n.media-object.img-thumbnail {\n  max-width: none;\n}\n.media-right,\n.media > .pull-right {\n  padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n  padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n  display: table-cell;\n  vertical-align: top;\n}\n.media-middle {\n  vertical-align: middle;\n}\n.media-bottom {\n  vertical-align: bottom;\n}\n.media-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n.list-group {\n  padding-left: 0;\n  margin-bottom: 20px;\n}\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n  margin-bottom: -1px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n  margin-bottom: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n  color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n  color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n  color: #555;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\nbutton.list-group-item {\n  width: 100%;\n  text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #eee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n  color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n  color: #777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  z-index: 2;\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n  color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n  color: #c7ddef;\n}\n.list-group-item-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n  color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n  color: #3c763d;\n  background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n  color: #fff;\n  background-color: #3c763d;\n  border-color: #3c763d;\n}\n.list-group-item-info {\n  color: #31708f;\n  background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n  color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n  color: #31708f;\n  background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n  color: #fff;\n  background-color: #31708f;\n  border-color: #31708f;\n}\n.list-group-item-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n  color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n  color: #8a6d3b;\n  background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n  color: #fff;\n  background-color: #8a6d3b;\n  border-color: #8a6d3b;\n}\n.list-group-item-danger {\n  color: #a94442;\n  background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n  color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n  color: #a94442;\n  background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n  color: #fff;\n  background-color: #a94442;\n  border-color: #a94442;\n}\n.list-group-item-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.list-group-item-text {\n  margin-bottom: 0;\n  line-height: 1.3;\n}\n.panel {\n  margin-bottom: 20px;\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n}\n.panel-body {\n  padding: 15px;\n}\n.panel-heading {\n  padding: 10px 15px;\n  border-bottom: 1px solid transparent;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n  color: inherit;\n}\n.panel-title {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-size: 16px;\n  color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n  color: inherit;\n}\n.panel-footer {\n  padding: 10px 15px;\n  background-color: #f5f5f5;\n  border-top: 1px solid #ddd;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n  margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n  border-width: 1px 0;\n  border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n  border-top: 0;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n  border-bottom: 0;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n  border-top-width: 0;\n}\n.list-group + .panel-footer {\n  border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n  margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n  border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n  border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n  border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n  border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n  border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n  border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n  border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n  border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n  border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n  border-bottom: 0;\n}\n.panel > .table-responsive {\n  margin-bottom: 0;\n  border: 0;\n}\n.panel-group {\n  margin-bottom: 20px;\n}\n.panel-group .panel {\n  margin-bottom: 0;\n  border-radius: 4px;\n}\n.panel-group .panel + .panel {\n  margin-top: 5px;\n}\n.panel-group .panel-heading {\n  border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n  border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n  border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n  border-bottom: 1px solid #ddd;\n}\n.panel-default {\n  border-color: #ddd;\n}\n.panel-default > .panel-heading {\n  color: #333;\n  background-color: #f5f5f5;\n  border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n  color: #f5f5f5;\n  background-color: #333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ddd;\n}\n.panel-primary {\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #337ab7;\n}\n.panel-success {\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n  color: #dff0d8;\n  background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #d6e9c6;\n}\n.panel-info {\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n  color: #d9edf7;\n  background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #bce8f1;\n}\n.panel-warning {\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n  color: #fcf8e3;\n  background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #faebcc;\n}\n.panel-danger {\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n  color: #f2dede;\n  background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n  position: relative;\n  display: block;\n  height: 0;\n  padding: 0;\n  overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n.embed-responsive-16by9 {\n  padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n  padding-bottom: 75%;\n}\n.well {\n  min-height: 20px;\n  padding: 19px;\n  margin-bottom: 20px;\n  background-color: #f5f5f5;\n  border: 1px solid #e3e3e3;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n}\n.well blockquote {\n  border-color: #ddd;\n  border-color: rgba(0, 0, 0, .15);\n}\n.well-lg {\n  padding: 24px;\n  border-radius: 6px;\n}\n.well-sm {\n  padding: 9px;\n  border-radius: 3px;\n}\n.close {\n  float: right;\n  font-size: 21px;\n  font-weight: bold;\n  line-height: 1;\n  color: #000;\n  text-shadow: 0 1px 0 #fff;\n  filter: alpha(opacity=20);\n  opacity: .2;\n}\n.close:hover,\n.close:focus {\n  color: #000;\n  text-decoration: none;\n  cursor: pointer;\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\nbutton.close {\n  -webkit-appearance: none;\n  padding: 0;\n  cursor: pointer;\n  background: transparent;\n  border: 0;\n}\n.modal-open {\n  overflow: hidden;\n}\n.modal {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1050;\n  display: none;\n  overflow: hidden;\n  -webkit-overflow-scrolling: touch;\n  outline: 0;\n}\n.modal.fade .modal-dialog {\n  -webkit-transition: -webkit-transform .3s ease-out;\n       -o-transition:      -o-transform .3s ease-out;\n          transition:         transform .3s ease-out;\n  -webkit-transform: translate(0, -25%);\n      -ms-transform: translate(0, -25%);\n       -o-transform: translate(0, -25%);\n          transform: translate(0, -25%);\n}\n.modal.in .modal-dialog {\n  -webkit-transform: translate(0, 0);\n      -ms-transform: translate(0, 0);\n       -o-transform: translate(0, 0);\n          transform: translate(0, 0);\n}\n.modal-open .modal {\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: 10px;\n}\n.modal-content {\n  position: relative;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #999;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  outline: 0;\n  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n          box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n}\n.modal-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1040;\n  background-color: #000;\n}\n.modal-backdrop.fade {\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.modal-backdrop.in {\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.modal-header {\n  padding: 15px;\n  border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n  margin-top: -2px;\n}\n.modal-title {\n  margin: 0;\n  line-height: 1.42857143;\n}\n.modal-body {\n  position: relative;\n  padding: 15px;\n}\n.modal-footer {\n  padding: 15px;\n  text-align: right;\n  border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n  margin-bottom: 0;\n  margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n  margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n  margin-left: 0;\n}\n.modal-scrollbar-measure {\n  position: absolute;\n  top: -9999px;\n  width: 50px;\n  height: 50px;\n  overflow: scroll;\n}\n@media (min-width: 768px) {\n  .modal-dialog {\n    width: 600px;\n    margin: 30px auto;\n  }\n  .modal-content {\n    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n            box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n  }\n  .modal-sm {\n    width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg {\n    width: 900px;\n  }\n}\n.tooltip {\n  position: absolute;\n  z-index: 1070;\n  display: block;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 12px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: left;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  word-spacing: normal;\n  word-wrap: normal;\n  white-space: normal;\n  filter: alpha(opacity=0);\n  opacity: 0;\n\n  line-break: auto;\n}\n.tooltip.in {\n  filter: alpha(opacity=90);\n  opacity: .9;\n}\n.tooltip.top {\n  padding: 5px 0;\n  margin-top: -3px;\n}\n.tooltip.right {\n  padding: 0 5px;\n  margin-left: 3px;\n}\n.tooltip.bottom {\n  padding: 5px 0;\n  margin-top: 3px;\n}\n.tooltip.left {\n  padding: 0 5px;\n  margin-left: -3px;\n}\n.tooltip-inner {\n  max-width: 200px;\n  padding: 3px 8px;\n  color: #fff;\n  text-align: center;\n  background-color: #000;\n  border-radius: 4px;\n}\n.tooltip-arrow {\n  position: absolute;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n  bottom: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n  right: 5px;\n  bottom: 0;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n  bottom: 0;\n  left: 5px;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n  top: 50%;\n  left: 0;\n  margin-top: -5px;\n  border-width: 5px 5px 5px 0;\n  border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n  top: 50%;\n  right: 0;\n  margin-top: -5px;\n  border-width: 5px 0 5px 5px;\n  border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n  top: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n  top: 0;\n  right: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n  top: 0;\n  left: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.popover {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1060;\n  display: none;\n  max-width: 276px;\n  padding: 1px;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: left;\n  text-align: start;\n  text-decoration: none;\n  text-shadow: none;\n  text-transform: none;\n  letter-spacing: normal;\n  word-break: normal;\n  word-spacing: normal;\n  word-wrap: normal;\n  white-space: normal;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n          box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n\n  line-break: auto;\n}\n.popover.top {\n  margin-top: -10px;\n}\n.popover.right {\n  margin-left: 10px;\n}\n.popover.bottom {\n  margin-top: 10px;\n}\n.popover.left {\n  margin-left: -10px;\n}\n.popover-title {\n  padding: 8px 14px;\n  margin: 0;\n  font-size: 14px;\n  background-color: #f7f7f7;\n  border-bottom: 1px solid #ebebeb;\n  border-radius: 5px 5px 0 0;\n}\n.popover-content {\n  padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n  position: absolute;\n  display: block;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.popover > .arrow {\n  border-width: 11px;\n}\n.popover > .arrow:after {\n  content: \"\";\n  border-width: 10px;\n}\n.popover.top > .arrow {\n  bottom: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-color: #999;\n  border-top-color: rgba(0, 0, 0, .25);\n  border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n  bottom: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-color: #fff;\n  border-bottom-width: 0;\n}\n.popover.right > .arrow {\n  top: 50%;\n  left: -11px;\n  margin-top: -11px;\n  border-right-color: #999;\n  border-right-color: rgba(0, 0, 0, .25);\n  border-left-width: 0;\n}\n.popover.right > .arrow:after {\n  bottom: -10px;\n  left: 1px;\n  content: \" \";\n  border-right-color: #fff;\n  border-left-width: 0;\n}\n.popover.bottom > .arrow {\n  top: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-width: 0;\n  border-bottom-color: #999;\n  border-bottom-color: rgba(0, 0, 0, .25);\n}\n.popover.bottom > .arrow:after {\n  top: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-width: 0;\n  border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n  top: 50%;\n  right: -11px;\n  margin-top: -11px;\n  border-right-width: 0;\n  border-left-color: #999;\n  border-left-color: rgba(0, 0, 0, .25);\n}\n.popover.left > .arrow:after {\n  right: 1px;\n  bottom: -10px;\n  content: \" \";\n  border-right-width: 0;\n  border-left-color: #fff;\n}\n.carousel {\n  position: relative;\n}\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner > .item {\n  position: relative;\n  display: none;\n  -webkit-transition: .6s ease-in-out left;\n       -o-transition: .6s ease-in-out left;\n          transition: .6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n  .carousel-inner > .item {\n    -webkit-transition: -webkit-transform .6s ease-in-out;\n         -o-transition:      -o-transform .6s ease-in-out;\n            transition:         transform .6s ease-in-out;\n\n    -webkit-backface-visibility: hidden;\n            backface-visibility: hidden;\n    -webkit-perspective: 1000px;\n            perspective: 1000px;\n  }\n  .carousel-inner > .item.next,\n  .carousel-inner > .item.active.right {\n    left: 0;\n    -webkit-transform: translate3d(100%, 0, 0);\n            transform: translate3d(100%, 0, 0);\n  }\n  .carousel-inner > .item.prev,\n  .carousel-inner > .item.active.left {\n    left: 0;\n    -webkit-transform: translate3d(-100%, 0, 0);\n            transform: translate3d(-100%, 0, 0);\n  }\n  .carousel-inner > .item.next.left,\n  .carousel-inner > .item.prev.right,\n  .carousel-inner > .item.active {\n    left: 0;\n    -webkit-transform: translate3d(0, 0, 0);\n            transform: translate3d(0, 0, 0);\n  }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  display: block;\n}\n.carousel-inner > .active {\n  left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.carousel-inner > .next {\n  left: 100%;\n}\n.carousel-inner > .prev {\n  left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n  left: 0;\n}\n.carousel-inner > .active.left {\n  left: -100%;\n}\n.carousel-inner > .active.right {\n  left: 100%;\n}\n.carousel-control {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 15%;\n  font-size: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n  background-color: rgba(0, 0, 0, 0);\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.carousel-control.left {\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control.right {\n  right: 0;\n  left: auto;\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n  color: #fff;\n  text-decoration: none;\n  filter: alpha(opacity=90);\n  outline: 0;\n  opacity: .9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n  position: absolute;\n  top: 50%;\n  z-index: 5;\n  display: inline-block;\n  margin-top: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n  left: 50%;\n  margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n  right: 50%;\n  margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n  width: 20px;\n  height: 20px;\n  font-family: serif;\n  line-height: 1;\n}\n.carousel-control .icon-prev:before {\n  content: '\\2039';\n}\n.carousel-control .icon-next:before {\n  content: '\\203a';\n}\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  padding-left: 0;\n  margin-left: -30%;\n  text-align: center;\n  list-style: none;\n}\n.carousel-indicators li {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  margin: 1px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #000 \\9;\n  background-color: rgba(0, 0, 0, 0);\n  border: 1px solid #fff;\n  border-radius: 10px;\n}\n.carousel-indicators .active {\n  width: 12px;\n  height: 12px;\n  margin: 0;\n  background-color: #fff;\n}\n.carousel-caption {\n  position: absolute;\n  right: 15%;\n  bottom: 20px;\n  left: 15%;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n}\n.carousel-caption .btn {\n  text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-prev,\n  .carousel-control .icon-next {\n    width: 30px;\n    height: 30px;\n    margin-top: -10px;\n    font-size: 30px;\n  }\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .icon-prev {\n    margin-left: -10px;\n  }\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-next {\n    margin-right: -10px;\n  }\n  .carousel-caption {\n    right: 20%;\n    left: 20%;\n    padding-bottom: 30px;\n  }\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n  display: table;\n  content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n  clear: both;\n}\n.center-block {\n  display: block;\n  margin-right: auto;\n  margin-left: auto;\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  font: 0/0 a;\n  color: transparent;\n  text-shadow: none;\n  background-color: transparent;\n  border: 0;\n}\n.hidden {\n  display: none !important;\n}\n.affix {\n  position: fixed;\n}\n@-ms-viewport {\n  width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n  display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n  display: none !important;\n}\n@media (max-width: 767px) {\n  .visible-xs {\n    display: block !important;\n  }\n  table.visible-xs {\n    display: table !important;\n  }\n  tr.visible-xs {\n    display: table-row !important;\n  }\n  th.visible-xs,\n  td.visible-xs {\n    display: table-cell !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-block {\n    display: block !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline {\n    display: inline !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm {\n    display: block !important;\n  }\n  table.visible-sm {\n    display: table !important;\n  }\n  tr.visible-sm {\n    display: table-row !important;\n  }\n  th.visible-sm,\n  td.visible-sm {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-block {\n    display: block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md {\n    display: block !important;\n  }\n  table.visible-md {\n    display: table !important;\n  }\n  tr.visible-md {\n    display: table-row !important;\n  }\n  th.visible-md,\n  td.visible-md {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-block {\n    display: block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg {\n    display: block !important;\n  }\n  table.visible-lg {\n    display: table !important;\n  }\n  tr.visible-lg {\n    display: table-row !important;\n  }\n  th.visible-lg,\n  td.visible-lg {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-block {\n    display: block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (max-width: 767px) {\n  .hidden-xs {\n    display: none !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .hidden-sm {\n    display: none !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .hidden-md {\n    display: none !important;\n  }\n}\n@media (min-width: 1200px) {\n  .hidden-lg {\n    display: none !important;\n  }\n}\n.visible-print {\n  display: none !important;\n}\n@media print {\n  .visible-print {\n    display: block !important;\n  }\n  table.visible-print {\n    display: table !important;\n  }\n  tr.visible-print {\n    display: table-row !important;\n  }\n  th.visible-print,\n  td.visible-print {\n    display: table-cell !important;\n  }\n}\n.visible-print-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-block {\n    display: block !important;\n  }\n}\n.visible-print-inline {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline {\n    display: inline !important;\n  }\n}\n.visible-print-inline-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline-block {\n    display: inline-block !important;\n  }\n}\n@media print {\n  .hidden-print {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap.css.map */\n"
  },
  {
    "path": "app/public/lib/bootstrap/js/bootstrap.js",
    "content": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under the MIT license\n */\n\nif (typeof jQuery === 'undefined') {\n  throw new Error('Bootstrap\\'s JavaScript requires jQuery')\n}\n\n+function ($) {\n  'use strict';\n  var version = $.fn.jquery.split(' ')[0].split('.')\n  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {\n    throw new Error('Bootstrap\\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')\n  }\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: transition.js v3.3.7\n * http://getbootstrap.com/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      WebkitTransition : 'webkitTransitionEnd',\n      MozTransition    : 'transitionend',\n      OTransition      : 'oTransitionEnd otransitionend',\n      transition       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n\n    return false // explicit for ie8 (  ._.)\n  }\n\n  // http://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false\n    var $el = this\n    $(this).one('bsTransitionEnd', function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n\n    if (!$.support.transition) return\n\n    $.event.special.bsTransitionEnd = {\n      bindType: $.support.transition.end,\n      delegateType: $.support.transition.end,\n      handle: function (e) {\n        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n      }\n    }\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: alert.js v3.3.7\n * http://getbootstrap.com/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.VERSION = '3.3.7'\n\n  Alert.TRANSITION_DURATION = 150\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = $(selector === '#' ? [] : selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.closest('.alert')\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      // detach from parent, fire event then clean up data\n      $parent.detach().trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one('bsTransitionEnd', removeElement)\n        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.alert\n\n  $.fn.alert             = Plugin\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: button.js v3.3.7\n * http://getbootstrap.com/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element  = $(element)\n    this.options   = $.extend({}, Button.DEFAULTS, options)\n    this.isLoading = false\n  }\n\n  Button.VERSION  = '3.3.7'\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state += 'Text'\n\n    if (data.resetText == null) $el.data('resetText', $el[val]())\n\n    // push to event loop to allow forms to submit\n    setTimeout($.proxy(function () {\n      $el[val](data[state] == null ? this.options[state] : data[state])\n\n      if (state == 'loadingText') {\n        this.isLoading = true\n        $el.addClass(d).attr(d, d).prop(d, true)\n      } else if (this.isLoading) {\n        this.isLoading = false\n        $el.removeClass(d).removeAttr(d).prop(d, false)\n      }\n    }, this), 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var changed = true\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n      if ($input.prop('type') == 'radio') {\n        if ($input.prop('checked')) changed = false\n        $parent.find('.active').removeClass('active')\n        this.$element.addClass('active')\n      } else if ($input.prop('type') == 'checkbox') {\n        if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false\n        this.$element.toggleClass('active')\n      }\n      $input.prop('checked', this.$element.hasClass('active'))\n      if (changed) $input.trigger('change')\n    } else {\n      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n      this.$element.toggleClass('active')\n    }\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  var old = $.fn.button\n\n  $.fn.button             = Plugin\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document)\n    .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      var $btn = $(e.target).closest('.btn')\n      Plugin.call($btn, 'toggle')\n      if (!($(e.target).is('input[type=\"radio\"], input[type=\"checkbox\"]'))) {\n        // Prevent double click on radios, and the double selections (so cancellation) on checkboxes\n        e.preventDefault()\n        // The target component still receive the focus\n        if ($btn.is('input,button')) $btn.trigger('focus')\n        else $btn.find('input:visible,button:visible').first().trigger('focus')\n      }\n    })\n    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n    })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: carousel.js v3.3.7\n * http://getbootstrap.com/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      = null\n    this.sliding     = null\n    this.interval    = null\n    this.$active     = null\n    this.$items      = null\n\n    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n  }\n\n  Carousel.VERSION  = '3.3.7'\n\n  Carousel.TRANSITION_DURATION = 600\n\n  Carousel.DEFAULTS = {\n    interval: 5000,\n    pause: 'hover',\n    wrap: true,\n    keyboard: true\n  }\n\n  Carousel.prototype.keydown = function (e) {\n    if (/input|textarea/i.test(e.target.tagName)) return\n    switch (e.which) {\n      case 37: this.prev(); break\n      case 39: this.next(); break\n      default: return\n    }\n\n    e.preventDefault()\n  }\n\n  Carousel.prototype.cycle = function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getItemIndex = function (item) {\n    this.$items = item.parent().children('.item')\n    return this.$items.index(item || this.$active)\n  }\n\n  Carousel.prototype.getItemForDirection = function (direction, active) {\n    var activeIndex = this.getItemIndex(active)\n    var willWrap = (direction == 'prev' && activeIndex === 0)\n                || (direction == 'next' && activeIndex == (this.$items.length - 1))\n    if (willWrap && !this.options.wrap) return active\n    var delta = direction == 'prev' ? -1 : 1\n    var itemIndex = (activeIndex + delta) % this.$items.length\n    return this.$items.eq(itemIndex)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || this.getItemForDirection(type, $active)\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var that      = this\n\n    if ($next.hasClass('active')) return (this.sliding = false)\n\n    var relatedTarget = $next[0]\n    var slideEvent = $.Event('slide.bs.carousel', {\n      relatedTarget: relatedTarget,\n      direction: direction\n    })\n    this.$element.trigger(slideEvent)\n    if (slideEvent.isDefaultPrevented()) return\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n      $nextIndicator && $nextIndicator.addClass('active')\n    }\n\n    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      $next.addClass(type)\n      $next[0].offsetWidth // force reflow\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one('bsTransitionEnd', function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger(slidEvent)\n          }, 0)\n        })\n        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n    } else {\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger(slidEvent)\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  var old = $.fn.carousel\n\n  $.fn.carousel             = Plugin\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  var clickHandler = function (e) {\n    var href\n    var $this   = $(this)\n    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n    if (!$target.hasClass('carousel')) return\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    Plugin.call($target, options)\n\n    if (slideIndex) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  }\n\n  $(document)\n    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      Plugin.call($carousel, $carousel.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: collapse.js v3.3.7\n * http://getbootstrap.com/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n/* jshint latedef: false */\n\n+function ($) {\n  'use strict';\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.$trigger      = $('[data-toggle=\"collapse\"][href=\"#' + element.id + '\"],' +\n                           '[data-toggle=\"collapse\"][data-target=\"#' + element.id + '\"]')\n    this.transitioning = null\n\n    if (this.options.parent) {\n      this.$parent = this.getParent()\n    } else {\n      this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n    }\n\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.VERSION  = '3.3.7'\n\n  Collapse.TRANSITION_DURATION = 350\n\n  Collapse.DEFAULTS = {\n    toggle: true\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var activesData\n    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n    if (actives && actives.length) {\n      activesData = actives.data('bs.collapse')\n      if (activesData && activesData.transitioning) return\n    }\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    if (actives && actives.length) {\n      Plugin.call(actives, 'hide')\n      activesData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')[dimension](0)\n      .attr('aria-expanded', true)\n\n    this.$trigger\n      .removeClass('collapsed')\n      .attr('aria-expanded', true)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse in')[dimension]('')\n      this.transitioning = 0\n      this.$element\n        .trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse in')\n      .attr('aria-expanded', false)\n\n    this.$trigger\n      .addClass('collapsed')\n      .attr('aria-expanded', false)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse')\n        .trigger('hidden.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n  Collapse.prototype.getParent = function () {\n    return $(this.options.parent)\n      .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n      .each($.proxy(function (i, element) {\n        var $element = $(element)\n        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n      }, this))\n      .end()\n  }\n\n  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n    var isOpen = $element.hasClass('in')\n\n    $element.attr('aria-expanded', isOpen)\n    $trigger\n      .toggleClass('collapsed', !isOpen)\n      .attr('aria-expanded', isOpen)\n  }\n\n  function getTargetFromTrigger($trigger) {\n    var href\n    var target = $trigger.attr('data-target')\n      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n    return $(target)\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.collapse\n\n  $.fn.collapse             = Plugin\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n    var $this   = $(this)\n\n    if (!$this.attr('data-target')) e.preventDefault()\n\n    var $target = getTargetFromTrigger($this)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $this.data()\n\n    Plugin.call($target, option)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: dropdown.js v3.3.7\n * http://getbootstrap.com/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=\"dropdown\"]'\n  var Dropdown = function (element) {\n    $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.VERSION = '3.3.7'\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = selector && $(selector)\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n  function clearMenus(e) {\n    if (e && e.which === 3) return\n    $(backdrop).remove()\n    $(toggle).each(function () {\n      var $this         = $(this)\n      var $parent       = getParent($this)\n      var relatedTarget = { relatedTarget: this }\n\n      if (!$parent.hasClass('open')) return\n\n      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return\n\n      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this.attr('aria-expanded', 'false')\n      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))\n    })\n  }\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we use a backdrop because click events don't delegate\n        $(document.createElement('div'))\n          .addClass('dropdown-backdrop')\n          .insertAfter($(this))\n          .on('click', clearMenus)\n      }\n\n      var relatedTarget = { relatedTarget: this }\n      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this\n        .trigger('focus')\n        .attr('aria-expanded', 'true')\n\n      $parent\n        .toggleClass('open')\n        .trigger($.Event('shown.bs.dropdown', relatedTarget))\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if (!isActive && e.which != 27 || isActive && e.which == 27) {\n      if (e.which == 27) $parent.find(toggle).trigger('focus')\n      return $this.trigger('click')\n    }\n\n    var desc = ' li:not(.disabled):visible a'\n    var $items = $parent.find('.dropdown-menu' + desc)\n\n    if (!$items.length) return\n\n    var index = $items.index(e.target)\n\n    if (e.which == 38 && index > 0)                 index--         // up\n    if (e.which == 40 && index < $items.length - 1) index++         // down\n    if (!~index)                                    index = 0\n\n    $items.eq(index).trigger('focus')\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.dropdown')\n\n      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown             = Plugin\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: modal.js v3.3.7\n * http://getbootstrap.com/javascript/#modals\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options             = options\n    this.$body               = $(document.body)\n    this.$element            = $(element)\n    this.$dialog             = this.$element.find('.modal-dialog')\n    this.$backdrop           = null\n    this.isShown             = null\n    this.originalBodyPad     = null\n    this.scrollbarWidth      = 0\n    this.ignoreBackdropClick = false\n\n    if (this.options.remote) {\n      this.$element\n        .find('.modal-content')\n        .load(this.options.remote, $.proxy(function () {\n          this.$element.trigger('loaded.bs.modal')\n        }, this))\n    }\n  }\n\n  Modal.VERSION  = '3.3.7'\n\n  Modal.TRANSITION_DURATION = 300\n  Modal.BACKDROP_TRANSITION_DURATION = 150\n\n  Modal.DEFAULTS = {\n    backdrop: true,\n    keyboard: true,\n    show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this.isShown ? this.hide() : this.show(_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.checkScrollbar()\n    this.setScrollbar()\n    this.$body.addClass('modal-open')\n\n    this.escape()\n    this.resize()\n\n    this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.$dialog.on('mousedown.dismiss.bs.modal', function () {\n      that.$element.one('mouseup.dismiss.bs.modal', function (e) {\n        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true\n      })\n    })\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(that.$body) // don't move modals dom position\n      }\n\n      that.$element\n        .show()\n        .scrollTop(0)\n\n      that.adjustDialog()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element.addClass('in')\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$dialog // wait for modal to slide in\n          .one('bsTransitionEnd', function () {\n            that.$element.trigger('focus').trigger(e)\n          })\n          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n        that.$element.trigger('focus').trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n    this.resize()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .off('click.dismiss.bs.modal')\n      .off('mouseup.dismiss.bs.modal')\n\n    this.$dialog.off('mousedown.dismiss.bs.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (document !== e.target &&\n            this.$element[0] !== e.target &&\n            !this.$element.has(e.target).length) {\n          this.$element.trigger('focus')\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keydown.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.resize = function () {\n    if (this.isShown) {\n      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n    } else {\n      $(window).off('resize.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.$body.removeClass('modal-open')\n      that.resetAdjustments()\n      that.resetScrollbar()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $(document.createElement('div'))\n        .addClass('modal-backdrop ' + animate)\n        .appendTo(this.$body)\n\n      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {\n        if (this.ignoreBackdropClick) {\n          this.ignoreBackdropClick = false\n          return\n        }\n        if (e.target !== e.currentTarget) return\n        this.options.backdrop == 'static'\n          ? this.$element[0].focus()\n          : this.hide()\n      }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one('bsTransitionEnd', callback)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      var callbackRemove = function () {\n        that.removeBackdrop()\n        callback && callback()\n      }\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.$backdrop\n          .one('bsTransitionEnd', callbackRemove)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callbackRemove()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n  // these following methods are used to handle overflowing modals\n\n  Modal.prototype.handleUpdate = function () {\n    this.adjustDialog()\n  }\n\n  Modal.prototype.adjustDialog = function () {\n    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n    this.$element.css({\n      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n    })\n  }\n\n  Modal.prototype.resetAdjustments = function () {\n    this.$element.css({\n      paddingLeft: '',\n      paddingRight: ''\n    })\n  }\n\n  Modal.prototype.checkScrollbar = function () {\n    var fullWindowWidth = window.innerWidth\n    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8\n      var documentElementRect = document.documentElement.getBoundingClientRect()\n      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)\n    }\n    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth\n    this.scrollbarWidth = this.measureScrollbar()\n  }\n\n  Modal.prototype.setScrollbar = function () {\n    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n    this.originalBodyPad = document.body.style.paddingRight || ''\n    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)\n  }\n\n  Modal.prototype.resetScrollbar = function () {\n    this.$body.css('padding-right', this.originalBodyPad)\n  }\n\n  Modal.prototype.measureScrollbar = function () { // thx walsh\n    var scrollDiv = document.createElement('div')\n    scrollDiv.className = 'modal-scrollbar-measure'\n    this.$body.append(scrollDiv)\n    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n    this.$body[0].removeChild(scrollDiv)\n    return scrollbarWidth\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option, _relatedTarget) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  var old = $.fn.modal\n\n  $.fn.modal             = Plugin\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) // strip for ie7\n    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    if ($this.is('a')) e.preventDefault()\n\n    $target.one('show.bs.modal', function (showEvent) {\n      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n      $target.one('hidden.bs.modal', function () {\n        $this.is(':visible') && $this.trigger('focus')\n      })\n    })\n    Plugin.call($target, option, this)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tooltip.js v3.3.7\n * http://getbootstrap.com/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       = null\n    this.options    = null\n    this.enabled    = null\n    this.timeout    = null\n    this.hoverState = null\n    this.$element   = null\n    this.inState    = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.VERSION  = '3.3.7'\n\n  Tooltip.TRANSITION_DURATION = 150\n\n  Tooltip.DEFAULTS = {\n    animation: true,\n    placement: 'top',\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',\n    trigger: 'hover focus',\n    title: '',\n    delay: 0,\n    html: false,\n    container: false,\n    viewport: {\n      selector: 'body',\n      padding: 0\n    }\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled   = true\n    this.type      = type\n    this.$element  = $(element)\n    this.options   = this.getOptions(options)\n    this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))\n    this.inState   = { click: false, hover: false, focus: false }\n\n    if (this.$element[0] instanceof document.constructor && !this.options.selector) {\n      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')\n    }\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    options = $.extend({}, this.getDefaults(), this.$element.data(), options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay,\n        hide: options.delay\n      }\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    if (obj instanceof $.Event) {\n      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true\n    }\n\n    if (self.tip().hasClass('in') || self.hoverState == 'in') {\n      self.hoverState = 'in'\n      return\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.isInStateTrue = function () {\n    for (var key in this.inState) {\n      if (this.inState[key]) return true\n    }\n\n    return false\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    if (obj instanceof $.Event) {\n      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false\n    }\n\n    if (self.isInStateTrue()) return\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.' + this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n      if (e.isDefaultPrevented() || !inDom) return\n      var that = this\n\n      var $tip = this.tip()\n\n      var tipId = this.getUID(this.type)\n\n      this.setContent()\n      $tip.attr('id', tipId)\n      this.$element.attr('aria-describedby', tipId)\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n        .data('bs.' + this.type, this)\n\n      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n      this.$element.trigger('inserted.bs.' + this.type)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var orgPlacement = placement\n        var viewportDim = this.getPosition(this.$viewport)\n\n        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :\n                    placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :\n                    placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :\n                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n\n      var complete = function () {\n        var prevHoverState = that.hoverState\n        that.$element.trigger('shown.bs.' + that.type)\n        that.hoverState = null\n\n        if (prevHoverState == 'out') that.leave(that)\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        $tip\n          .one('bsTransitionEnd', complete)\n          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n        complete()\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function (offset, placement) {\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  += marginTop\n    offset.left += marginLeft\n\n    // $.fn.offset doesn't round pixel values\n    // so we use setOffset directly with our own function B-0\n    $.offset.setOffset($tip[0], $.extend({\n      using: function (props) {\n        $tip.css({\n          top: Math.round(props.top),\n          left: Math.round(props.left)\n        })\n      }\n    }, offset), 0)\n\n    $tip.addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      offset.top = offset.top + height - actualHeight\n    }\n\n    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n    if (delta.left) offset.left += delta.left\n    else offset.top += delta.top\n\n    var isVertical          = /top|bottom/.test(placement)\n    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n    $tip.offset(offset)\n    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n  }\n\n  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {\n    this.arrow()\n      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n      .css(isVertical ? 'top' : 'left', '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function (callback) {\n    var that = this\n    var $tip = $(this.$tip)\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n      if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.\n        that.$element\n          .removeAttr('aria-describedby')\n          .trigger('hidden.bs.' + that.type)\n      }\n      callback && callback()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && $tip.hasClass('fade') ?\n      $tip\n        .one('bsTransitionEnd', complete)\n        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n      complete()\n\n    this.hoverState = null\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function ($element) {\n    $element   = $element || this.$element\n\n    var el     = $element[0]\n    var isBody = el.tagName == 'BODY'\n\n    var elRect    = el.getBoundingClientRect()\n    if (elRect.width == null) {\n      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n    }\n    var isSvg = window.SVGElement && el instanceof window.SVGElement\n    // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.\n    // See https://github.com/twbs/bootstrap/issues/20280\n    var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())\n    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n    return $.extend({}, elRect, scroll, outerDims, elOffset)\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n  }\n\n  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n    var delta = { top: 0, left: 0 }\n    if (!this.$viewport) return delta\n\n    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n    var viewportDimensions = this.getPosition(this.$viewport)\n\n    if (/right|left/.test(placement)) {\n      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll\n      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n      if (topEdgeOffset < viewportDimensions.top) { // top overflow\n        delta.top = viewportDimensions.top - topEdgeOffset\n      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n      }\n    } else {\n      var leftEdgeOffset  = pos.left - viewportPadding\n      var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n      if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n        delta.left = viewportDimensions.left - leftEdgeOffset\n      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow\n        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n      }\n    }\n\n    return delta\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.getUID = function (prefix) {\n    do prefix += ~~(Math.random() * 1000000)\n    while (document.getElementById(prefix))\n    return prefix\n  }\n\n  Tooltip.prototype.tip = function () {\n    if (!this.$tip) {\n      this.$tip = $(this.options.template)\n      if (this.$tip.length != 1) {\n        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')\n      }\n    }\n    return this.$tip\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = this\n    if (e) {\n      self = $(e.currentTarget).data('bs.' + this.type)\n      if (!self) {\n        self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n        $(e.currentTarget).data('bs.' + this.type, self)\n      }\n    }\n\n    if (e) {\n      self.inState.click = !self.inState.click\n      if (self.isInStateTrue()) self.enter(self)\n      else self.leave(self)\n    } else {\n      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n    }\n  }\n\n  Tooltip.prototype.destroy = function () {\n    var that = this\n    clearTimeout(this.timeout)\n    this.hide(function () {\n      that.$element.off('.' + that.type).removeData('bs.' + that.type)\n      if (that.$tip) {\n        that.$tip.detach()\n      }\n      that.$tip = null\n      that.$arrow = null\n      that.$viewport = null\n      that.$element = null\n    })\n  }\n\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data && /destroy|hide/.test(option)) return\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip             = Plugin\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: popover.js v3.3.7\n * http://getbootstrap.com/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.VERSION  = '3.3.7'\n\n  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right',\n    trigger: 'click',\n    content: '',\n    template: '<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events\n      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'\n    ](content)\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n            o.content.call($e[0]) :\n            o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data && /destroy|hide/.test(option)) return\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.popover\n\n  $.fn.popover             = Plugin\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: scrollspy.js v3.3.7\n * http://getbootstrap.com/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    this.$body          = $(document.body)\n    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target || '') + ' .nav li > a'\n    this.offsets        = []\n    this.targets        = []\n    this.activeTarget   = null\n    this.scrollHeight   = 0\n\n    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.VERSION  = '3.3.7'\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.getScrollHeight = function () {\n    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var that          = this\n    var offsetMethod  = 'offset'\n    var offsetBase    = 0\n\n    this.offsets      = []\n    this.targets      = []\n    this.scrollHeight = this.getScrollHeight()\n\n    if (!$.isWindow(this.$scrollElement[0])) {\n      offsetMethod = 'position'\n      offsetBase   = this.$scrollElement.scrollTop()\n    }\n\n    this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#./.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && $href.is(':visible')\n          && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        that.offsets.push(this[0])\n        that.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.getScrollHeight()\n    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (this.scrollHeight != scrollHeight) {\n      this.refresh()\n    }\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n    }\n\n    if (activeTarget && scrollTop < offsets[0]) {\n      this.activeTarget = null\n      return this.clear()\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])\n        && this.activate(targets[i])\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    this.clear()\n\n    var selector = this.selector +\n      '[data-target=\"' + target + '\"],' +\n      this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length) {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate.bs.scrollspy')\n  }\n\n  ScrollSpy.prototype.clear = function () {\n    $(this.selector)\n      .parentsUntil(this.options.target, '.active')\n      .removeClass('active')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy             = Plugin\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load.bs.scrollspy.data-api', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      Plugin.call($spy, $spy.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tab.js v3.3.7\n * http://getbootstrap.com/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    // jscs:disable requireDollarBeforejQueryAssignment\n    this.element = $(element)\n    // jscs:enable requireDollarBeforejQueryAssignment\n  }\n\n  Tab.VERSION = '3.3.7'\n\n  Tab.TRANSITION_DURATION = 150\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.data('target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var $previous = $ul.find('.active:last a')\n    var hideEvent = $.Event('hide.bs.tab', {\n      relatedTarget: $this[0]\n    })\n    var showEvent = $.Event('show.bs.tab', {\n      relatedTarget: $previous[0]\n    })\n\n    $previous.trigger(hideEvent)\n    $this.trigger(showEvent)\n\n    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n    var $target = $(selector)\n\n    this.activate($this.closest('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $previous.trigger({\n        type: 'hidden.bs.tab',\n        relatedTarget: $this[0]\n      })\n      $this.trigger({\n        type: 'shown.bs.tab',\n        relatedTarget: $previous[0]\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n          .removeClass('active')\n        .end()\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', false)\n\n      element\n        .addClass('active')\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', true)\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu').length) {\n        element\n          .closest('li.dropdown')\n            .addClass('active')\n          .end()\n          .find('[data-toggle=\"tab\"]')\n            .attr('aria-expanded', true)\n      }\n\n      callback && callback()\n    }\n\n    $active.length && transition ?\n      $active\n        .one('bsTransitionEnd', next)\n        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tab\n\n  $.fn.tab             = Plugin\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  var clickHandler = function (e) {\n    e.preventDefault()\n    Plugin.call($(this), 'show')\n  }\n\n  $(document)\n    .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n    .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: affix.js v3.3.7\n * http://getbootstrap.com/javascript/#affix\n * ========================================================================\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n\n    this.$target = $(this.options.target)\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element     = $(element)\n    this.affixed      = null\n    this.unpin        = null\n    this.pinnedOffset = null\n\n    this.checkPosition()\n  }\n\n  Affix.VERSION  = '3.3.7'\n\n  Affix.RESET    = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0,\n    target: window\n  }\n\n  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n    var scrollTop    = this.$target.scrollTop()\n    var position     = this.$element.offset()\n    var targetHeight = this.$target.height()\n\n    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n    if (this.affixed == 'bottom') {\n      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n    }\n\n    var initializing   = this.affixed == null\n    var colliderTop    = initializing ? scrollTop : position.top\n    var colliderHeight = initializing ? targetHeight : height\n\n    if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n    return false\n  }\n\n  Affix.prototype.getPinnedOffset = function () {\n    if (this.pinnedOffset) return this.pinnedOffset\n    this.$element.removeClass(Affix.RESET).addClass('affix')\n    var scrollTop = this.$target.scrollTop()\n    var position  = this.$element.offset()\n    return (this.pinnedOffset = position.top - scrollTop)\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var height       = this.$element.height()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n    var scrollHeight = Math.max($(document).height(), $(document.body).height())\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n    if (this.affixed != affix) {\n      if (this.unpin != null) this.$element.css('top', '')\n\n      var affixType = 'affix' + (affix ? '-' + affix : '')\n      var e         = $.Event(affixType + '.bs.affix')\n\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      this.affixed = affix\n      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n      this.$element\n        .removeClass(Affix.RESET)\n        .addClass(affixType)\n        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n    }\n\n    if (affix == 'bottom') {\n      this.$element.offset({\n        top: scrollHeight - height - offsetBottom\n      })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.affix\n\n  $.fn.affix             = Plugin\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop    != null) data.offset.top    = data.offsetTop\n\n      Plugin.call($spy, data)\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "app/public/lib/bootstrap/js/npm.js",
    "content": "// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.\nrequire('../../js/transition.js')\nrequire('../../js/alert.js')\nrequire('../../js/button.js')\nrequire('../../js/carousel.js')\nrequire('../../js/collapse.js')\nrequire('../../js/dropdown.js')\nrequire('../../js/modal.js')\nrequire('../../js/tooltip.js')\nrequire('../../js/popover.js')\nrequire('../../js/scrollspy.js')\nrequire('../../js/tab.js')\nrequire('../../js/affix.js')"
  },
  {
    "path": "app/public/lib/page/page.css",
    "content": "/*分页插件样式*/\n/**width: 1180px; margin: 0 auto; text-align: center; */\n.copot-page{width: 100%;color:#555555;font-size:12px;}\n#copot-page{padding: 10px 0; color: #a9a9a9;}\n.copot-page a{display:inline-block;padding:3px 8px;border:solid 1px #E7E7E7;border-radius:3px;margin-right:5px;text-decoration:none;font-size:12px;background:#fff; color: #a9a9a9;}\n.copot-page a:hover{background:#887FFE;color:#fff;}\n.copot-page a.nowPage{background:#887FFE;border:solid 1px #887FFE;color:#fff;} \n.copot-page .Page-search-span{margin:0 5px 0 15px;}\n.copot-page #Page-search{width:40px;height:28px;padding-left:10px;margin:0 3px;border:solid 1px #E7E7E7;}\n.copot-page #pageSearch{width:40px;height:28px;font-size:12px;border:solid 1px #887FFE;background:#887FFE;color:#fff;cursor:pointer; }"
  },
  {
    "path": "app/public/lib/page/page.js",
    "content": "/* eslint-disable */\n\nfunction Page(json) {\n\tthis.nowPage = parseInt(json.nowPage); //当前页\n\tthis.parent = json.parent; //分页内容所放的div元素\n\tthis.call = json.callback;\n\tthis.totalCount = json.totalCount;\n\tthis.pageSize = json.pageSize;\n\tthis.totalPage = Math.ceil(this.totalCount / this.pageSize); //总页数 \n\tthis.html = \"\";\n\tthis.setting = json.setting || {\n\t\tdefaultPage: 5, //默认展示的页数\n\t\tfirstPageText: \"首页\", //第一页的字 （可以是：Home）\n\t\tprevPageText: \"上一页\", //上一页的字 \n\t\tnextPageText: \"下一页\", //下一页的字\n\t\tlastPageText: \"尾页\" //尾页的字\n\t};\n\tthis.centPage = Math.ceil(this.setting.defaultPage / 2); //中间页\n\tthis.init(); //初始化\n}\n//初始化函数\nPage.prototype.init = function () {\n\tthis.parent.html(\"\"); //清空\n\tthis.firstPage(); //首页\n\tthis.prevPageText(); //上一页\n\tthis.everyPage(); //分页\n\tthis.nextPage(); //下一页\n\tthis.lastPage(); //尾页\n\tthis.totalPageText(); //页数显示信息\n\tthis.parent.append(this.html);\n\tthis.callback();\n}\n\n//循环页数\nPage.prototype.everyPage = function () {\n\tif (this.totalPage <= this.setting.defaultPage) {\n\t\tfor (var i = 1; i <= this.totalPage; i++) {\n\t\t\tif (this.nowPage == i) {\n\t\t\t\tthis.html += \"<a href='#\" + i + \"' class='nowPage'>\" + i + \"</a>\";\n\t\t\t} else {\n\t\t\t\tthis.html += \"<a href='#\" + i + \"'>\" + i + \"</a>\";\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor (var i = 1; i <= this.setting.defaultPage; i++) {\n\t\t\tvar page1 = this.nowPage - this.centPage + i;\n\t\t\tvar page2 = this.totalPage - this.setting.defaultPage + i;\n\n\t\t\tif (this.nowPage < this.centPage) {\n\t\t\t\tif (this.nowPage == i) {\n\t\t\t\t\tthis.html += \"<a href='#\" + i + \"' class='nowPage'>\" + i + \"</a>\";\n\t\t\t\t} else {\n\t\t\t\t\tthis.html += \"<a href='#\" + i + \"'>\" + i + \"</a>\";\n\t\t\t\t}\n\t\t\t} else if (this.nowPage > this.totalPage - this.centPage) {\n\t\t\t\tif (this.setting.defaultPage - (this.totalPage - this.nowPage) == i) {\n\t\t\t\t\tthis.html += \"<a href='#\" + this.nowPage + \"' class='nowPage'>\" + this.nowPage + \"</a>\";\n\t\t\t\t} else {\n\t\t\t\t\tthis.html += \"<a href='#\" + page2 + \"'>\" + page2 + \"</a>\";\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (this.centPage == i) {\n\t\t\t\t\tthis.html += \"<a href='#\" + page1 + \"' class='nowPage'>\" + page1 + \"</a>\";\n\t\t\t\t} else {\n\t\t\t\t\tthis.html += \"<a href='#\" + page1 + \"'>\" + page1 + \"</a>\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n//首页\nPage.prototype.firstPage = function () {\n\tif (this.nowPage > this.centPage && this.totalPage >= this.setting.defaultPage + 1) {\n\t\tthis.html += \"<a href='#1'>\" + this.setting.firstPageText + \"</a>\";\n\t}\n}\n//上一页\nPage.prototype.prevPageText = function () {\n\tif (this.nowPage >= 2) {\n\t\tthis.html += \"<a href='#\" + (this.nowPage - 1) + \"'>\" + this.setting.prevPageText + \"</a>\";\n\t}\n}\n\n//下一页\nPage.prototype.nextPage = function () {\n\tif (this.totalPage - this.nowPage >= 1) {\n\t\tthis.html += \"<a href='#\" + (this.nowPage + 1) + \"'>\" + this.setting.nextPageText + \"</a>\";\n\t}\n}\n\n//尾页\nPage.prototype.lastPage = function () {\n\tif (this.totalPage - this.nowPage >= this.centPage && this.totalPage > this.setting.defaultPage) {\n\t\tthis.html += \"<a href='#\" + this.totalPage + \"'>\" + this.setting.lastPageText + \"</a>\";\n\t}\n}\n\n//总共页数\nPage.prototype.totalPageText = function () {\n\tthis.html += \"<span class='page-msg'>共<span>\" + this.totalCount + \"</span>条记录  每页<span>\" + this.pageSize + \"</span>条  第<span>\" + this.nowPage + \"</span>页/共<span>\" + this.totalPage + \"</span>页</span>\";\n}\n\n//点击分页执行的函数\nPage.prototype.callback = function () {\n\tvar This = this;\n\n\t//给每个a绑定事件\n\tthis.parent.find(\"a\").bind(\"click\", function () {\n\t\twindow.scrollTo(0, 0)\n\t\t// Layer.loading({width:300,height:110,srcType:3,title:\"正在加载中，请稍后...\"});\n\t\tThis.parent.html(\"\"); //清空\n\t\tvar nowPage = $(this).attr(\"href\").substring(1);\n\t\t//写入分页\n\t\tnew Page({\n\t\t\tparent: This.parent,\n\t\t\tnowPage: nowPage,\n\t\t\ttotalPage: This.totalPage,\n\t\t\tpageSize: This.pageSize,\n\t\t\ttotalCount: This.totalCount,\n\t\t\tsetting: This.setting,\n\t\t\tcallback: This.call\n\t\t});\n\n\t\tThis.call(nowPage, This.totalPage); //传参\n\n\t\treturn false;\n\t});\n\n}"
  },
  {
    "path": "app/public/lib/popup/popup.css",
    "content": "/*layer-mobile-css*/\n@keyframes popScaleShow{from{opacity: 0;}to{opacity: 1;}}\n@-webkit-keyframes popScaleShow{from{opacity: 0;}to{opacity: 1;}}\n@keyframes popScale{from{opacity: 0;transform:scale(0.5,0.5);}to{opacity: 1;transform:scale(1,1);}}\n@-webkit-keyframes popScale{from{opacity: 0;-webkit-transform:scale(0.5,0.5);}to{opacity: 1;-webkit-transform:scale(1,1);}}\niframe { display: block;}\n.popup{width:100%;box-sizing:border-box;height:100%;position: fixed;left:0;top:0;z-index:100000;animation:popScaleShow 300ms linear;-webkit-animation:popScaleShow 300ms linear;}\n.popup .mask{width:100%;height:100%;position: absolute;left:0;top:0;background: rgba(0,0,0,.6);z-index:-1;}\n.popup .popup_main{position:fixed;left:50%;top:50%;min-width:350px;min-height:100px;overflow:hidden;max-width:90%!important;\n\ttransform:translateX(-50%);-webkit-transform:translateX(-50%);background:transparent;\n}\n.maminContent{width:100%;height:100%;background: #fff;overflow: hidden;animation:popScale 300ms;-webkit-animation:popScale 300ms;}\n.popup .maskMain{background:rgba(0,0,0,.6);color:#fff;}\n.popup .header_poup{height:30px;padding-left:46px;margin-top:10px;line-height:50px;position: relative;font-size:16px;}\n.popup .header_poup span{display:block;width:15px;height:15px;cursor:pointer;background: url('/public/img/PopLayer-close.png') no-repeat top center;background-size:80% 80%;position: absolute;right:20px;top:15px;}\n.popup .header_poup i{width:25px;height:25px;display:inline-block;background: url('/public/img/icon-1.png') no-repeat center center;position: absolute;\n\tleft:15px;top:10px;\n}\n.popup .header_poup i.icon-1{background: url('/public/img/icon-1.png') no-repeat center center;}\n.popup .header_poup i.icon-2{background: url('/public/img/icon-2.png') no-repeat center center;}\n.popup .header_poup i.icon-3{background: url('/public/img/icon-3.png') no-repeat center center;}\n.popup .content{line-height:25px;padding:30px 20px 10px 50px;color:#888;}\n.popup .footer{height:60px;width:100%;line-height: 40px;position: relative;margin-top:20px;padding-left:50px;padding-top: 0;background: #fff;}\n.popup .footer span{font-size:14px;height:35px;line-height:35px;cursor:pointer;display:inline-block;text-align:center;color:#4a4a4a;}\n.popup .footer .yes{padding:0px 25px;background: #ff4200;color:#fff;border-radius:5px;}\n.popup .footer .yes:hover,\n.popup .footer .yes5:hover{background:#ef6536;}\n.popup .footer .yes5{}\n.popup .footer .yesok{background:#ff4200;}\n.popup .footer .no{padding:0 15px;border:solid 1px #999;border-radius: 5px;margin-left:15px;}\n.popup .footer .no:hover{background:#e8e3e3;}\n.popup-hide .popup_main{min-width:100px;}\n.popup-hide .popup_main .content{line-height:50px;}\n.popup-hide .content{text-align:center;}\n.popup-loading{cursor: wait;}\n.popup-loading .popup_main{min-width:150px;height:60px;padding:0 15px 0 5px;}\n.popup-loading .popup_main .content{line-height:60px;}\n.popup-loading .popup_main .content:before{content: \"\";display: block;width:40px;height:60px;background: url('/public/img/loading.gif') no-repeat center center;float:left;\n\tbackground-size: 60%;margin-top:-15px;\n}\n.popup-loading .content{text-align:center;}\n.popup .html{line-height:25px;}\n.popup-iframe .popup_main{width:80%;height:80%;left:10%;top:10%;}\n.popup-iframe .popup_main .contentios{overflow-scrolling:touch;-webkit-overflow-scrolling:touch;overflow-y: scroll; }\n.popup .yesHtml{text-align:center;cursor:pointer;color:#f48e08;padding-top:15px;border-top:solid 1px #dcdcdc;}\n.popup .yesHtml:hover{color:#b76b07;}\n.popup .popup_main .content-no{padding-top:12px;padding-bottom:12px;line-height:25px;}\n.popup .popup_main .miss_popup{border-radius: 5px!important;color:#fff;}\n.popup .popup_main .miss_popup .content-no{padding-left:20px;color:#fff;}"
  },
  {
    "path": "app/public/lib/popup/popup.js",
    "content": "/* eslint-disable */\n\n//构造函数\nfunction PopLayer(){\n    this.setting={};\n}\n/*extent json函数*/\nPopLayer.prototype.extend=function(json1,json2){\n    var newJson=json1;\n    for(var i in json1){\n        for( var j in json2){\n            newJson[j]=json2[j];\n        }\n    }\n    return newJson;\n}\n/*公共变量*/\nPopLayer.prototype.varLiang=function(json){\n    this.setting={\n        time:2000,\n        header:\"信息\",     //头部信息\n        haveHeader:true,  //是否显示头部\n        maskHide:true,   //是否点击遮罩隐藏\n        closeBut:false,    //是否需要关闭按钮\n        loadingImg:'loading-1',\n        type:\"msg\",  \n        style:'',\n        title:\"请填写提示信息！\",\n        yesHtml:'',\n        imgs:{\n           msg:'icon-1',\n           error:'icon-2', \n           success:'icon-3', \n        },\n        yes:function(){},\n        callback:function(){},\n    };\n\tif(json){\n        this.setting=this.extend(this.setting,json); //继承\n    }\n}\n/*页面层*/\nPopLayer.prototype.customHtml=function(json){\n\tthis.varLiang(json);\n    var str='<div class=\"popup\">';\n\tstr+=this.setting.maskHide?'<div class=\"mask\" onclick=\"closeThisPopup(this)\"></div>':'<div class=\"mask\"></div>';\n    str+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent\">';\n    if(this.setting.haveHeader){\n    \tstr+=this.setting.closeBut?'<div class=\"header_poup\">'+this.setting.header+'</div>':'<div class=\"header_poup\">'+this.setting.header+'<span onclick=\"closeThisPopup(this)\"></span></div>';\n    }\n    str+='<div class=\"content html\">'+this.setting.html+'</div>';\n    str+='</div></div></div>';\n\t         \n    addendHtml(str);\n    middle(); //居中\n    this.setting.callback();\n\t\n}\n/*iframe层*/\nPopLayer.prototype.iframe=function(json){\n    win.showLoading();\n\tthis.varLiang(json);\n    var str='<div class=\"popup popup-iframe\">';\n\tstr+=this.setting.maskHide?'<div class=\"mask\" onclick=\"closeThisPopup(this)\"></div>':'<div class=\"mask\"></div>';\n    str+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent\">';\n    if(this.setting.haveHeader){\n    \tstr+=this.setting.closeBut?'<div class=\"header_poup\">'+this.setting.header+'</div>':'<div class=\"header_poup\">'+this.setting.header+'<span onclick=\"closeThisPopup(this)\"></span></div>';\n    }\n    var isIos=navigator.userAgent.indexOf('Mac OS X')>-1;\n    var isIpad=navigator.userAgent.indexOf('iPad;')>-1;\n    if(isIos || isIpad){\n       str+='<div class=\"content html contentios\">'; \n    }else{\n       str+='<div class=\"content html\">'; \n    };\n    str+='<iframe id=\"iframePage\" src=\"'+json.src+'\" width=\"100%\" height=\"100%\" frameborder=\"0\"></iframe></div>';\n    if(this.setting.yesHtml){str+='<div class=\"yesHtml\" onclick=\"closeIframePopup(this)\">'+this.setting.yesHtml+'</div>';}\n    str+='</div></div></div>';\n\t         \n    addendHtml(str);\n    var height=0;\n    if(this.setting.yesHtml){\n        height=$('div.popup_main').height()-20;\n    }else{\n        height=$('div.popup_main').height();     \n    }\n\tif(this.setting.haveHeader){\n\t\t$('div.content').css({height:(height-80)+'px'});\n\t}else{\n\t\t$('div.content').css({height:(height-50)+'px'});\n\t};\n    var _this=this;\n    /*横竖屏切换时执行*/\n    $(window).on('resize',function() {\n        var heightTwo=0;\n        if(_this.setting.yesHtml){\n            heightTwo=$('div.popup_main').height()-20;\n        }else{\n            heightTwo=$('div.popup_main').height();     \n        }\n        if(_this.setting.haveHeader){\n            $('div.content').css({height:(heightTwo-80)+'px'});\n        }else{\n            $('div.content').css({height:(heightTwo-50)+'px'});\n        }\n    });\n    setTimeout(function(){win.hideLoading();},1000);\n    window.closeIframePopup=function(obj){\n        $(obj).parents('div.popup').remove();\n        if(json.callback){json.callback()}\n    }\n}\n//信息层\nPopLayer.prototype.alert=function(json){\n    this.varLiang(json);\n    var str='<div class=\"popup\">';\n\tstr+=this.setting.maskHide?'<div class=\"mask\" onclick=\"closeThisPopup(this)\"></div>':'<div class=\"mask\"></div>';\n    str+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent\">';\n    if(this.setting.haveHeader){\n    \tstr+=this.setting.closeBut?'<div class=\"header_poup\"><i class=\"'+this.setting.imgs[json.type]+'\"></i>\\\n        '+this.setting.header+'</div>':\n        '<div class=\"header_poup\"><i class=\"'+this.setting.imgs[json.type]+'\"></i>'+this.setting.header+'<span onclick=\"closeThisPopup(this)\"></span></div>';\n    }\n    str+='<div class=\"content\">'+this.setting.title+'</div>';\n    str+='<div class=\"footer\"><span class=\"yes yesok\">确定</span></div>';\n    str+='</div></div></div>';\n\tvar This=this;\n    addendHtml(str);\n    $('span.yes').click(function(){\n        if(json.yes){\n            dosomePopup(this,This.setting.yes);\n        }else{\n            closeThisPopup(this);\n        }\n    });\n    middle(); //居中\n}\n//确认层\nPopLayer.prototype.confirm=function(json){\n    this.varLiang(json);\n    var This=this;\n    var str='<div class=\"popup\">';\n    \tstr+=this.setting.maskHide?'<div class=\"mask\" onclick=\"closeThisPopup(this)\"></div>':'<div class=\"mask\"></div>';\n\t\tstr+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent\">';\n        if(this.setting.haveHeader){\n        \tstr+='<div class=\"header_poup\"><i class=\"'+this.setting.imgs[json.type]+'\"></i>'+this.setting.header+'<span onclick=\"closeThisPopup(this)\"></span></div>';\n        }\n        str+='<div class=\"content\">'+this.setting.title+'</div>';\n        str+='<div class=\"footer\"><span class=\"yes yes5 yesok\">确定</span><span class=\"no\">取消</span></div>';\n        str+='</div></div></div>';     \n    addendHtml(str);\n    $('span.yes').click(function(){\n        dosomePopup(this,This.setting.yes);\n    });\n    $('span.no').click(function(){\n        dosomePopup(this,This.setting.no);\n    });\n    middle(); //居中\n}\n/*2s消失*/\nPopLayer.prototype.miss=function(json){\n\tthis.varLiang(json);\n    this.setting.haveHeader=true;\n    var str='<div class=\"popup popup-hide\" onclick=\"closeThisPopup(this)\">';\n\t\tstr+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent maskMain miss_popup\">';\n        str+='<div class=\"content content-no\">'+this.setting.title+'</div>';\n        str+='</div></div></div>';     \n    addendHtml(str);\n    middle(); //居中\n    return new Promise((res,ret)=>{\n        setTimeout(function(){\n            $('div.popup-hide').remove();\n            res();\n        },this.setting.time);\n    })\n}\n/*2s消失*/\nPopLayer.prototype.missWaring=function(json){\n\tthis.varLiang(json);\n    this.setting.haveHeader=true;\n    var str='<div class=\"popup popup-hide\" onclick=\"closeThisPopup(this)\">';\n\t\tstr+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent maskMain miss_popup\">';\n        str+='<div class=\"content content-no\" style=\"color:#ff0000\">'+this.setting.title+'</div>';\n        str+='</div></div></div>';     \n    addendHtml(str);\n    middle(); //居中\n    return new Promise((res,ret)=>{\n        setTimeout(function(){\n            $('div.popup-hide').remove();\n            res();\n        },this.setting.time);\n    })\n}\n\n//加载层\nPopLayer.prototype.loading=function(json){\n    this.varLiang(json);\n    if(json.title){\n        this.setting.title= json.title;\n    }else{\n        this.setting.title=\"加载中,请稍后...\";  \n    };\n    var str='<div class=\"popup popup-loading\">';\n\t\tstr+='<div class=\"popup_main\" style=\"'+this.setting.style+'\"><div class=\"maminContent maskMain\">';\n        str+='<div class=\"content content-no\">'+this.setting.title+'</div>';\n        str+='</div></div></div>';     \n    addendHtml(str);\n    middle(); //居中\n}\n/*关闭加载层*/\nPopLayer.prototype.closeLoading=function(){\n\t$('div.popup-loading').remove();\n}\n/*关闭iframe层*/\nPopLayer.prototype.closeIframe=function(){\n\t$(\".popup-iframe\", parent.document).remove();\n}\n/*确认回调函数*/\nwindow.dosomePopup=function(obj,yes){\n\tcloseThisPopup(obj);\n\tyes();\n}\nwindow.addendHtml=function(str){\n    if(!$('div.popup').length){\n        $('body').append(str);\n    }\n}\n/*关闭遮罩*/\nwindow.closeThisPopup=function(obj){\n\tif($(obj).parents('div.popup').length){\n        $(obj).parents('div.popup').remove();\n    }else{\n        $(obj).remove();\n    }\n}\n//居中函数\nwindow.middle=function(){\n\tvar main=$('div.popup_main');\n    main.css({marginTop:-main.height()/2-20+\"px\"}); \n}\n\nwindow.popup = new PopLayer();\n\n\n\n\n\n"
  },
  {
    "path": "app/router/api.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    const apiV1Router = app.router.namespace('/api/v1/');\n    const { controller, io, middleware } = app;\n    const {\n        files,\n        team,\n        application,\n        assets,\n        util,\n        environment,\n        email,\n        build,\n        commtask,\n        logs,\n        user,\n    } = controller.api;\n\n    // 校验用户是否登录中间件\n    const tokenRequired = middleware.tokenRequired();\n    // -----------------用户相关------------------\n    apiV1Router.get('set/token', user.setToken);\n    // 用户登录\n    apiV1Router.post('user/login', user.login);\n    // 用户注册\n    apiV1Router.post('user/register', user.register);\n    // 退出登录\n    apiV1Router.get('user/logout', tokenRequired, user.logout);\n    // 新增 | 编辑\n    apiV1Router.post('user/handle', tokenRequired, user.handle);\n    // 获得用户列表\n    apiV1Router.get('user/getUserList', tokenRequired, user.getUserList);\n    // 冻结解冻用户\n    apiV1Router.post('user/setStatus', tokenRequired, user.setStatus);\n    // 删除用户\n    apiV1Router.post('user/delete', tokenRequired, user.delete);\n\n    // --------------------------- 团队管理 --------------------------------\n    // get list\n    apiV1Router.get('team/list', tokenRequired, team.list);\n    // 新增 | 编辑\n    apiV1Router.post('team/handle', tokenRequired, team.handle);\n    // 设置状态\n    apiV1Router.post('team/setStatus', tokenRequired, team.setStatus);\n    // 删除\n    apiV1Router.post('team/delete', tokenRequired, team.delete);\n\n    // --------------------------- 环境管理 --------------------------------\n    // get list\n    apiV1Router.get('environment/list', tokenRequired, environment.list);\n    // 新增 | 编辑\n    apiV1Router.post('environment/handle', tokenRequired, environment.handle);\n    // 删除\n    apiV1Router.post('environment/delete', tokenRequired, environment.delete);\n\n    // --------------------------- 应用管理 --------------------------------\n    // get list\n    apiV1Router.get('application/list', tokenRequired, application.list);\n    // get all\n    apiV1Router.get('application/all', tokenRequired, application.all);\n    // 新增 | 编辑\n    apiV1Router.post('application/handle', tokenRequired, application.handle);\n    // 设置状态\n    apiV1Router.post('application/setStatus', tokenRequired, application.setStatus);\n    // 删除\n    apiV1Router.post('application/delete', tokenRequired, application.delete);\n    // 分配资产\n    apiV1Router.post('application/distribution', tokenRequired, application.distribution);\n    // 获得单个应用详情\n    apiV1Router.get('application/itemdetail', tokenRequired, application.itemdetail);\n    // 更新单个应用构建配置\n    apiV1Router.post('application/updateConfigs', tokenRequired, application.updateConfigs);\n    // 绑定|取消 应用绑定的邮箱\n    apiV1Router.post('application/handleEmail', tokenRequired, application.handleEmail);\n\n    // --------------------------- 资产管理 --------------------------------\n    // get list\n    apiV1Router.get('assets/list', tokenRequired, assets.list);\n    // get all\n    apiV1Router.get('assets/all', tokenRequired, assets.all);\n    // 新增 | 编辑\n    apiV1Router.post('assets/handle', tokenRequired, assets.handle);\n    // 设置状态\n    apiV1Router.post('assets/setStatus', tokenRequired, assets.setStatus);\n    // 删除\n    apiV1Router.post('assets/delete', tokenRequired, assets.delete);\n\n    // --------------------------- 邮件管理 --------------------------------\n    // get list\n    apiV1Router.get('email/list', tokenRequired, email.list);\n    // 新增 | 编辑\n    apiV1Router.post('email/handle', tokenRequired, email.handle);\n    // 设置状态\n    apiV1Router.post('email/setStatus', tokenRequired, email.setStatus);\n    // 删除\n    apiV1Router.post('email/delete', tokenRequired, email.delete);\n\n    // -------------------------- 脚本任务管理 ------------------------------\n    // get list\n    apiV1Router.get('commtask/list', tokenRequired, commtask.list);\n    // 新增 | 编辑\n    apiV1Router.post('commtask/handle', tokenRequired, commtask.handle);\n    // 删除\n    apiV1Router.post('commtask/delete', tokenRequired, commtask.delete);\n\n    // --------------------------shh sftp 交互------------------------------\n    // update files\n    apiV1Router.post('files/updatefile', tokenRequired, files.updatefile);\n    // add files\n    apiV1Router.post('files/addfile', tokenRequired, files.addfile);\n\n    // --------------------------工具方法------------------------------\n    // 获得ssh key\n    apiV1Router.post('util/getAssetSshKey', tokenRequired, util.getAssetSshKey);\n    // 执行shell任务\n    apiV1Router.post('util/handleShellTasks', tokenRequired, util.handleShellTasks);\n\n    // -------------------------- build 构建 ------------------------------\n    // 生成构建配置\n    apiV1Router.post('build/generateBuildConfig', tokenRequired, build.generateBuildConfig);\n    // 备份服务\n    apiV1Router.post('build/backupApplications', tokenRequired, build.backupApplications);\n    // 构建应用\n    apiV1Router.post('build/buildApplicationed', tokenRequired, build.buildApplicationed);\n    // 应用还原\n    apiV1Router.post('build/reductionApplications', tokenRequired, build.reductionApplications);\n\n    // -------------------------- logs 构建 ------------------------------\n    apiV1Router.get('logs/list', tokenRequired, logs.list);\n\n    // -------------------------- socket.io ------------------------------\n    // socket.io\n    io.of('/').route('socket', io.controller.nsp.socket);\n};\n"
  },
  {
    "path": "app/router/home.js",
    "content": "'use strict';\nmodule.exports = app => {\n    const { router, controller } = app;\n    const { web } = controller;\n\n    // 首页\n    router.get('/', web.team);\n\n    // 团队管理\n    router.get('/team', web.team);\n\n    // 资产管理\n    router.get('/assets', web.assets);\n\n    // 环境管理\n    router.get('/environment', web.environment);\n\n    // 应用管理\n    router.get('/application', web.application);\n\n    // 应用配置\n    router.get('/appconfig', web.appconfig);\n\n    // 邮件管理\n    router.get('/emails', web.emails);\n\n    // 应该构建\n    router.get('/build', web.build);\n\n    // 开始构建\n    router.get('/buildprocess', web.buildprocess);\n\n    // 资产构建\n    router.get('/assetsconfig', web.assetsconfig);\n\n    // 脚本任务\n    router.get('/commtask', web.commtask);\n\n    // 控制台\n    router.get('/console', web.console);\n\n    // 构建日志\n    router.get('/logs', web.logs);\n\n    // 应用还原\n    router.get('/reduction', web.reduction);\n\n    // 用户管理\n    router.get('/user', web.user);\n\n    // 用户登录\n    router.get('/login', web.login);\n};\n"
  },
  {
    "path": "app/router.js",
    "content": "'use strict';\n\nmodule.exports = app => {\n    require('./router/home')(app);\n    require('./router/api')(app);\n};\n"
  },
  {
    "path": "app/service/application.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\n\nclass ApplicationService extends Service {\n\n    // init 初始化\n    async list(pageNo, pageSize, team_code, environ_code, net_type, status, name) {\n        pageSize = pageSize * 1;\n        pageNo = pageNo * 1;\n        const query = { $match: {} };\n        if (team_code) query.$match.team_code = team_code;\n        if (environ_code) query.$match.environ_code = environ_code;\n        if (net_type) query.$match.net_type = net_type * 1;\n        if (status + '') query.$match.status = status * 1;\n        if (name) query.$match.name = { $regex: new RegExp(name) };\n\n        const count = Promise.resolve(this.ctx.model.Application.count(query.$match).exec());\n        const datas = Promise.resolve(\n            this.ctx.model.Application.aggregate([\n                query,\n                {\n                    $lookup: {\n                        from: 'teams',\n                        localField: 'team_code',\n                        foreignField: 'code',\n                        as: 'teamlist',\n                    },\n                },\n                {\n                    $lookup: {\n                        from: 'environments',\n                        localField: 'environ_code',\n                        foreignField: 'code',\n                        as: 'environlist',\n                    },\n                },\n                {\n                    $project: {\n                        _id: 1,\n                        name: 1,\n                        teamlist: 1,\n                        assets_list: 1,\n                        email_list: 1,\n                        net_type: 1,\n                        status: 1,\n                        environlist: 1,\n                        team_code: 1,\n                        environ_code: 1,\n                        user_name: 1,\n                        code: 1,\n                        task_list: 1,\n                    },\n                },\n                { $skip: (pageNo - 1) * pageSize },\n                { $sort: { create_time: -1 } },\n                { $limit: pageSize },\n            ]).exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n    async all() {\n        return this.ctx.model.Application.aggregate([\n            { $match: { status: 1 } },\n            {\n                $project: {\n                    _id: 1,\n                    name: 1,\n                    assets_list: 1,\n                },\n            },\n            { $sort: { count: -1 } },\n        ]).exec();\n    }\n\n    // add | update\n    async handle(json) {\n        const { type, name, code, status, _id, team_code, environ_code, user_name } = json;\n        let result = '';\n        if (type === 1) {\n            // add\n            const application = this.ctx.model.Application();\n            application.name = name;\n            application.code = code;\n            application.status = status;\n            application.team_code = team_code;\n            application.user_name = user_name;\n            application.environ_code = environ_code;\n            application.create_time = new Date();\n            result = await application.save();\n        } else if (type === 2) {\n            // update\n            result = await this.ctx.model.Application.update({ _id }, { $set: { name, code, status, team_code, environ_code, user_name } }, { multi: true });\n        }\n        return result;\n    }\n\n    // 设置状态\n    async setStatus(json) {\n        const { _id, status } = json;\n        return await this.ctx.model.Application.update({ _id }, { $set: { status } }, { multi: true });\n    }\n\n    // 删除\n    async delete(_id) {\n        return await this.ctx.model.Application.remove({ _id });\n    }\n\n    // 分配资产\n    async distribution(_id, assets_list = [], net_type) {\n        net_type = net_type * 1;\n        return await this.ctx.model.Application.update({ _id }, { $set: { assets_list, net_type } }, { multi: true });\n    }\n\n    // 单个应用详情\n    async itemdetail(id) {\n        return await this.ctx.model.Application.findOne({ _id: id, status: 1 });\n    }\n    // 更新单个应用部署配置\n    async updateConfigs(id, tasklist) {\n        return await this.ctx.model.Application.update(\n            { _id: id },\n            { $set: { task_list: tasklist } },\n            { multi: true }\n        );\n    }\n\n    // 绑定|取消 应该绑定的邮箱\n    async handleEmail(id, emaillist) {\n        return await this.ctx.model.Application.update(\n            { _id: id },\n            { $set: { email_list: emaillist } },\n            { multi: true }\n        );\n    }\n\n}\n\nmodule.exports = ApplicationService;\n"
  },
  {
    "path": "app/service/assets.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\n\nclass AssetsService extends Service {\n\n    // init 初始化\n    async list(pageNo, pageSize, team_code, assets_name, status) {\n        pageSize = pageSize * 1;\n        pageNo = pageNo * 1;\n        const query = { $match: {} };\n        if (team_code) query.$match.team_code = team_code;\n        if (assets_name) query.$match.name = { $regex: new RegExp(assets_name) };\n        if (status + '') query.$match.status = status * 1;\n        const count = Promise.resolve(this.ctx.model.Assets.count(query.$match).exec());\n        const datas = Promise.resolve(\n            this.ctx.model.Assets.aggregate([\n                query,\n                {\n                    $lookup: {\n                        from: 'teams',\n                        localField: 'team_code',\n                        foreignField: 'code',\n                        as: 'teamlist',\n                    },\n                },\n                { $skip: (pageNo - 1) * pageSize },\n                { $sort: { create_time: -1 } },\n                { $limit: pageSize },\n            ]).exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n    async all() {\n        return this.ctx.model.Assets.aggregate([\n            { $match: { status: 1 } },\n            {\n                $lookup: {\n                    from: 'teams',\n                    localField: 'team_code',\n                    foreignField: 'code',\n                    as: 'teamlist',\n                },\n            },\n            { $sort: { count: -1 } },\n        ]).exec();\n    }\n\n    // add | update\n    async handle(json) {\n        const { type, name, code, status, _id, team_code, outer_ip, lan_ip, user, port, password, user_name } = json;\n        let result = '';\n        if (type === 1) {\n            // add\n            const assets = this.ctx.model.Assets();\n            assets.name = name;\n            assets.code = code;\n            assets.status = status;\n            assets.team_code = team_code;\n            assets.outer_ip = outer_ip;\n            assets.lan_ip = lan_ip;\n            assets.user = user;\n            assets.user_name = user_name;\n            assets.port = port;\n            assets.password = password;\n            assets.create_time = new Date();\n            result = await assets.save();\n        } else if (type === 2) {\n            // update\n            result = await this.ctx.model.Assets.update(\n                { _id },\n                { $set: { name, code, status, team_code, outer_ip, lan_ip, user, port, password, user_name } },\n                { multi: true }\n            );\n        }\n        return result;\n    }\n\n    // 设置状态\n    async setStatus(json) {\n        const { _id, status } = json;\n        return await this.ctx.model.Assets.update({ _id }, { $set: { status } }, { multi: true });\n    }\n\n    // 删除\n    async delete(_id) {\n        return await this.ctx.model.Assets.remove({ _id });\n    }\n\n}\n\nmodule.exports = AssetsService;\n"
  },
  {
    "path": "app/service/build.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\nconst fs = require('fs');\nconst path = require('path');\n\nclass BuildService extends Service {\n\n    // 生成构建配置\n    async generateBuildConfig(id, taskItem = {}, assetslist = [], user_name) {\n        let file = '';\n        let paths = '/';\n        if (taskItem.shell_path && taskItem.shell_path.indexOf('/') > -1) {\n            file = path.basename(taskItem.shell_path);\n            paths = path.dirname(taskItem.shell_path);\n        } else {\n            throw new Error('shell脚本地址必须以/开头!');\n        }\n\n        taskItem.shell_write_type = taskItem.shell_write_type ? parseInt(taskItem.shell_write_type) : 1;\n        taskItem.shell_body = taskItem.shell_body.replace(/\\r\\n/g, '\\n');\n\n        // 本地创建文件\n        if (taskItem.shell_write_type === 1) fs.writeFileSync(path.resolve(__dirname, '../tempFile/' + file), taskItem.shell_body);\n\n        // 发布到相应服务器\n        const result = [];\n        for (let i = 0; i < assetslist.length; i++) { result.push(this.genConfigsForSsh(paths, file, assetslist[i], taskItem)); }\n        const data = await Promise.all(result);\n        // add logs\n        const json = {\n            application_id: id,\n            task_name: `${taskItem.task_name}任务-生成构建配置`,\n            type: 3,\n            isLoad: true,\n            user_name,\n            content: data || [],\n        };\n        this.ctx.service.logs.addLogs(json);\n        // 删除本地文件\n        if (taskItem.shell_write_type === 1) fs.unlinkSync(path.resolve(__dirname, '../tempFile/' + file));\n        return json;\n    }\n\n    // 脚本备份\n    async backupApplications(id, taskItem = {}, assetslist = [], user_name) {\n        const bakList = [];\n        const { project_path, backups_path } = taskItem;\n        const backupDir = 'bak_' + this.app.format(new Date(), 'yyyy-MM-dd:hh:mm:ss');\n        const projectName = project_path ? project_path.split('/').splice(-1).join() : '';\n        const backupPath = `${backups_path}/${backupDir}/${projectName}`;\n\n        for (let i = 0; i < assetslist.length; i++) {\n            bakList.push(Promise.resolve(this.backUpProject(taskItem, assetslist[i], backupPath, backupDir)));\n        }\n        const result = await Promise.all(bakList);\n        const json = {\n            application_id: id,\n            task_name: `${taskItem.task_name}任务-服务备份`,\n            type: 2,\n            isLoad: true,\n            user_name,\n            content: result || [],\n        };\n        this.ctx.service.logs.addLogs(json);\n        return json;\n    }\n\n    // 备份还原\n    async reductionApplications(id, taskItem = {}, assetslist = [], user_name) {\n        let file = '';\n        let paths = '/';\n        if (taskItem.reduction_shell_path && taskItem.reduction_shell_path.indexOf('/') > -1) {\n            file = path.basename(taskItem.reduction_shell_path);\n            paths = path.dirname(taskItem.reduction_shell_path);\n        } else {\n            throw new Error('shell脚本地址必须以/开头!');\n        }\n\n        taskItem.reduction_shell_write_type = taskItem.reduction_shell_write_type ? parseInt(taskItem.reduction_shell_write_type) : 1;\n        taskItem.reduction_shell_body = taskItem.reduction_shell_body.replace(/\\r\\n/g, '\\n');\n\n        // 本地创建文件\n        if (taskItem.reduction_shell_write_type === 1) fs.writeFileSync(path.resolve(__dirname, '../tempFile/' + file), taskItem.reduction_shell_body);\n\n        // 发布到相应服务器\n        const result = [];\n        for (let i = 0; i < assetslist.length; i++) { result.push(this.reductionProject(paths, file, assetslist[i], taskItem)); }\n        const data = await Promise.all(result);\n        // add logs\n        const json = {\n            application_id: id,\n            task_name: `${taskItem.name}任务-备份还原`,\n            type: 5,\n            user_name,\n            isLoad: true,\n            content: data || [],\n        };\n        this.ctx.service.logs.addLogs(json);\n        // 删除本地文件\n        if (taskItem.reduction_shell_write_type === 1) fs.unlinkSync(path.resolve(__dirname, '../tempFile/' + file));\n        return json;\n    }\n\n    // 应用还原\n    reductionProject(paths, file, asstesItem, taskItem) {\n        return new Promise((resolve, reject) => {\n            const Client = require('ssh2-sftp-client');\n            const sftp = new Client();\n            sftp.connect({\n                host: asstesItem.outer_ip,\n                port: asstesItem.port,\n                username: asstesItem.user,\n                password: asstesItem.password,\n            })\n                .then(() => {\n                    return sftp.exists(paths);\n                })\n                .then(data => {\n                    if (!data) return sftp.mkdir(paths, true);\n                    return 1;\n                })\n                .then(() => {\n                    if (taskItem.reduction_shell_write_type === 1) {\n                        return sftp.fastPut(path.resolve(__dirname, '../tempFile/' + file), taskItem.reduction_shell_path);\n                    } else if (taskItem.reduction_shell_write_type === 2) {\n                        taskItem.reduction_shell_body = taskItem.reduction_shell_body.replace(/'/g, '\"');\n                        return this.exec(sftp, \"echo '\" + taskItem.reduction_shell_body + \"' > \" + taskItem.reduction_shell_path);\n                    }\n                })\n                .then(data => {\n                    if (typeof (data) === 'string') {\n                        data = {\n                            data,\n                            code: 0,\n                        };\n                    }\n                    if (data.code === 0) {\n                        const shell = taskItem.reduction_shell_opction ?\n                            `sh ${taskItem.reduction_shell_path} ${taskItem.reduction_shell_opction}` :\n                            `sh ${taskItem.reduction_shell_path}`;\n                        return this.exec(sftp, shell);\n                    } else if (data.code !== 0) {\n                        return data;\n                    }\n                })\n                .then(data => {\n                    if (typeof (data) === 'string') {\n                        data = {\n                            data,\n                            code: 0,\n                        };\n                    }\n                    resolve(Object.assign({}, data, {\n                        assets_name: asstesItem.assets_name || '',\n                        lan_ip: asstesItem.lan_ip || '',\n                        outer_ip: asstesItem.outer_ip || '',\n                    }));\n                    sftp.end();\n                })\n                .catch(err => {\n                    reject(err);\n                });\n        });\n    }\n\n    // 文件备份\n    async backUpProject(taskItem, assets, backupPath, backupDir) {\n        const { outer_ip, port, user, password } = assets;\n        const { project_path, backups_path } = taskItem;\n        return new Promise((resolve, reject) => {\n            const Client = require('ssh2-sftp-client');\n            const sftp = new Client();\n            sftp.connect({\n                host: outer_ip,\n                username: user,\n                port,\n                password,\n            })\n                .then(() => {\n                    const sh = `mkdir -p ${backups_path}/${backupDir} && cp -r ${project_path} ${backupPath}`;\n                    return this.exec(sftp, sh) || {};\n                }).then(data => {\n                    resolve(Object.assign({}, data, {\n                        assets_name: assets.name || '',\n                        lan_ip: assets.lan_ip || '',\n                        outer_ip: assets.outer_ip || '',\n                        backup_path: backupPath,\n                        project_path,\n                    }));\n                    sftp.end();\n                })\n                .catch(err => {\n                    reject(err);\n                });\n        });\n    }\n\n    // 上传文件\n    async genConfigsForSsh(paths, file, asstesItem, taskItem) {\n        return new Promise((resolve, reject) => {\n            const Client = require('ssh2-sftp-client');\n            const sftp = new Client();\n            sftp.connect({\n                host: asstesItem.outer_ip,\n                port: asstesItem.port,\n                username: asstesItem.user,\n                password: asstesItem.password,\n            })\n                .then(() => {\n                    return sftp.exists(paths);\n                })\n                .then(data => {\n                    if (!data) return sftp.mkdir(paths, true);\n                    return 1;\n                })\n                .then(() => {\n                    if (taskItem.shell_write_type === 1) {\n                        return sftp.fastPut(path.resolve(__dirname, '../tempFile/' + file), taskItem.shell_path);\n                    } else if (taskItem.shell_write_type === 2) {\n                        taskItem.shell_body = taskItem.shell_body.replace(/'/g, '\"');\n                        return this.exec(sftp, \"echo '\" + taskItem.shell_body + \"' > \" + taskItem.shell_path);\n                    }\n                })\n                .then(data => {\n                    if (typeof (data) === 'string') {\n                        data = {\n                            data,\n                            code: 0,\n                        };\n                    }\n                    resolve(Object.assign({}, data, {\n                        assets_name: asstesItem.name || '',\n                        lan_ip: asstesItem.lan_ip || '',\n                        outer_ip: asstesItem.outer_ip || '',\n                    }));\n                    sftp.end();\n                })\n                .catch(err => {\n                    reject(err);\n                });\n        });\n    }\n\n    // 执行shell任务\n    exec(sftp, shell) {\n        return new Promise((resolve, reject) => {\n            let str = '';\n            sftp.client.exec(shell, (err, stream) => {\n                if (err) {\n                    reject(err);\n                    return;\n                }\n                stream.on('close', code => {\n                    return resolve({\n                        data: str,\n                        shell,\n                        code,\n                    });\n                }).on('data', data => {\n                    str += data;\n                }).stderr.on('data', data => {\n                    str += data;\n                });\n            });\n            return undefined;\n        });\n    }\n}\n\nmodule.exports = BuildService;\n"
  },
  {
    "path": "app/service/commtask.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\nconst fs = require('fs');\nconst path = require('path');\n\nclass CommtaskService extends Service {\n\n    // init 初始化\n    async list() {\n        return await this.ctx.model.Commtask\n            .find()\n            .sort({ create_time: -1 })\n            .exec() || [];\n    }\n\n    // add | update\n    async handle(query) {\n        let { _id, name, type, btn_color, is_plain, shell_body, handletype, shell_opction, shell_path, shell_write_type, user_name } = query;\n        handletype = handletype * 1;\n        let result = '';\n        if (handletype === 1) {\n            // add\n            const commtask = this.ctx.model.Commtask();\n            commtask.name = name;\n            commtask.type = type;\n            commtask.btn_color = btn_color;\n            commtask.is_plain = is_plain;\n            commtask.user_name = user_name;\n            commtask.shell_body = shell_body;\n            commtask.shell_opction = shell_opction;\n            commtask.shell_path = shell_path;\n            commtask.shell_write_type = shell_write_type || 1;\n            commtask.create_time = new Date();\n            result = await commtask.save();\n        } else if (handletype === 2) {\n            // update\n            result = await this.ctx.model.Commtask.update(\n                { _id },\n                { $set: { name, type, btn_color, is_plain, shell_body, shell_opction, shell_path, shell_write_type, user_name } },\n                { multi: true }\n            );\n        }\n        return result;\n    }\n\n    // 删除\n    async delete(id) {\n        return await this.ctx.model.Commtask.remove({ _id: id });\n    }\n\n    // init 初始化\n    async generateBuildConfig(id, item, assetslist = []) {\n        let file = '';\n        let paths = '/';\n        if (item.shell_path && item.shell_path.indexOf('/') > -1) {\n            file = path.basename(item.shell_path);\n            paths = path.dirname(item.shell_path);\n        } else {\n            throw new Error('shell脚本地址必须以/开头!');\n        }\n        // 本地创建文件\n        fs.writeFileSync(path.resolve(__dirname, '../tempFile/' + file), item.shell_body);\n        const result = [];\n        for (let i = 0; i < assetslist.length; i++) {\n            result.push(this.service.util.genConfigsForSsh({\n                paths,\n                file,\n                host: assetslist[i].outer_ip,\n                port: assetslist[i].port,\n                username: assetslist[i].user,\n                password: assetslist[i].password,\n                shell_path: item.shell_path,\n            }));\n        }\n        await Promise.all(result);\n        // 删除\n        fs.unlinkSync(path.resolve(__dirname, '../tempFile/' + file));\n        return {};\n    }\n\n}\n\nmodule.exports = CommtaskService;\n"
  },
  {
    "path": "app/service/console.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\nconst ssh2 = require('../util/ssh2');\nconst path = require('path');\nclass ConsoleService extends Service {\n\n    // 获得服务器的ssh key\n    async getAssetSshKey(query) {\n        const type = query.type * 1;\n        const email = query.email;\n        await ssh2.init(query);\n\n        let result = '';\n        if (type === 1) {\n            // get ssh key\n            result = await ssh2.exec('cat ~/.ssh/id_rsa.pub');\n        } else if (type === 2) {\n            // add new ssh key\n            await ssh2.exec(`echo y | ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa -C \"${email}\"`);\n            result = await ssh2.exec('cat ~/.ssh/id_rsa.pub');\n        }\n        ssh2.end();\n        return result;\n    }\n\n    // 执行shell任务\n    // 安装git环境\n    async handleShellTasks(query) {\n        const { shell_body } = query;\n        // 窗口运行命令\n        await ssh2.init(query);\n        const result = await ssh2.exec(shell_body);\n        ssh2.end();\n        return {\n            result,\n        };\n    }\n\n    // 上传文件\n    async genConfigsForSsh(query) {\n        const { paths, file, host, port, username, password, shell_path } = query;\n        return new Promise((resolve, reject) => {\n            const Client = require('ssh2-sftp-client');\n            const sftp = new Client();\n            sftp.connect({\n                host,\n                port,\n                username,\n                password,\n            })\n                .then(() => {\n                    return sftp.exists(paths);\n                })\n                .then(data => {\n                    if (!data) return sftp.mkdir(paths, true);\n                    return 1;\n                })\n                .then(() => {\n                    return sftp.fastPut(path.resolve(__dirname, '../tempFile/' + file), shell_path);\n                })\n                .then(() => {\n                    resolve(1);\n                    sftp.end();\n                })\n                .catch(err => {\n                    reject(err);\n                });\n        });\n    }\n}\n\nmodule.exports = ConsoleService;\n"
  },
  {
    "path": "app/service/email.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\n\nclass EmailService extends Service {\n\n    // init 初始化\n    async list(pageNo, pageSize, status) {\n        pageSize = pageSize * 1;\n        pageNo = pageNo * 1;\n        const query = {};\n        if (status) query.status = status * 1;\n        const count = Promise.resolve(this.ctx.model.Email.count(query).exec());\n        const datas = Promise.resolve(\n            this.ctx.model.Email.find(query)\n                .skip((pageNo - 1) * pageSize)\n                .limit(pageSize)\n                .sort({ create_time: -1 })\n                .exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n    // add | update\n    async handle(json) {\n        const { type, name, email, status, id } = json;\n        let result = '';\n        if (type === 1) {\n            // add\n            const emails = this.ctx.model.Email();\n            emails.name = name;\n            emails.email = email;\n            emails.status = status;\n            emails.create_time = new Date();\n            result = await emails.save();\n        } else if (type === 2) {\n            // update\n            result = await this.ctx.model.Email.update({ _id: id }, { $set: { name, email, status } }, { multi: true });\n        }\n        return result;\n    }\n\n    // 设置状态\n    async setStatus(json) {\n        const { id, status } = json;\n        return await this.ctx.model.Email.update({ _id: id }, { $set: { status } }, { multi: true });\n    }\n\n    // 删除\n    async delete(id) {\n        return await this.ctx.model.Email.remove({ _id: id });\n    }\n\n}\n\nmodule.exports = EmailService;\n"
  },
  {
    "path": "app/service/environment.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\n\nclass EnvironmentService extends Service {\n\n    // init 初始化\n    async list(pageNo, pageSize) {\n        pageSize = pageSize * 1;\n        pageNo = pageNo * 1;\n        const count = Promise.resolve(this.ctx.model.Environment.count().exec());\n        const datas = Promise.resolve(\n            this.ctx.model.Environment.find()\n                .skip((pageNo - 1) * pageSize)\n                .limit(pageSize)\n                .exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n    // add | update\n    async handle(json) {\n        const { type, name, code, id } = json;\n        let result = '';\n        if (type === 1) {\n            // add\n            const environment = this.ctx.model.Environment();\n            environment.name = name;\n            environment.code = code;\n            environment.create_time = new Date();\n            result = await environment.save();\n        } else if (type === 2) {\n            // update\n            result = await this.ctx.model.Environment.update({ _id: id }, { $set: { name, code } }, { multi: true });\n        }\n        return result;\n    }\n\n    // 删除\n    async delete(id) {\n        return await this.ctx.model.Environment.remove({ _id: id });\n    }\n\n}\n\nmodule.exports = EnvironmentService;\n"
  },
  {
    "path": "app/service/files.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\nconst path = require('path');\nconst fs = require('fs');\nconst util = require('../util/utils');\n\nclass FilesService extends Service {\n\n    // add files\n    async addfile(filename, content, remotePath) {\n        // 中转站文件夹\n        const file = path.resolve(__dirname, '../transferdir', filename);\n        fs.writeFileSync(file, content);\n        remotePath = util.isDirEnd(remotePath) ? `${remotePath}${filename}` : `${remotePath}/${filename}`;\n        await this.ctx.service.sftp.fastPut(file, remotePath);\n        fs.unlinkSync(file);\n        return {};\n    }\n\n    // update files\n    async updatefile(filename, content) {\n        content = content.replace(/;/g, '\\;');\n        content = content.replace(/\"/g, '\\'');\n        await this.ctx.service.ssh2.exec(`echo -e \"${content}\" > ${filename}`);\n    }\n\n\n}\n\nmodule.exports = FilesService;\n"
  },
  {
    "path": "app/service/logs.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\n\nclass LogsService extends Service {\n\n    // add logs\n    async addLogs(json = {}) {\n        const { task_name, application_id, commtask_id, type, content, date, user_name } = json;\n        const logs = this.ctx.model.Logs();\n        logs.name = task_name || '';\n        logs.type = type || '';\n        logs.content = content || '';\n        logs.application_id = application_id || '';\n        logs.commtask_id = commtask_id || '';\n        logs.user_name = user_name || '';\n        logs.create_time = date ? new Date(date) : new Date();\n        return await logs.save();\n    }\n\n    // get list\n    async list(pageNo, pageSize, type, name, application_id) {\n        pageSize = pageSize * 1;\n        pageNo = pageNo * 1;\n        type = type * 1;\n\n        const query = { type };\n        if (name) query.name = { $regex: new RegExp(name) };\n        if (application_id) query.application_id = application_id;\n\n        const count = Promise.resolve(this.ctx.model.Logs.count(query).exec());\n        const datas = Promise.resolve(\n            this.ctx.model.Logs.find(query)\n                .skip((pageNo - 1) * pageSize)\n                .limit(pageSize)\n                .sort({ create_time: -1 })\n                .exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n}\n\nmodule.exports = LogsService;\n"
  },
  {
    "path": "app/service/qiniu.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\nconst qiniu = require('qiniu');\n\nclass QiniuService extends Service {\n\n    init(){\n        const qiniuConfig = this.app.config.qiniu || {};\n        this.bucket = qiniuConfig.bucket;\n        this.mac = new qiniu.auth.digest.Mac(qiniuConfig.ACCESS_KEY, qiniuConfig.SECRET_KEY);\n    }\n\n    //构建上传策略函数\n    getToken(key) {\n        this.init();\n\n        const putPolicy = new qiniu.rs.PutPolicy({\n            scope: this.bucket,\n            expires: 7200\n        });\n\n        const uploadToken = putPolicy.uploadToken(this.mac);\n\n        return uploadToken;\n    }\n\n    getKey(num = 15){\n        return this.app.randomString(num);\n    }\n\n    //调用uploadFile上传    \n    async upload(filePath) {\n        filePath = filePath || ''\n        //生成上传 Token\n        let token = this.getToken();\n        //要上传文件的本地路径\n        let key = this.getKey();\n        let imgs = await this.uploadFile(token, key, filePath)\n        return imgs\n    }\n\n    // 上传文件\n    uploadFile(uptoken, key, localFile) {\n        var extra = new qiniu.io.PutExtra();\n        return new Promise((resolve, reject) => {\n            qiniu.io.putFile(uptoken, key, localFile, extra, function (err, ret) {\n                if (!err) {\n                    // 上传成功， 处理返回值\n                    resolve(ret.key)\n                } else {\n                    // 上传失败， 处理返回代码\n                    reject(err)\n                }\n            });\n        });\n    }\n\n}\n\nmodule.exports = QiniuService;\n\n\n\n"
  },
  {
    "path": "app/service/team.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\n\nclass TeamService extends Service {\n\n    // init 初始化\n    async list(pageNo, pageSize, status) {\n        pageSize = pageSize * 1;\n        pageNo = pageNo * 1;\n        const query = {};\n        if (status) query.status = status * 1;\n        const count = Promise.resolve(this.ctx.model.Team.count(query).exec());\n        const datas = Promise.resolve(\n            this.ctx.model.Team.find(query)\n                .skip((pageNo - 1) * pageSize)\n                .limit(pageSize)\n                .sort({ create_time: -1 })\n                .exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n    // add | update\n    async handle(json) {\n        const { type, name, code, status, _id } = json;\n        let result = '';\n        if (type === 1) {\n            // add\n            const team = this.ctx.model.Team();\n            team.name = name;\n            team.code = code;\n            team.status = status;\n            team.create_time = new Date();\n            result = await team.save();\n        } else if (type === 2) {\n            // update\n            result = await this.ctx.model.Team.update({ _id }, { $set: { name, code, status } }, { multi: true });\n        }\n        return result;\n    }\n\n    // 设置状态\n    async setStatus(json) {\n        const { _id, status } = json;\n        return await this.ctx.model.Team.update({ _id }, { $set: { status } }, { multi: true });\n    }\n\n    // 删除\n    async delete(_id) {\n        return await this.ctx.model.Team.remove({ _id });\n    }\n\n}\n\nmodule.exports = TeamService;\n"
  },
  {
    "path": "app/service/user.js",
    "content": "'use strict';\nconst crypto = require('crypto');\nconst Service = require('egg').Service;\n\nclass UserService extends Service {\n\n    // 用户登录\n    async login(userName, passWord) {\n        // 检测用户是否存在\n        const userInfo = await this.getUserInfoForUserName(userName) || {};\n        if (!userInfo.token) throw new Error('用户名不存在！');\n\n        const newPwd = crypto.createHmac('sha256', passWord)\n            .update(this.app.config.user_pwd_salt_addition)\n            .digest('hex');\n\n        if (userInfo.pass_word !== newPwd) throw new Error('用户密码不正确！');\n        if (userInfo.status !== 1) throw new Error('用户被冻结不能登录，请联系管理员！');\n\n        // 清空以前的登录态\n        if (userInfo.usertoken) this.app.redis.set(`${userInfo.usertoken}_user_login`, '');\n\n        // 设置新的redis登录态\n        const random_key = this.app.randomString();\n        this.app.redis.set(`${random_key}_user_login`, JSON.stringify(userInfo), 'EX', this.app.config.user_login_timeout);\n        // 设置登录cookie\n        this.ctx.cookies.set('usertoken', random_key, {\n            maxAge: this.app.config.user_login_timeout * 1000,\n            httpOnly: true,\n            encrypt: true,\n            signed: true,\n        });\n        // 更新用户信息\n        await this.updateUserToken({ username: userName, usertoken: random_key });\n\n        userInfo.pass_word = '';\n        return userInfo;\n    }\n\n    // 登出\n    logout(usertoken) {\n        this.ctx.cookies.set('usertoken', '');\n        this.app.redis.set(`${usertoken}_user_login`, '');\n        return {};\n    }\n\n    // 用户注册\n    async register(userName, passWord) {\n        if (!userName || userName !== 'admin') throw new Error('请使用admin账号进行注册！');\n        // 检测用户是否存在\n        const userInfo = await this.getUserInfoForUserName(userName);\n        if (userInfo.token) throw new Error('用户注册：用户已存在！');\n\n        const newPwd = crypto.createHmac('sha256', passWord)\n            .update(this.app.config.user_pwd_salt_addition)\n            .digest('hex');\n\n        // 新增用户\n        const token = this.app.randomString();\n        const user = this.ctx.model.User();\n        user.user_name = userName;\n        user.pass_word = newPwd;\n        user.token = token;\n        user.usertoken = token;\n        user.create_time = new Date();\n        const result = await user.save() || {};\n        result.pass_word = '';\n\n        // 设置redis登录态\n        this.app.redis.set(`${token}_user_login`, JSON.stringify(result), 'EX', this.app.config.user_login_timeout);\n        // 设置登录cookie\n        this.ctx.cookies.set('usertoken', token, {\n            maxAge: this.app.config.user_login_timeout * 1000,\n            httpOnly: true,\n            encrypt: true,\n            signed: true,\n        });\n\n        return result;\n    }\n\n    // 根据用户名称查询用户信息\n    async getUserInfoForUserName(userName) {\n        return await this.ctx.model.User.findOne({ user_name: userName }).exec() || {};\n    }\n\n    // 查询用户列表信息（分页）\n    async getUserList(pageNo, pageSize, userName) {\n        pageNo = pageNo * 1;\n        pageSize = pageSize * 1;\n\n        const query = {};\n        if (userName) query.user_name = userName;\n\n        const count = Promise.resolve(this.ctx.model.User.count(query).exec());\n        const datas = Promise.resolve(\n            this.ctx.model.User.find(query).skip((pageNo - 1) * pageSize)\n                .limit(pageSize)\n                .exec()\n        );\n        const all = await Promise.all([ count, datas ]);\n        const [ totalNum, datalist ] = all;\n\n        return {\n            datalist,\n            totalNum,\n            pageNo,\n        };\n    }\n\n    // add | update\n    async handle(json) {\n        const { type, user_name, pass_word, status, _id } = json;\n\n        let newPwd = '';\n        if (type === 1 || type === 3) {\n            newPwd = crypto.createHmac('sha256', pass_word)\n                .update(this.app.config.user_pwd_salt_addition)\n                .digest('hex');\n        }\n        const token = this.app.randomString();\n\n        let result = '';\n        if (type === 1) {\n            // add\n            const user = this.ctx.model.User();\n            user.user_name = user_name;\n            user.pass_word = newPwd;\n            user.status = status;\n            user.token = token;\n            user.usertoken = token;\n            user.create_time = new Date();\n            result = await user.save();\n        } else if (type === 2 || type === 3) {\n            const json = {};\n            if (user_name) json.user_name = user_name;\n            if (newPwd) json.pass_word = newPwd;\n            if (status + '') json.status = status;\n            // update\n            result = await this.ctx.model.User.update(\n                { _id },\n                { $set: json },\n                { multi: true }\n            );\n        }\n        return result;\n    }\n\n    // 通过redis登录key获取用户信息\n    async getUserInfoForUsertoken(usertoken) {\n        return this.app.redis.get(`${usertoken}_user_login`) || {};\n    }\n\n    // 设置状态\n    async setStatus(json) {\n        const { _id, status, usertoken } = json;\n        const result = await this.ctx.model.User.update({ _id }, { $set: { status } }, { multi: true });\n        // 清空登录态\n        this.app.redis.set(`${usertoken}_user_login`, '');\n        return result;\n    }\n\n    // 删除用户\n    async delete(id, usertoken) {\n        // 删除\n        const result = await this.ctx.model.User.findOneAndRemove({ _id: id }).exec();\n        // 清空登录态\n        if (usertoken) this.app.redis.set(`${usertoken}_user_login`, '');\n        return result;\n    }\n\n    // 更新用户登录态随机数\n    async updateUserToken(opt) {\n        const query = {};\n        if (opt.username) {\n            query.user_name = opt.username;\n        } else if (opt.token) {\n            query.token = opt.token;\n        }\n        const result = await this.ctx.model.User.update(\n            query,\n            { usertoken: opt.usertoken },\n            { multi: true }\n        ).exec();\n\n        return result;\n    }\n\n    // 根据token查询用户信息\n    async finUserForToken(usertoken) {\n        let user_info = await this.app.redis.get(`${usertoken}_user_login`);\n\n        if (user_info) {\n            user_info = JSON.parse(user_info);\n            if (user_info.status !== 1) return { desc: '用户被冻结不能登录，请联系管理员！' };\n        } else {\n            return null;\n        }\n        return await this.ctx.model.User.findOne({ token: user_info.token }).exec();\n    }\n\n    // 根据github node_id 获得用户是否已存在\n    async getUserInfoForGithubId(id) {\n        return await this.ctx.model.User.findOne({ token: id }).exec() || {};\n    }\n\n}\n\nmodule.exports = UserService;\n"
  },
  {
    "path": "app/service/util.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Service = require('egg').Service;\nconst ssh2 = require('../util/ssh2');\nconst path = require('path');\nclass UtilService extends Service {\n\n    // 获得服务器的ssh key\n    async getAssetSshKey(query) {\n        const type = query.type * 1;\n        const email = query.email;\n        await ssh2.init(query);\n\n        let result = '';\n        if (type === 1) {\n            // get ssh key\n            result = await ssh2.exec('cat ~/.ssh/id_rsa.pub');\n        } else if (type === 2) {\n            // add new ssh key\n            await ssh2.exec(`echo y | ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa -C \"${email}\"`);\n            result = await ssh2.exec('cat ~/.ssh/id_rsa.pub');\n        }\n        ssh2.end();\n        return result;\n    }\n\n    // 执行shell任务\n    // 安装git环境\n    async handleShellTasks(query) {\n        const { shell_body } = query;\n        // 窗口运行命令\n        await ssh2.init(query);\n        const result = await ssh2.exec(shell_body);\n        ssh2.end();\n        return {\n            result,\n        };\n    }\n\n    // 上传文件\n    async genConfigsForSsh(query) {\n        const { paths, file, host, port, username, password, shell_path } = query;\n        return new Promise((resolve, reject) => {\n            const Client = require('ssh2-sftp-client');\n            const sftp = new Client();\n            sftp.connect({\n                host,\n                port,\n                username,\n                password,\n            })\n                .then(() => {\n                    return sftp.exists(paths);\n                })\n                .then(data => {\n                    if (!data) return sftp.mkdir(paths, true);\n                    return 1;\n                })\n                .then(() => {\n                    return sftp.fastPut(path.resolve(__dirname, '../tempFile/' + file), shell_path);\n                })\n                .then(() => {\n                    resolve(1);\n                    sftp.end();\n                })\n                .catch(err => {\n                    reject(err);\n                });\n        });\n    }\n}\n\nmodule.exports = UtilService;\n"
  },
  {
    "path": "app/tempFile/install-node.sh",
    "content": "#!/bin/bash\nwget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | NVM_DIR=/usr/local/nvm bash\nexport NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/dist\necho \"export NVM_DIR=\"/usr/local/nvm\"\"  >> /etc/bashrc\necho \"[ -s \"\\$NVM_DIR/nvm.sh\" ] && \\. \"\\$NVM_DIR/nvm.sh\" \"  >> /etc/bashrc\necho \"export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/dist\" >> /etc/bashrc\nsource /etc/bashrc\nsource ~/.bash_profile\nsource /etc/bashrc\nsource ~/.bash_profile\nnvm install 10.15.3\nnvm alias default v10.15.3\nnvm use 10.15.3"
  },
  {
    "path": "app/util/sftp.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Client = require('ssh2-sftp-client');\nconst servers = new Client();\nconst sftp = {};\nclass Sftp {\n    constructor() {\n        this.host = '';\n        this.port = '';\n        this.username = '';\n        this.password = '';\n    }\n\n    // init 初始化\n    async init(json = {}) {\n        this.host = json.host;\n        this.port = json.port;\n        this.username = json.username;\n        this.password = json.password;\n        await this.connectStatus();\n    }\n\n    async connectStatus() {\n        if (!sftp[this.host]) {\n            sftp[this.host] = await this.connect();\n            const loop = () => { sftp[this.host] = null; };\n            servers.on('end', loop);\n            servers.on('error', loop);\n        }\n    }\n\n    async connect() {\n        return new Promise(resolve => {\n            servers.connect({\n                host: this.host,\n                port: this.port,\n                username: this.username,\n                password: this.password,\n            }).then(async () => {\n                resolve(servers);\n                // const result = await sftp.list('/data/down');\n                // await sftp.fastGet('/data/performance/config/*.js', path.resolve(__dirname,'../../download/*.js'));\n                // await sftp.mkdir('/data/down', false);\n                // await sftp.fastPut(path.resolve(__dirname, '../../config/config.default.js'), '/data/down/config.default.js')\n            });\n        });\n    }\n\n    // 查看文件列表\n    async list(remoteFilePath) {\n        await this.connectStatus();\n        return await sftp[this.host].list(remoteFilePath);\n    }\n\n    // 获得单个文件内容\n    async get(remoteFilePath) {\n        await this.connectStatus();\n        return await sftp[this.host].get(remoteFilePath);\n    }\n\n    // 新增文件夹\n    async mkdir(remoteFilePath, recursive) {\n        await this.connectStatus();\n        return await sftp[this.host].mkdir(remoteFilePath, recursive);\n    }\n\n    // 删除文件夹\n    async rmdir(localPath, recursive) {\n        await this.connectStatus();\n        return await sftp[this.host].rmdir(localPath, recursive);\n    }\n\n    // 下载文件\n    async fastGet(remotePath, localPath) {\n        await this.connectStatus();\n        return await sftp[this.host].fastGet(remotePath, localPath);\n    }\n\n    // 上传文件\n    async fastPut(localPath, remotePath) {\n        await this.connectStatus();\n        return await sftp[this.host].fastPut(localPath, remotePath);\n    }\n\n    // 删除文件\n    async delete(remoteFilePath) {\n        await this.connectStatus();\n        return await sftp[this.host].delete(remoteFilePath);\n    }\n\n    // 文件重命名\n    async rename(remoteSourcePath, remoteDestPath) {\n        await this.connectStatus();\n        return await sftp[this.host].rename(remoteSourcePath, remoteDestPath);\n    }\n\n    // 文件权限更改\n    async chmod(remoteDestPath, mode) {\n        await this.connectStatus();\n        return await sftp[this.host].chmod(remoteDestPath, mode);\n    }\n\n    // 关闭窗口\n    async end() {\n        await this.connectStatus();\n        await sftp[this.host].end();\n        sftp[this.host] = null;\n        return 'success';\n    }\n\n}\n\nmodule.exports = Sftp;\n"
  },
  {
    "path": "app/util/socket.js",
    "content": "\n'use strict';\n\nconst debug = require('debug');\nconst SSH = require('ssh2').Client;\n\n// public\nmodule.exports = function socket(json) {\n    const socket = json.socket.socket;\n    const conn = new SSH();\n    let isEnd = false;\n    let isSend = false;\n    let isSuccess = false;\n    let timer = null;\n    const timeout = 2000;\n    const regExp = new RegExp(`\\\\[${json.username || ''}@.+?\\\\]`);\n    const socketIndex = json.socket.data.split('_').splice(-1).join();\n    const taskType = json.taskType || 'command';\n\n    socket.on(json.socket.geometry || 'geometry', function socketOnGeometry(cols, rows) {\n        json.cols = cols;\n        json.rows = rows;\n    });\n\n    conn.on('ready', function connOnReady() {\n        conn.shell({\n            term: json.term,\n            cols: json.cols,\n            rows: json.rows,\n        }, function connShell(err, stream) {\n            setTimeout(() => { json.initialTask && stream.write(json.initialTask); }, 200);\n\n            if (err) {\n                socket.emit(json.socket.close || 'close', 'SSH2 CONN ERROR');\n                conn.end();\n                return;\n            }\n\n            socket.on(json.socket.data || 'data', function socketOnData(data) {\n                if (isEnd) isEnd = false;\n                if (isSuccess) isSuccess = false;\n                try { stream.write(data); } catch (err) { console.log(err); }\n            });\n\n            socket.on(json.socket.close || 'close', function() {\n                conn.end();\n            });\n\n            socket.on('resize', function socketOnResize(data) {\n                stream.setWindow(data.rows, data.cols);\n            });\n\n            socket.on('disconnect', function socketOnDisconnect(reason) {\n                err = { message: reason };\n                debug('CLIENT SOCKET DISCONNECT', err);\n                socket.emit(json.socket.close || 'close', 'CLIENT SOCKET DISCONNECT');\n                conn.end();\n            });\n\n            socket.on('error', function socketOnError(err) {\n                debug('SOCKET ERROR', err);\n                socket.emit(json.socket.close || 'close', 'SOCKET ERROR');\n                conn.end();\n            });\n\n            stream.on('close', function streamOnClose(code, signal) {\n                err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) };\n                debug('STREAM CLOSE', err);\n                socket.emit(json.socket.close || 'close', 'STREAM CLOSE');\n                conn.end();\n            });\n\n            stream.on('data', function streamOnData(data) {\n                data = data.toString('utf-8');\n                socket.emit(json.socket.data || 'data', data);\n                // 执行成功\n                if (data.indexOf('SUCCESSFUL_COMMAND_CONSTRUCTION') > -1) isSuccess = true;\n                if (timer) clearTimeout(timer);\n                if (regExp.test(data) && (taskType === 'shell' || taskType === 'git')) {\n                    timer = setTimeout(() => {\n                        isEnd = true;\n                        if (!isSend) {\n                            socket.emit(json.socket.end || 'data', {\n                                isSuccess, isEnd,\n                                date: json.date || new Date(),\n                                index: socketIndex,\n                            });\n                        }\n                        isSend = true;\n                    }, timeout);\n                }\n            });\n        });\n    });\n\n    conn.on('end', function connOnEnd(err) { socket.emit(json.socket.close || 'close', 'CONN END BY HOST'); debug('CONN END BY HOST', err); });\n    conn.on('close', function connOnClose(err) { socket.emit(json.socket.close || 'close', 'CONN CLOSE'); debug('CONN CLOSE', err); });\n    conn.on('error', function connOnError(err) { socket.emit(json.socket.close || 'close', 'CONN ERROR'); debug('CONN ERROR', err); });\n\n    if (json.username && json.password) {\n        conn.connect({\n            host: json.host,\n            port: json.port,\n            username: json.username,\n            password: json.password,\n            tryKeyboard: true,\n            readyTimeout: 20000,\n            keepaliveInterval: 120000,\n            keepaliveCountMax: 30,\n            debug: debug('ssh2'),\n        });\n    } else {\n        socket.emit(json.socket.close || 'close', 1);\n    }\n};\n\n"
  },
  {
    "path": "app/util/ssh2.js",
    "content": "// 七牛JDK\n'use strict';\n\nconst Client = require('ssh2').Client;\nconst servers = new Client();\nconst conn = {};\n\nclass Ssh2 {\n\n    constructor() {\n        this.host = '';\n        this.port = '';\n        this.username = '';\n        this.password = '';\n    }\n\n    async init(json = {}) {\n        this.host = json.host;\n        this.port = json.port;\n        this.username = json.username;\n        this.password = json.password;\n        await this.connectStatus();\n    }\n\n    async connectStatus() {\n        if (!conn[this.host]) {\n            conn[this.host] = await this.connect();\n            const loop = () => { conn[this.host] = null; };\n            servers.on('end', loop);\n            servers.on('error', loop);\n        }\n    }\n\n    // 链接\n    async connect() {\n        return new Promise(resolve => {\n            servers.on('ready', () => {\n                resolve(servers);\n            }).connect({\n                host: this.host,\n                port: this.port,\n                username: this.username,\n                password: this.password,\n            });\n        });\n    }\n\n    // 执行linux命令和shell脚本\n    // 例如：exec('bash /data/down/app.sh')\n    async exec(exec) {\n        await this.connectStatus();\n        return new Promise(resolve => {\n            let str = '';\n            conn[this.host].exec(exec, (err, stream) => {\n                if (err) throw err;\n                stream.on('close', code => {\n                    // conn[this.host].end();\n                    if (code === 0) {\n                        resolve(str);\n                    } else {\n                        resolve('');\n                    }\n                }).on('data', data => {\n                    str += data;\n                }).stderr.on('data', data => {\n                    str += data;\n                });\n            });\n        });\n    }\n\n    // 执行linux命令\n    // 例如 shell('ll -a')\n    async shell(shell) {\n        await this.connectStatus();\n        return new Promise(resolve => {\n            let str = '';\n            conn[this.host].shell((err, stream) => {\n                if (err) throw err;\n                stream.on('close', code => {\n                    // conn[this.host].end();\n                    if (code === 0) {\n                        resolve(str);\n                    } else {\n                        resolve('');\n                    }\n                }).on('data', data => {\n                    str += data;\n                }).stderr.on('data', data => {\n                    str += data;\n                });\n                stream.end(shell);\n            });\n        });\n    }\n\n    // end\n    async end() {\n        if (conn[this.host]) {\n            conn[this.host].end();\n            conn[this.host] = null;\n        }\n    }\n}\n\nmodule.exports = new Ssh2();\n"
  },
  {
    "path": "app/util/utils.js",
    "content": "\n'use strict';\n\nclass Util {\n\n    constructor() {\n        this.name = '';\n    }\n\n    // 判断路径最后是否有 /\n    isDirEnd(path) {\n        if (!path) return '';\n        path = path.trim();\n        let result = false;\n        const last = path.substr(-1);\n        if (last === '/') {\n            result = true;\n        } else {\n            result = false;\n        }\n        return result;\n    }\n\n}\n\nmodule.exports = new Util();\n"
  },
  {
    "path": "app/view/appconfig.html",
    "content": "<style scoped>\n    .config_main{\n        padding:20px;\n        background: #fff;\n    }\n    .config_main .row-col{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n    .config_main .block{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        flex-wrap: wrap;\n    }\n    .config_main .block .left{\n        width:150px;\n        color:#999;\n    }\n    .config_main .block .right{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n    .config_main .block .inp1{\n        width:380px;\n    }\n    .config_main .block .inp2{\n        width:300px;\n    }\n    .config_main .block .textarea{\n        width:800px;\n    }\n    .config_main .btns_submit{\n        padding:50px 0 100px 0;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n    .lable_parent{\n        position: relative;\n    }\n    .lable_parent .delete{\n        position: absolute;\n        right:20px;\n        top:0;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">应用构建配置</div>\n        </el-col>\n    </el-row>\n    <div class=\"config_main mt30\">\n        <div class=\"block mb50\">\n            <div class=\"bottom\">\n                <div class=\"top mb20\">资产列表</div>\n                <div class=\"mb10\">\n                    <el-button @click=\"serverConfigs()\" type=\"primary\" icon=\"el-icon-tickets\" size=\"medium\">脚本任务</el-button>\n                    <el-button @click=\"applicationConsole()\" type=\"danger\" icon=\"el-icon-s-tools\" size=\"medium\">控制台</el-button>\n                    <span class=\"ml10 light\"><i class=\"el-icon-warning fs-16 mr5 primary\"></i>默认执行所有资产项操作，若有选择则执行选中资产项的相关操作(例如：部署、生成构建配置等操作)。\n                    </span>\n                </div>\n                <el-table \n                    :data=\"assetslist\" \n                    ref=\"multipleTable\" \n                    border \n                    @selection-change=\"handleSelectionChange\" \n                    style=\"width:1000px;font-size:12px;\">\n                    <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n                    <el-table-column prop=\"name\" label=\"资产名称\" width=\"200\">\n                    </el-table-column>\n                    <el-table-column prop=\"outer_ip\" label=\"外网IP\" width=\"200\">\n                    </el-table-column>\n                    <el-table-column prop=\"lan_ip\" label=\"内网IP\" width=\"200\">\n                    </el-table-column>\n                    <el-table-column prop=\"user\" label=\"用户名\" width=\"150\">\n                    </el-table-column>\n                    <el-table-column label=\"操作\">\n                        <template slot-scope=\"scope\">\n                            <div>\n                                <el-button @click=\"serverConfigs(scope.row)\" type=\"primary\" size=\"mini\" plain>脚本任务</el-button>\n                            </div>\n                        </template>\n                    </el-table-column>\n                </el-table>\n            </div>\n        </div>\n        <div class=\"block mb50\">\n            <el-button type=\"primary\" icon=\"el-icon-plus\" @click=\"addNewTask\" class=\"ml30\">新建任务</el-button>\n        </div>\n        <el-tabs \n            v-model=\"editableTabsValue\" \n            closable \n            type=\"border-card\"\n            v-if=\"tasklist.length\" \n            @tab-remove=\"removeTab\">\n            <el-tab-pane \n                class=\"lable_parent\" \n                v-for=\"(item,index) in tasklist\" \n                :key=\"index\" \n                :label=\"item.task_name\" \n                :name=\"index+''\">\n                <div class=\"delete\">\n                    <el-button type=\"danger\" icon=\"el-icon-delete\" size=\"medium\" @click=\"deleteItemTask(item,index)\" class=\"ml30\">删除此任务</el-button>\n                </div>\n                <div class=\"block mt30\">\n                    <div class=\"left\">任务名称<span class=\"red\">*</span></div>\n                    <div class=\"right\">\n                        <el-input v-model=\"item.task_name\" class=\"inp1\"></el-input>\n                    </div>\n                </div>\n                <div class=\"block mt30\">\n                    <div class=\"left\">任务类型<span class=\"red\">*</span></div>\n                    <div class=\"right\">\n                        {{item.task_type=='git'?'git hooks任务':''}}\n                        {{item.task_type=='shell'?'shell脚本任务':''}}\n                        {{item.task_type=='command'?'命令行运行任务':''}}\n                    </div>\n                </div>\n                <div v-show=\"item.task_type!='command'?true:false\">\n                    <div v-show=\"item.task_type=='git'?true:false\">\n                        <div class=\"block mt30\">\n                            <div class=\"left\">选择git部署方式<span class=\"red\">*</span></div>\n                            <div class=\"right\">\n                                <el-button :type=\"item.git_type=='https'?'primary':'default'\" size=\"small\" @click=\"selectGitType(index,item,'https')\" round>https方式\n                                </el-button>\n                                <el-button :type=\"item.git_type=='ssh'?'primary':'default'\" size=\"small\" @click=\"selectGitType(index,item,'ssh')\" class=\"ml30\" round>\n                                    ssh方式</el-button>\n                            </div>\n                        </div>\n                        <div class=\"block mt30\">\n                            <div class=\"left\"></div>\n                            <div class=\"right light\">\n                                <span v-if=\"item.git_type=='https'?true:false\">\n                                    <i class=\"el-icon-warning fs-16 mr5 primary\"></i>\n                                    使用https方式拉取代码时需要用户名和密码，若用户名中有<span class=\"red\">@</span>时需要转换为<span class=\"red\">%40</span>。\n                                </span>\n                                <span v-if=\"item.git_type=='ssh'?true:false\">\n                                    <i class=\"el-icon-warning fs-16 mr5 primary\"></i>\n                                    使用ssh方式拉取代码时请确认git端项目已填写ssh key秘钥。\n                                </span>\n                            </div>\n                        </div>\n                    </div>\n                    <div class=\"block mt30\">\n                        <div class=\"left\">部署shell脚本路径<span class=\"red\">*</span></div>\n                        <div class=\"right\">\n                            <el-input v-model=\"item.shell_path\" class=\"inp1\" placeholder=\"例如：/usr/src/sh/build.sh\"></el-input>\n                            <span class=\"ml20 light\">备注：若目录或文件不存在则自动创建。</span>\n                        </div>\n                    </div>\n                    <div class=\"block mt30\">\n                        <div class=\"left\">shell脚本参数</div>\n                        <div class=\"right\">\n                            <el-input v-model=\"item.shell_opction\" class=\"inp1\" placeholder=\"shell脚本参数请以空格隔开\"></el-input>\n                        </div>\n                    </div>\n                    <div class=\"block mt30\">\n                        <div class=\"left\">shell脚本写入方式<span class=\"red\">*</span></div>\n                        <div class=\"right\">\n                            <el-select v-model=\"item.shell_write_type\" class=\"inp1\" placeholder=\"请选择\">\n                                <el-option v-for=\"item in options1\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                                </el-option>\n                            </el-select>\n                        </div>\n                    </div>\n                    <div class=\"block mt30\">\n                        <div class=\"left\">发布前是否先做备份</div>\n                        <div class=\"right\">\n                            <el-switch v-model=\"item.is_backups\"></el-switch>\n                        </div>\n                    </div>\n                    <div class=\"row-col mt30\">\n                        <div class=\"block\">\n                            <div class=\"left\">需要备份的资源<span class=\"red\">*</span></div>\n                            <div class=\"right\">\n                                <el-input v-model=\"item.project_path\" class=\"inp2\" placeholder=\"备份目标可以是文件夹也可以是一个文件\"></el-input>\n                            </div>\n                        </div>\n                        <div class=\"block ml20\">\n                            <div class=\"left\">备份文件存放位置<span class=\"red\">*</span></div>\n                            <div class=\"right\">\n                                <el-input v-model=\"item.backups_path\" class=\"inp2\" placeholder=\"备份文件存储的位置\"></el-input>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div class=\"block mt30\">\n                    <div class=\"left\">{{item.task_type=='command'?'命令行脚本内容':'部署shell脚本'}}<span class=\"red\">*</span></div>\n                    <div>\n                        <div class=\"mt10 right\">\n                            <div :id=\"'editor_'+index\" :style=\"{width:'1000px',height:item.task_type=='command'?'60px':'350px'}\"></div>\n                        </div>\n                        <div class=\"mt10 light\" v-show=\"item.task_type!='command'?true:false\">备注：服务器初始shell脚本。</div>\n                    </div>\n                </div>\n                <div class=\"block mt30\">\n                    <div class=\"left\">备注</div>\n                    <div class=\"right\">\n                        <el-input type=\"textarea\" :rows=\"2\" placeholder=\"选填\" v-model=\"item.remarks\" style=\"width:1000px;\">\n                        </el-input>\n                    </div>\n                </div>\n            </el-tab-pane>\n        </el-tabs>\n        <div class=\"btns_submit\" v-if=\"tasklist.length\">\n            <el-button type=\"primary\" @click=\"submitConfigs\" class=\"mb20\">保存构建配置</el-button>\n            <el-button type=\"danger\" @click=\"goToBuild\" class=\"mb20\" plain>去发布应用</el-button>\n            <el-button type=\"default\" @click=\"goBack\" class=\"mb20\">取消</el-button>\n        </div>\n        \n        <!-- 新建任务model -->\n        <el-dialog width=\"600px\" title=\"新建任务\" :visible.sync=\"dialogTableVisible\">\n            <div class=\"com_model_main\">\n                <div class=\"com_model_block\">\n                    <div class=\"left\">任务名称</div>\n                    <el-input class=\"inp\" v-model.trim=\"task_name\" placeholder=\"请输入任务名称\"></el-input>\n                </div>\n                <div class=\"com_model_block\">\n                    <div class=\"left\">任务类型</div>\n                    <el-select class=\"inp\" v-model=\"task_type\" placeholder=\"请选择\">\n                        <el-option v-for=\"item in options\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                        </el-option>\n                    </el-select>\n                </div>\n                <div class=\"btns\">\n                    <el-button type=\"primary\" @click=\"submitTask\"> 提交 </el-button>\n                </div>\n            </div>\n        </el-dialog>\n    </div>\n</div>\n<script>var require = { paths: { 'vs': 'https://cdn.bootcss.com/monaco-editor/0.16.2/min/vs' } };</script>\n<script src=\"//cdn.bootcss.com/monaco-editor/0.16.2/min/vs/loader.js\"></script>\n<script>\n    const monacos = {};\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                id: util.getQueryString('id'),\n                details:{},\n                assetslist:[],\n                dialogTableVisible:false,\n                options:[{\n                    value: 'git',\n                    label: 'GIT任务'\n                },{\n                    value: 'shell',\n                    label: 'SHELL任务'\n                }, {\n                    value: 'command',\n                    label: '命令行运行任务'\n                }],\n                options1: [{\n                    value: 1,\n                    label: '新建文件并上传方式'\n                }, {\n                    value: 2,\n                    label: 'shell窗口命令行创建方式'\n                }],\n                task_name:'',\n                task_type: '',\n                tasklist:[],\n                editableTabsValue: util.getQueryString('tab') || '0',\n                multipleSelection:[],\n            }\n        },\n        mounted() {\n            this.itemdetail();\n        },\n        methods: {\n            runMonaco(id,data,type) {\n                const moId = `editor_${id}`;\n                if(type) {\n                    monacos[moId].setValue(data);\n                    return;\n                }\n                //载入Monaco\n                let editor = null;\n                _this = this;\n                require(['vs/editor/editor.main'], function () {\n                    //得到支持的语言\n                    var modesIds = monaco.languages.getLanguages().map(function (lang) { return lang.id }).sort();\n                    //创建编辑器\n                    monacos[moId] = editor = monaco.editor.create(document.getElementById(moId), {\n                        value: data,\n                        language: 'shell',\n                        scrollBeyondLastLine: false,\n                        automaticLayout: true,\n                        fontSize:14,\n                        //主题，三款：vs、vs-dark、hc-black\n                        theme: 'vs-dark',\n                        minimap: {\n                            enabled: false\n                        }\n                    });\n                    editor.onDidChangeModelContent(function (e) {\n                        _this.tasklist[id].shell_body = editor.getValue()\n                    });\n                });\n            },\n            itemdetail(type=false){\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/itemdetail`,\n                    data: {\n                        id: this.id,\n                    },\n                    success: data => {\n                        this.details = data.data;\n                        this.assetslist = data.data.assets_list || [];\n                        const tasklist = data.data.task_list || [];\n                        if (tasklist && tasklist.length) {\n                            tasklist.forEach((item, index) => {\n                                item.shell_write_type = item.shell_write_type ? parseInt(item.shell_write_type) : 1;\n                                item.is_backups = item.is_backups == 1 ? true : false;\n                                if(!item.shell_body) item.shell_body = '#!/bin/bash\\r\\n'\n                                this.runMonaco(index, item.shell_body, type)\n                            })\n                        }\n                        this.tasklist = tasklist;\n                        setTimeout(()=>{ this.assetslist.forEach(item => {this.$refs.multipleTable.toggleRowSelection(item, true);})})\n                    }\n                })\n            },\n            selectGitType(index,item,type){\n                item.git_type = type;\n                const https = 'git clone https://xxx%40qq.com:123456@git.xxx.com/www.git'\n                const ssh = 'git clone git@git.xxx.com/www.git'\n                if(type === 'https'){\n                    if(item.shell_body.indexOf(ssh) > -1){\n                        item.shell_body = item.shell_body.replace(ssh, https)\n                    }else if (item.shell_body.indexOf(https) == -1) {\n                        item.shell_body = item.shell_body + '\\r\\n' + https + '\\r\\n'\n                    }\n                }else if(type === 'ssh'){\n                    if (item.shell_body.indexOf(https) > -1) {\n                        item.shell_body = item.shell_body.replace(https, ssh)\n                    }else if (item.shell_body.indexOf(ssh) == -1) {\n                        item.shell_body =  item.shell_body + '\\r\\n' +ssh + '\\r\\n'\n                    }\n                }\n                monacos[`editor_${index}`].setValue(item.shell_body);\n            },\n            addNewTask(){\n                this.task_type = '';\n                this.task_name = '';\n                this.dialogTableVisible = true;\n            },\n            submitTask(){\n                if(!this.task_name){\n                    popup.alert({title:'请填写任务名称!'})\n                    return;\n                }\n                if (!this.task_type) {\n                    popup.alert({ title: '请选择任务类型!' })\n                    return;\n                }\n                this.dialogTableVisible = false;\n                this.editableTabsValue = this.tasklist.length + '';\n                let default_shell_body = '';\n                if(this.task_type === 'git'){\n                    default_shell_body = '#!/bin/bash\\r\\ngit clone https://xxx%40qq.com:123456@git.xxx.com/www.git\\r\\n'\n                } else if(this.task_type === 'shell'){\n                    default_shell_body = '#!/bin/bash\\r\\n';\n                }\n\n                this.tasklist.push({\n                    task_name:this.task_name,\n                    task_type: this.task_type,\n                    shell_body: default_shell_body,\n                    git_type:'https',\n                    is_backups:false,\n                })\n                setTimeout(()=>{ this.runMonaco(this.tasklist.length - 1, default_shell_body) },0)\n            },\n            removeTab(targetName){\n                popup.confirm({title: `确定删除此任务吗？`, yes: () => {\n                    let tabs = this.tasklist;\n                    let activeName = this.editableTabsValue;\n                    activeName = tabs[targetName + 1]? targetName: activeName - 1;\n                    this.editableTabsValue = activeName + '';\n                    this.tasklist = tabs.filter((tab,index) => index != targetName);\n                }})\n            },\n            deleteItemTask(item,index){\n                popup.confirm({ title: `确定删除${item.task_name}任务吗？`, yes: () => {\n                    this.editableTabsValue = '0';\n                    this.tasklist.splice(index, 1);\n                }})\n            },\n            goBack(){\n                location.href=\"/application\"\n            },\n            submitConfigs(){\n                this.tasklist.forEach((item,index)=>{\n                    item.is_backups = item.is_backups ? 1 : 0;\n                    item.shell_body = monacos[`editor_${index}`].getValue() || '#!/bin/bash\\r\\n';\n                    if(item.shell_body.indexOf('SUCCESSFUL_COMMAND_CONSTRUCTION') == -1 && item.task_type != 'command') {\n                        item.shell_body = item.shell_body + '\\r\\n#以下内容由系统默认增加,放置于最后,不要做任何修改,用于判断shell是否执行成功\\r\\nif [ $? -eq 0 ]\\r\\nthen\\r\\n    echo \"SUCCESSFUL_COMMAND_CONSTRUCTION\"\\r\\nelse\\r\\n    echo \"ERROR_COMMAND_CONSTRUCTION\"\\r\\nfi'\n                    }\n                })\n                util.ajax({\n                    url: `${config.baseApi}api/v1/application/updateConfigs`,\n                    data: {\n                        id:this.id,\n                        tasklist:this.tasklist,\n                    },\n                    success: data => {\n                        this.itemdetail(true);\n                        popup.miss({title:'操作成功!'})\n                    }\n                })\n            },\n            handleSelectionChange(val) {\n                this.multipleSelection = val;\n            },\n            serverConfigs(item) {\n                if (!item && !this.multipleSelection.length) {\n                    popup.alert({ title: '请选择需要配置的资产!' })\n                    return;\n                }\n                const assestsList = item ? [item] : this.multipleSelection;\n                util.setStorage('session', 'assets_list_configs', JSON.stringify(assestsList));\n                location.href = \"/assetsconfig\";\n            },\n            goToBuild(){\n                location.href = \"/buildprocess?id=\"+this.id;\n            },\n            applicationConsole() {\n                if (!this.multipleSelection.length) {\n                    popup.alert({ title: '请选择进入控制台的资产列表!' })\n                    return;\n                }\n                let ids = '';\n                this.multipleSelection && this.multipleSelection.forEach(item => {\n                    ids = ids + item._id + ','\n                })\n                ids = ids ? ids.slice(0, -1) : '';\n                window.open(\"/console?ids=\" + ids);\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/application.html",
    "content": "<style scoped>\n    .com_top .inp{\n        width:180px;\n    }\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n    .model_assets_list .btns{\n        border-top:solid 1px #eee;\n        padding:35px 0 0;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n    }\n    .model_assets_list .ml{\n        margin-left:20px;\n    }\n    .model_assets_list .common_pages{\n        justify-content: flex-start;\n    }\n    .assets_item_list:not(:last-child){\n        border-bottom:solid 1px #eee;\n        padding-bottom:5px;\n        margin-bottom:5px;\n    }\n    .assets_item_list.first{\n        border-bottom:none;\n        padding-bottom:0px;\n        margin-bottom:0px;\n    }\n    .assets_item_item{\n        font-size:12px;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n    .assets_item_item .as_1{\n        display: inline-block;\n        width:220px;\n    }\n    .assets_item_item .as_2{\n        display: inline-block;\n        width:150px;\n    }\n    .model_assets_list .b_t{\n        padding-top:15px;\n        border-top:solid 1px #eee; \n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">应用管理</div>\n        </el-col>\n    </el-row>\n    <div class=\"com_top\">\n        <el-select v-model=\"environ_code\" class=\"inp\" @change=\"getList\" placeholder=\"部署环境筛选\">\n            <el-option v-for=\"item in environlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-select v-model=\"team_code\" class=\"inp\" @change=\"getList\" placeholder=\"所属团队筛选\">\n            <el-option v-for=\"item in teamlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-select v-model=\"net_type\" class=\"inp\" @change=\"getList\" placeholder=\"部署网络方式筛选\">\n            <el-option v-for=\"item in netoption\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-select v-model=\"status\" class=\"inp\" @change=\"getList\" placeholder=\"应用状态筛选\">\n            <el-option v-for=\"item in statusopction\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-input v-model=\"app_name\" class=\"inp ml20\" placeholder=\"按应用名称查询\"></el-input>\n        <el-button @click=\"getList\" type=\"primary\" >搜索</el-button>\n    </div>\n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">应用管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\" @click=\"handleApplication(1)\">新增应用</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\" :row-class-name=\"tableRowClassName\">\n                <el-table-column prop=\"name\" label=\"应用名称\" width=\"180\">\n                </el-table-column>\n                <el-table-column label=\"所属团队\" width=\"120\">\n                    <template slot-scope=\"scope\">\n                        <div>{{scope.row.teamlist[0]?scope.row.teamlist[0].name:''}}</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"所属环境\" width=\"100\">\n                    <template slot-scope=\"scope\">\n                        <div class=\"success\">{{scope.row.environlist[0]?scope.row.environlist[0].name:''}}</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"拥有资产\" width=\"510\">\n                    <template slot-scope=\"scope\">\n                        <div class=\"assets_item_list\" \n                            :class=\"{'first':scope.row.assets_list.length==1?true:false}\"\n                            v-for=\"(item,index) in scope.row.assets_list\" \n                            :key=\"index\">\n                            <div class=\"assets_item_item\" >\n                                <span class=\"as_1\">资产名称：{{item.name}}</span>\n                                <span class=\"as_2\" v-if=\"scope.row.net_type==1?true:false\">外网IP：{{item.outer_ip}}</span>\n                                <span class=\"as_2\" v-if=\"scope.row.net_type==2?true:false\">内网IP：{{item.lan_ip}}</span>\n                                <el-button type=\"primary\" size=\"small\" @click=\"buildApplication(scope.row,item)\" plain>单机部署</el-button>\n                            </div>\n                        </div>\n                    </template>\n                </el-table-column>\n                <el-table-column prop=\"user_name\" label=\"操作人\" width=\"80\">\n                </el-table-column>\n                <el-table-column label=\"部署网络方式\" width=\"120\">\n                    <template slot-scope=\"scope\">\n                        <div>{{scope.row.net_type == 1?'外网IP':'内网IP'}}</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"可用状态\" width=\"100\">\n                    <template slot-scope=\"scope\">\n                        <div v-if=\"scope.row.status == 1\" class=\"success\">已启用</div>\n                        <div v-if=\"scope.row.status != 1\" class=\"red\">已禁用</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"操作\" width=\"320\" fixed=\"right\">\n                    <template slot-scope=\"scope\">\n                        <div class=\"mb10\">\n                            <el-button @click=\"buildApplication(scope.row)\" type=\"primary\" size=\"small\">快速部署</el-button>\n                            <el-button @click=\"setAppConfig(scope.row)\" type=\"success\" size=\"small\" plain>构建配置</el-button>\n                            <el-button @click=\"handleApplication(2,scope.row)\" type=\"primary\" size=\"small\">编辑</el-button>\n                        </div>\n                        <div class=\"mb10\">\n                            <el-button @click=\"distAsstes(scope.row)\" type=\"success\" size=\"small\" plain>分配资产</el-button>\n                            <el-button @click=\"distEmails(scope.row)\" type=\"warning\" size=\"small\" plain>分配邮件</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" type=\"danger\" size=\"small\">删除</el-button>\n                        </div>\n                        <div>\n                            <el-button @click=\"applicationConsole(scope.row)\" type=\"primary\" size=\"small\">控制台</el-button>\n                            <el-button @click=\"serverConfigs(scope.row)\" type=\"primary\" size=\"small\" plain>脚本任务</el-button>\n                            <el-button v-if=\"scope.row.status==1?true:false\" @click=\"hanleUse(scope.row)\" type=\"danger\" size=\"small\" plain>禁用\n                            </el-button>\n                            <el-button v-if=\"scope.row.status==0?true:false\" @click=\"hanleUse(scope.row)\" type=\"success\" size=\"small\">启用</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"common_pages\">\n            <el-pagination background @current-change=\"handleCurrentChange\"\n                :current-page.sync=\"currentPage\" :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\"\n                :total=\"totalCount\">\n            </el-pagination>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog width=\"500px\" title=\"应用编辑\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"com_model_block\">\n                <div class=\"left\">所属团队<span class=\"red\">*</span></div>\n                <el-select class=\"inp\" v-model=\"team.team_code\" placeholder=\"请选择\">\n                    <el-option v-for=\"item in teamlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                </el-option>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">选择环境<span class=\"red\">*</span></div>\n                <el-select class=\"inp\" v-model=\"team.environ_code\" placeholder=\"请选择\">\n                    <el-option v-for=\"item in environlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                    </el-option>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">应用名称<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"team.name\" placeholder=\"请输应用名称\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">应用编码<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"team.code\" placeholder=\"以app_开头、例如app_zxhy\" :disabled=\"team.type==2?true:false\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">启用状态</div>\n                <el-switch v-model=\"team.status\" active-color=\"#409EFF\"></el-switch>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitApplication\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n    <!-- 分配资产 -->\n    <el-dialog width=\"950px\" class=\"model_assets_list\" title=\"\b分配资产\" :visible.sync=\"assets.dialogAppVisible\">\n        <el-table :data=\"assets.assestsList\" ref=\"multipleTable\" @selection-change=\"handleSelectionChange\">\n            <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n            <el-table-column property=\"name\" label=\"所属团队\" width=\"150\"></el-table-column>\n            <el-table-column property=\"name\" label=\"资产名称\" width=\"150\"></el-table-column>\n            <el-table-column property=\"code\" label=\"资产编码\" width=\"150\"></el-table-column>\n            <el-table-column property=\"outer_ip\" label=\"外网IP\" width=\"200\"></el-table-column>\n            <el-table-column property=\"lan_ip\" label=\"内网IP\" width=\"200\"></el-table-column>\n        </el-table>\n        <div class=\"common_pages\">\n            <el-pagination background @current-change=\"handleAssetsCurrentChange\" :current-page.sync=\"assets.currentPage\"\n                :page-size=\"assets.pageSize\" layout=\"prev, pager, next, jumper\" :total=\"assets.totalCount\">\n            </el-pagination>\n        </div>\n        <div class=\"com_model_block b_t\">\n            <div class=\"left\">网络部署方式</div>\n            <el-select class=\"inp\" v-model=\"assets.net_type\" placeholder=\"请选择\">\n                <el-option v-for=\"item in netoption\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                </el-option>\n        </div>\n        <div class=\"btns\">\n            <el-button type=\"primary\" @click=\"submitAppsAssets\">确定</el-button>\n            <el-button class=\"ml\" @click=\"assets.dialogAppVisible=false\">取消</el-button>\n        </div>\n    </el-dialog>\n    <!-- 分配邮件 -->\n    <el-dialog width=\"700px\" class=\"model_assets_list\" title=\"\b分配邮件\" :visible.sync=\"email.dialogEmailVisible\">\n        <el-table :data=\"email.emailsList\">\n            <el-table-column property=\"email\" label=\"邮件地址\" width=\"200\"></el-table-column>\n            <el-table-column property=\"name\" label=\"邮件所属人\" width=\"200\"></el-table-column>\n            <el-table-column label=\"操作\">\n                <template slot-scope=\"scope\">\n                    <el-button v-if=\"!scope.row.selected\" @click=\"handleEmails(1,scope.row)\" type=\"primary\" size=\"small\">绑定</el-button>\n                    <el-button v-if=\"scope.row.selected\" @click=\"handleEmails(2,scope.row)\" type=\"danger\" size=\"small\" plain>取消</el-button>\n                </template>\n            </el-table-column>\n        </el-table>\n        <div class=\"common_pages\">\n            <el-pagination background @current-change=\"handleAssetsCurrentChange\" :current-page.sync=\"assets.currentPage\"\n                :page-size=\"assets.pageSize\" layout=\"prev, pager, next, jumper\" :total=\"assets.totalCount\">\n            </el-pagination>\n        </div>\n    </el-dialog>\n</div>\n<script>\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                team_code:'',\n                environ_code:'',\n                net_type:'',\n                app_name:'',\n                status:'',\n                teamlist: [],\n                environlist:[],\n                datalist: [],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                dialogTableVisible: false,\n                type: 1,\n                team: {\n                    team_code:'',\n                    environ_code:'',\n                    type: 1,\n                    _id: '',\n                    name: '',\n                    code: '',\n                    status: true,\n                },\n                assets:{\n                    net_type:1,\n                    appItem:{},\n                    dialogAppVisible: false,\n                    multipleSelection: [],\n                    assestsList: [],\n                    currentPage: 1,\n                    pageSize:10,\n                    totalCount: 0,\n                },\n                netoption: [{\n                    value: 1,\n                    label: '外网IP'\n                }, {\n                    value: 2,\n                    label: '内网IP'\n                }],\n                statusopction: [{\n                    value: 1,\n                    label: '启用'\n                }, {\n                    value: 0,\n                    label: '禁用'\n                }],\n                email:{\n                    dialogEmailVisible:false,\n                    emailsList: [],\n                    emailItem: [],\n                    appId:'',\n                    currentPage: 1,\n                    pageSize: 10,\n                    totalCount: 0,\n                },\n            }\n        },\n        filters: {\n        },\n        mounted() {\n            this.getList();\n            this.getTeamList();\n            this.getEnvironList();\n            this.getAssestList();\n            this.getEmailList();\n        },\n        methods: {\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/list`,\n                    data: {\n                        team_code: this.team_code,\n                        environ_code:this.environ_code,\n                        net_type:this.net_type,\n                        status:this.status,\n                        app_name:this.app_name,\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                    },\n                    success: data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            getTeamList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/team/list`,\n                    data:{\n                        pageNo:1,\n                        pageSize:100,\n                        status:1,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item=>{\n                            options.push({\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.teamlist = options;\n                    }\n                })\n            },\n            getEnvironList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/environment/list`,\n                    data: {\n                        pageNo: 1,\n                        pageSize: 100,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.environlist = options;\n                    }\n                })\n            },\n            getAssestList(){\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/assets/list`,\n                    data: {\n                        status: 1,\n                        pageNo: this.assets.currentPage,\n                        pageSize: this.assets.pageSize,\n                    },\n                    success: data => {\n                        this.assets.totalCount = data.data.totalNum;\n                        this.assets.assestsList = data.data.datalist;\n                    }\n                })\n            },\n            distAsstes(item){\n                this.assets.appItem = item;\n                this.assets.dialogAppVisible = true;\n                const assets_list = item.assets_list || [];\n                this.assets.multipleSelection = assets_list;\n                if(assets_list.length) {\n                    this.assets.assestsList.forEach(item_1=>{\n                        assets_list.forEach(item_2=>{\n                            if(item_1.code === item_2.code){\n                                setTimeout(()=>{ this.$refs.multipleTable.toggleRowSelection(item_1, true); },100)\n                            }\n                        })\n                    })\n                }\n                this.$refs.multipleTable && this.$refs.multipleTable.clearSelection();\n            },\n            handleSelectionChange(val) {\n                this.assets.multipleSelection = val;\n            },\n            handleAssetsCurrentChange(){\n                this.assets.currentPage = val;\n                this.getAssestList();\n            },\n            submitAppsAssets(){\n                const assets_list = [];\n                util.ajax({\n                    url: `${config.baseApi}api/v1/application/distribution`,\n                    data: {\n                        _id: this.assets.appItem._id,\n                        assets_list: this.assets.multipleSelection,\n                        net_type:this.assets.net_type,\n                    },\n                    success: data => {\n                        this.assets.dialogAppVisible = false;\n                        this.getList();\n                    }\n                })\n            },\n            submitApplication() {\n                if (!this.team.team_code) {\n                    popup.alert({ title: '请选择所属团队!' });\n                    return;\n                }\n                if (!this.team.environ_code) {\n                    popup.alert({ title: '请选择所属环境!' });\n                    return;\n                }\n                if (!this.team.name) {\n                    popup.alert({ title: '请填写应用名称!' });\n                    return;\n                }\n                if (!this.team.code) {\n                    popup.alert({ title: '请填写应用编码!' });\n                    return;\n                }\n                this.team.status = this.team.status ? 1 : 0;\n                const userMsg = util.getStorage('local', 'userMsg');\n                this.team.user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n                util.ajax({\n                    url: `${config.baseApi}api/v1/application/handle`,\n                    data: this.team,\n                    success: data => {\n                        this.dialogTableVisible = false;\n                        this.getList();\n                        popup.miss({ title: '操作成功!' });\n                    }\n                })\n            },\n            handleApplication(type, item) {\n                if (item) item = JSON.parse(JSON.stringify(item));\n                if (item) {\n                    this.team = {\n                        team_code: item.team_code,\n                        environ_code: item.environ_code,\n                        type: item.type,\n                        _id: item._id,\n                        name: item.name,\n                        code: item.code,\n                        status: item.status == 1 ? true : false,\n                    };\n                } else {\n                    this.team = {\n                        team_code: '',\n                        environ_code:'',\n                        type: 1,\n                        _id: '',\n                        name: '',\n                        code: 'app_' + util.randomString(12),\n                        status: true,\n                    }\n                }\n                this.team.type = type;\n                this.dialogTableVisible = true;\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            hanleUse(item) {\n                let str = item.status == 1 ? '禁用' : '启用';\n                popup.confirm({\n                    title: `确定${str}${item.name}应用吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/application/setStatus`,\n                            data: {\n                                _id: item._id,\n                                status: item.status == 1 ? 0 : 1,\n                            },\n                            success: data => {\n                                item.status = item.status == 1 ? 0 : 1,\n                                    popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            hanleDelete(item) {\n                popup.confirm({\n                    title: `确定删除${item.name}应用吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/application/delete`,\n                            data: {\n                                _id: item._id,\n                            },\n                            success: data => {\n                                this.getList();\n                                popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            tableRowClassName({ row, rowIndex }) {\n                if (row.status != 1) {\n                    return 'disabled-row';\n                }\n                return '';\n            },\n            setAppConfig(item){\n               location.href = \"/appconfig?id=\" + item._id;\n            },\n            getEmailList(){\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/email/list`,\n                    data:{\n                        status: 1,\n                        pageNo: this.email.currentPage,\n                        pageSize:this.email.pageSize,\n                    },\n                    success: data => {\n                        this.email.totalCount = data.data.totalNum;\n                        this.email.emailsList = data.data.datalist;\n                    }\n                })\n            },\n            distEmails(item){\n                 const itemEmailList = item.email_list || [];\n                this.email.emailItem = itemEmailList;\n                this.email.appId = item._id;\n                this.email.dialogEmailVisible = true;\n                const emailsList = this.email.emailsList;\n                emailsList.forEach(item=>{\n                    item.selected = false;\n                    if(itemEmailList.includes(item.email)){\n                        item.selected = true;\n                    }\n                })\n                this.email.emailsList = emailsList;\n            },\n            handleEmails(type,item){\n                if(type === 1){\n                    // 绑定\n                    this.email.emailItem.push(item.email);\n                }else if(type === 2){\n                    // 取消\n                    const index = this.email.emailItem.indexOf(item.email);\n                    this.email.emailItem.splice(index,1)\n                }\n                util.ajax({\n                    url: `${config.baseApi}api/v1/application/handleEmail`,\n                    data:{\n                        id: this.email.appId,\n                        emaillist: this.email.emailItem,\n                    },\n                    success:data=>{\n                        this.email.dialogEmailVisible = false;\n                        this.getList();\n                        popup.miss({title:'操作成功!'})\n                    }\n                })\n            },\n            buildApplication(item,assetsItem){\n                if(!item.task_list || !item.task_list.length){\n                    popup.alert({ title: '请先增加应用构建配置!' })\n                    return\n                }\n                let url = '/buildprocess?id=' + item._id;\n                if(assetsItem) url = url + '&assetsId=' + assetsItem._id;\n                location.href = url;\n            },\n            applicationConsole(item){\n                if (!item.assets_list || !item.assets_list.length) {\n                    popup.alert({ title: '请先给应用分配资产!' })\n                    return\n                }\n                let ids = '';\n                item.assets_list && item.assets_list.forEach(item=>{\n                    ids = ids + item._id + ','\n                })\n                ids = ids ? ids.slice(0,-1) : '';\n                window.open(\"/console?ids=\" + ids);\n            },\n            serverConfigs(item) {\n                if(!item.assets_list || !item.assets_list.length) {\n                    popup.alert({title:'请先给应用分配资产!'})\n                    return\n                }\n                util.setStorage('session', 'assets_list_configs', JSON.stringify(item.assets_list));\n                location.href = \"/assetsconfig\";\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/assets.html",
    "content": "<style scoped>\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n    .com_model_block .inp{\n        width:220px;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">资产管理</div>\n        </el-col>\n    </el-row>\n    <div class=\"com_top\">\n        <el-select v-model=\"team_code\" class=\"inp\" @change=\"getList\" placeholder=\"选择所属团队\">\n            <el-option v-for=\"item in teamlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-input v-model=\"assets_name\" class=\"inp ml20\" placeholder=\"按资产名称查询\"></el-input>\n        <el-button @click=\"getList\" type=\"primary\">搜索</el-button>\n    </div>\n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">资产管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\" @click=\"handleAssets(1)\">新增资产</el-button>\n                <el-button type=\"danger\" size=\"medium\" icon=\"el-icon-s-tools\" @click=\"serverConfigs()\">脚本任务</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\" :row-class-name=\"tableRowClassName\" @selection-change=\"handleSelectionChange\">\n                <el-table-column type=\"selection\" width=\"55\">\n                </el-table-column>\n                <el-table-column prop=\"name\" label=\"资产名称\">\n                </el-table-column>\n                <el-table-column prop=\"code\" label=\"资产编码\" width=\"180\">\n                </el-table-column>\n                <el-table-column label=\"所属团队\">\n                    <template slot-scope=\"scope\">\n                        <div>{{scope.row.teamlist[0]?scope.row.teamlist[0].name:''}}</div>\n                    </template>\n                </el-table-column>\n                <el-table-column prop=\"outer_ip\" label=\"外网IP\" width=\"150\">\n                </el-table-column>\n                <el-table-column prop=\"lan_ip\" label=\"内网IP\" width=\"150\">\n                </el-table-column>\n                <el-table-column prop=\"user\" label=\"账号\" width=\"100\">\n                </el-table-column>\n                <el-table-column prop=\"port\" label=\"端口号\" width=\"100\">\n                </el-table-column>\n                <el-table-column prop=\"user_name\" label=\"操作人\" width=\"80\">\n                </el-table-column>\n                <el-table-column label=\"可用状态\" width=\"100\">\n                    <template slot-scope=\"scope\">\n                        <div v-if=\"scope.row.status == 1\" class=\"success\">已启用</div>\n                        <div v-if=\"scope.row.status != 1\" class=\"red\">已禁用</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"操作\" width=\"300\" fixed=\"right\">\n                    <template slot-scope=\"scope\">\n                        <div class=\"mb10\">\n                            <el-button type=\"primary\" size=\"small\" @click=\"goToConsole(scope.row)\">控制台</el-button>\n                            <el-button type=\"success\" size=\"small\" @click=\"serverConfigs(scope.row)\">脚本任务</el-button>\n                            <el-button v-if=\"scope.row.status==1?true:false\" @click=\"hanleUse(scope.row)\" type=\"danger\"\n                                size=\"small\" plain>禁用</el-button>\n                            <el-button v-if=\"scope.row.status==0?true:false\" @click=\"hanleUse(scope.row)\" type=\"success\"\n                                size=\"small\">启用</el-button>\n                        </div>\n                        <div>\n                            <el-button @click=\"handleAssets(2,scope.row)\" type=\"primary\" size=\"small\" plain>编辑</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" type=\"danger\" size=\"small\">删除</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"common_pages\">\n            <el-pagination background @size-change=\"handleSizeChange\" @current-change=\"handleCurrentChange\"\n                :current-page.sync=\"currentPage\" :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\"\n                :total=\"totalCount\">\n            </el-pagination>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog width=\"50%\" title=\"资产编辑\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"mb20 light\" v-if=\"team.type==2?true:false\">\n                <i class=\"el-icon-warning fs-16 mr5 primary\"></i>\n                更改了资产配置项后，所有相关应用需要手动更新分配资产按钮，以达到资产数据的同步更新!\n            </div>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">所属团队<span class=\"red\">*</span></div>\n                        <el-select class=\"inp\" v-model=\"team.team_code\" placeholder=\"请选择\">\n                            <el-option v-for=\"item in teamlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                        </el-option>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block ml20\">\n                        <div class=\"left\">资产名称<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.name\" placeholder=\"请输资产名称\"></el-input>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">资产编码<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.code\" placeholder=\"以ass_开头、例如ass_zxhy\" :disabled=\"team.type==2?true:false\"></el-input>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block ml20\">\n                        <div class=\"left\">外网IP<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.outer_ip\" placeholder=\"请输入外网IP\"></el-input>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">内网IP<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.lan_ip\" placeholder=\"请输入内容IP\"></el-input>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block ml20\">\n                        <div class=\"left\">登录用户名<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.user\" placeholder=\"请输入登录用户名\"></el-input>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">登录端口号<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.port\" placeholder=\"请输入登录端口号\"></el-input>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block ml20\">\n                        <div class=\"left\">登录密码<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.password\" placeholder=\"请输入登录密码\"></el-input>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">启用状态</div>\n                        <el-switch v-model=\"team.status\" active-color=\"#409EFF\"></el-switch>\n                    </div>\n                </el-col>\n            </el-row>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitAssets\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n</div>\n<script>\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                team_code:'',\n                assets_name:'',\n                teamlist:[],\n                datalist: [],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                dialogTableVisible: false,\n                type: 1,\n                team: {\n                    team_code:'',\n                    type: 1,\n                    _id: '',\n                    name: '',\n                    code: '',\n                    outer_ip:'',\n                    lan_ip:'',\n                    user:'',\n                    port:'',\n                    password:'',\n                    status: true,\n                },\n                multipleSelection:[],\n            }\n        },\n        filters: {\n        },\n        mounted() {\n            this.getList();\n            this.getTeamList();\n        },\n        methods: {\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/assets/list`,\n                    data: {\n                        team_code: this.team_code,\n                        assets_name: this.assets_name,\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                    },\n                    success: data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            getTeamList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/team/list`,\n                    data: {\n                        pageNo: 1,\n                        pageSize: 100,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.teamlist = options;\n                    }\n                })\n            },\n            submitAssets() {\n                if (!this.team.team_code) {\n                    popup.alert({ title: '请选择所属团队!' });\n                    return;\n                }\n                if (!this.team.name) {\n                    popup.alert({ title: '请填写资产名称!' });\n                    return;\n                }\n                if (!this.team.code) {\n                    popup.alert({ title: '请填写资产编码!' });\n                    return;\n                }\n                if (!this.team.outer_ip) {\n                    popup.alert({ title: '请填写外网IP地址!' });\n                    return;\n                }\n                if (!this.team.lan_ip) {\n                    popup.alert({ title: '请填写内网IP地址!' });\n                    return;\n                }\n                if (!this.team.user) {\n                    popup.alert({ title: '请填写登录用户名!' });\n                    return;\n                }\n                if (!this.team.password) {\n                    popup.alert({ title: '请填写登录用密码!' });\n                    return;\n                }\n                this.team.status = this.team.status ? 1 : 0;\n                const userMsg = util.getStorage('local', 'userMsg');\n                this.team.user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n                util.ajax({\n                    url: `${config.baseApi}api/v1/assets/handle`,\n                    data: this.team,\n                    success: data => {\n                        this.dialogTableVisible = false;\n                        this.getList();\n                        popup.miss({ title: '操作成功!' });\n                    }\n                })\n            },\n            handleAssets(type, item) {\n                if(item) item = JSON.parse(JSON.stringify(item));\n                if (item) {\n                    this.team = item;\n                    this.team.status = item.status == 1 ? true : false;\n                } else {\n                    this.team = {\n                        team_code: '',\n                        type: 1,\n                        _id: '',\n                        name: '',\n                        code: 'ass_' + util.randomString(12),\n                        outer_ip: '',\n                        lan_ip: '',\n                        user: '',\n                        port: '',\n                        password: '',\n                        status: true,\n                    }\n                }\n                this.team.type = type;\n                this.dialogTableVisible = true;\n            },\n            handleSizeChange(val) {\n                console.log(`每页 ${val} 条`);\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            hanleUse(item) {\n                let str = item.status == 1 ? '禁用' : '启用';\n                popup.confirm({\n                    title: `确定${str}${item.name}资产吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/assets/setStatus`,\n                            data: {\n                                _id: item._id,\n                                status: item.status == 1 ? 0 : 1,\n                            },\n                            success: data => {\n                                item.status = item.status == 1 ? 0 : 1,\n                                    popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            hanleDelete(item) {\n                popup.confirm({\n                    title: `确定删除${item.name}资产吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/assets/delete`,\n                            data: {\n                                _id: item._id,\n                            },\n                            success: data => {\n                                this.getList();\n                                popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            tableRowClassName({ row, rowIndex }) {\n                if (row.status != 1) {\n                    return 'disabled-row';\n                }\n                return '';\n            },\n            handleSelectionChange(val) {\n                this.multipleSelection = val;\n            },\n            serverConfigs(item) {\n                if(!item && !this.multipleSelection.length){\n                    popup.alert({title:'请选择需要配置的资产!'})\n                    return;\n                }\n                const assestsList = item ? [item] : this.multipleSelection;\n                util.setStorage('session','assets_list_configs',JSON.stringify(assestsList));\n                location.href = \"/assetsconfig\";\n            },\n            goToConsole(assetsItem){\n                window.open(\"/console?ids=\" + assetsItem._id)\n            },\n        },\n    })\n</script>"
  },
  {
    "path": "app/view/assetsconfig.html",
    "content": "<style scoped>\n    .config_main {\n        padding: 20px;\n        background: #fff;\n    }\n    .bottom .top{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: space-between;\n    }\n    .btns{\n        width:60%;\n        display: flex;\n        flex-direction: row;\n        flex-wrap: wrap;\n        margin-top:30px;\n        align-items: center;\n        justify-content: flex-start;\n    }\n    .comm_build_textarea {\n        width: 840px;\n        height: 220px;\n    }\n    .com_model_block .left{\n        width:130px;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">资产脚本任务</div>\n        </el-col>\n    </el-row>\n    <div class=\"config_main mt30\">\n        <div class=\"bottom\">\n            <div class=\"top mb20\">\n                <div>资产列表</div> \n                <el-button @click=\"addNewCommTask\" type=\"primary\" icon=\"el-icon-plus\" size=\"small\">新建脚本任务</el-button>\n            </div>\n            <div class=\"mb20\">\n                <span class=\"ml10 light\">\n                    <i class=\"el-icon-warning fs-16 mr5 primary\"></i>\n                    资产脚本任务可进行各种应用的安装，各种应用的版本检测，支持各种脚本命令，支持单机和多机运行。\n                </span>\n            </div>\n            <el-table \n                :data=\"assetslist\" border \n                ref=\"multipleTable\"\n                @selection-change=\"handleSelectionChange\"\n                style=\"width:100%;font-size:13px;\">\n                <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n                <el-table-column prop=\"name\" label=\"资产名称\">\n                </el-table-column>\n                <el-table-column prop=\"code\" label=\"资产编码\">\n                </el-table-column>\n                <el-table-column prop=\"outer_ip\" label=\"外网IP\">\n                </el-table-column>\n                <el-table-column prop=\"lan_ip\" label=\"内网IP\">\n                </el-table-column>\n                <el-table-column prop=\"user\" label=\"用户名\" width=\"120\">\n                </el-table-column>\n                <el-table-column label=\"操作\"  width=\"500\" fixed=\"right\">\n                    <template slot-scope=\"scope\" >\n                        <div>\n                            <span v-for=\"(item,index) in tasklist\" :key=\"index\">\n                                <el-button \n                                    @click=\"showTaskOpctions(2,item,scope.row)\"\n                                    v-if=\"item.type==1?true:false\" \n                                    :type=\"item.btn_color\" \n                                    :plain=\"item.is_plain==1?true:false\"\n                                    size=\"mini\" \n                                    >{{item.name}}</el-button>\n                                    <div v-if=\"index!==0 && index%3===0\" class=\"mb10\"></div>\n                            </span>\n                            <el-button @click=\"showSshKey(scope.row)\" type=\"danger\" size=\"mini\">获得SSH KEY</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"btns\">\n            <span \n                v-for=\"(item,index) in tasklist\" \n                :key=\"index\" \n                v-if=\"item.type==2?true:false\" \n                class=\"ml10\" >\n                <el-button \n                    @click=\"showTaskOpctions(1,item)\"\n                    :type=\"item.btn_color\" \n                    :plain=\"item.is_plain==1?true:false\"\n                    size=\"medium\">{{item.name}}</el-button>\n            </span>\n        </div>\n        <!-- ssk 弹出框 -->\n        <el-dialog width=\"800px\" title=\"SSH KEY\" :visible.sync=\"sshkey.dialogSshKeyVisible\">\n            <div class=\"com_model_main\">\n                <div class=\"mb20 common_tops\"><i class=\"el-icon-warning\"></i>请尽量使用获得ssh key按钮，若生成新的ssh key则相关地方均需要更改（例如：github\n                    ssk）</div>\n                <div>\n                    <el-button @click=\"getSshKey(1)\" type=\"primary\" plain>获得SSH KEY</el-button>\n                </div>\n                <div class=\"block mt30\">\n                    <div class=\"left\" style=\"width:80px;\">邮箱地址</div>\n                    <div class=\"right\">\n                        <el-input v-model=\"sshkey.email\" style=\"width:200px;margin-right:15px;\" placeholder=\"用来生成ssh key的邮箱备注\">\n                        </el-input>\n                        <el-button @click=\"getSshKey(2)\" class=\"ml10\" type=\"danger\" plain>生成新的SSH KEY</el-button>\n                    </div>\n                </div>\n                <div class=\"mt30 mb20\">ssh key</div>\n                <el-input type=\"textarea\" :rows=\"6\" v-model=\"sshkey.sshKey\"></el-input>\n            </div>\n        </el-dialog>\n\n        <!-- model弹出框 -->\n        <el-dialog width=\"1000px\" title=\"运行脚本任务\" :visible.sync=\"dialogTableVisible\">\n            <div class=\"com_model_main\">\n                <el-row>\n                    <el-col :span=\"12\">\n                        <div class=\"com_model_block\">\n                            <div class=\"left\">任务名称：</div>\n                            <div>{{taskItem.name}}</div>\n                        </div>\n                    </el-col>\n                    <el-col :span=\"12\">\n                        <div class=\"com_model_block\">\n                            <div class=\"left\">任务类型：</div>\n                            <div>{{taskItem.type==1?'窗口运行命令':''}}{{taskItem.type==2?'shell脚本命令':''}}</div>\n                        </div>\n                    </el-col>\n                </el-row>\n                <el-row>\n                    <el-col :span=\"12\">\n                        <div class=\"com_model_block\" v-if=\"taskItem.type==2?true:false\">\n                            <div class=\"left\">shell脚本路径：</div>\n                            <div>{{taskItem.shell_path}}</div>\n                        </div>\n                    </el-col>\n                    <el-col :span=\"12\">\n                        <div class=\"com_model_block\" v-if=\"taskItem.type==2?true:false\">\n                            <div class=\"left\">shell脚本参数：</div>\n                            <div>{{taskItem.shell_opction}}</div>\n                        </div>\n                    </el-col>\n                </el-row>\n                <el-row>\n                    <el-col :span=\"12\">\n                        <div class=\"com_model_block\" v-if=\"taskItem.type==2?true:false\">\n                            <div class=\"left\">shell脚本写入方式：</div>\n                            <div>{{taskItem.shell_write_type==1?'新建文件并上传方式':''}}{{taskItem.shell_write_type==2?'shell窗口命令行创建方式':''}}</div>\n                        </div>\n                    </el-col>\n                </el-row>\n                <div class=\"com_model_block\">\n                    <div class=\"left\">任务脚本内容：</div>\n                    <div id=\"editor\" class=\"comm_build_textarea\"></div>\n                </div>\n                <div class=\"ml10 light tc\" v-if=\"taskItem.type==2?true:false\">\n                    <i class=\"el-icon-warning fs-16 mr5 primary\"></i>\n                    请确保已生成构建配置，若未生成构建配置，则先执行生成配置按钮，再执行任务。\n                </div>\n                <div class=\"btns\">\n                    <el-button type=\"primary\" @click=\"deployApplications\">执行任务</el-button>\n                    <el-button type=\"danger\" v-if=\"taskItem.type==2?true:false\" @click=\"generateBuildConfig\" plain>生成配置</el-button>\n                    <el-button type=\"success\" @click=\"updageAppConfigs\" plain>修改配置</el-button>\n                </div>\n            </div>\n        </el-dialog>\n\n        <!-- xtrem弹出框 -->\n        <div class=\"comm_shell_model\" v-show=\"dialogBuildVisible\">\n            <div class=\"mask\" @click=\"dialogBuildVisible=false\"></div>\n            <div class=\"comm_shell_model_content\">\n                <span class=\"el-icon-close close\" @click=\"dialogBuildVisible=false\"></span>\n                <span :class=\"{'el-icon-menu':model_icon===1,'el-icon-s-unfold':model_icon===2}\" class=\"close menu\"\n                    @click=\"handleShellModel(3)\"></span>\n                <span class=\"el-icon-plus close plus\" @click=\"handleShellModel(1)\"></span>\n                <span class=\"el-icon-minus close minus\" @click=\"handleShellModel(2)\"></span>\n                <div class=\"comm_shell_navs\">\n                    <div class=\"item\" v-for=\"(item,index) in resultList\" :key=\"index\"\n                        :class=\"{'active':shell_model_item===index}\" @click=\"shell_model_item=index\">\n                        {{item.name}}\n                    </div>\n                </div>\n                <div class=\"comm_content_body\">\n                    <div class=\"com_content_list\" v-for=\"(item,index) in assetslist\" :key=\"index\">\n                        <div class=\"mt20 terminal\" :style=\"{zIndex:shell_model_item==index?10:1}\" :id=\"'terminal'+index\"></div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n    </div>\n    <script>var require = { paths: { 'vs': 'https://cdn.bootcss.com/monaco-editor/0.16.2/min/vs' } };</script>\n    <script src=\"//cdn.bootcss.com/monaco-editor/0.16.2/min/vs/loader.js\"></script>\n</div>\n<script>\n    let editor = null;\n    let xteamlist = null;\n    let socket = null;\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                assetslist: [],\n                multipleSelection: [],\n                sshkey: {\n                    dialogSshKeyVisible: false,\n                    sshKey: '',\n                    email: '',\n                    assetsItem: {},\n                },\n                tasklist:[],\n                dialogTableVisible:false,\n                taskItem: {},\n                type:'',\n                resultList:[],\n                dialogBuildVisible:false,\n                shell_model_item: 0,\n                model_icon: 1,\n                is_build:false,\n            }\n        },\n        mounted() {\n            this.getDatas();\n            this.getCommtaskList();\n        },\n        methods: {\n            getCommtaskList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/commtask/list`,\n                    success: data => {\n                        this.tasklist = data.data || [];\n                    }\n                })\n            },\n            getDatas(){\n                const asslist = util.getStorage('session','assets_list_configs');\n                this.assetslist = asslist ? JSON.parse(asslist) : [];\n                setTimeout(() => { this.assetslist.forEach(item => { this.$refs.multipleTable.toggleRowSelection(item, true); }) })\n            },\n            handleSelectionChange(val) {\n                this.multipleSelection = val;\n            },\n            showSshKey(item) {\n                if (this.sshkey.assetsItem._id !== item._id) this.sshkey.sshKey = '';\n                this.sshkey.assetsItem = item;\n                this.sshkey.dialogSshKeyVisible = true;\n            },\n            getSshKey(type) {\n                util.ajax({\n                    url: `${config.baseApi}api/v1/util/getAssetSshKey`,\n                    data: {\n                        type: type,\n                        email: this.sshkey.email,\n                        host: this.sshkey.assetsItem.outer_ip,\n                        port: this.sshkey.assetsItem.port,\n                        username: this.sshkey.assetsItem.user,\n                        password: this.sshkey.assetsItem.password,\n                    },\n                    success: data => {\n                        this.sshkey.sshKey = data.data;\n                    }\n                })\n            },\n            handleTask(assetsItem,commItem){\n                util.ajax({\n                    url: `${config.baseApi}api/v1/util/handleShellTasks`,\n                    data: {\n                        shell_type: commItem.type,\n                        shell_body: commItem.shell_body,\n                        shell_opction: commItem.shell_opction,\n                        shell_path: commItem.shell_path,\n                        host: assetsItem.outer_ip,\n                        port: assetsItem.port,\n                        username: assetsItem.user,\n                        password: assetsItem.password,\n                    },\n                    success: data => {\n                        popup.miss({ title: data.data.result })\n                    }\n                })\n            },\n            addNewCommTask(){\n                location.href=\"/commtask?model=1\"\n            },\n            showTaskOpctions(type, taskItem, assetsItem) {\n                if(type === 1){\n                    if(!this.multipleSelection.length){\n                        popup.alert({title:'请选择需要执行任务的资产列表'})\n                        return\n                    } else {\n                        this.resultList = this.multipleSelection;\n                    }\n                }else if(type === 2){\n                    this.resultList = [assetsItem]\n                }\n                this.is_build = false;\n                this.type = type;\n                this.taskItem = taskItem;\n                editor ? editor.setValue(taskItem.shell_body) : setTimeout(() => { this.runMonaco('editor', taskItem.shell_body); });\n                this.dialogTableVisible = true;\n            },\n            runMonaco(id, data) {\n                //载入Monaco\n                const _this = this;\n                require(['vs/editor/editor.main'], function () {\n                    //得到支持的语言\n                    var modesIds = monaco.languages.getLanguages().map(function (lang) { return lang.id }).sort();\n                    //创建编辑器\n                    editor = monaco.editor.create(document.getElementById(id), {\n                        //内容\n                        value: data,\n                        //语言\n                        language: 'shell',\n                        scrollBeyondLastLine: false,\n                        automaticLayout: true,\n                        //主题，三款：vs、vs-dark、hc-black\n                        theme: 'vs-dark',\n                        readOnly:true,\n                        //代码略缩图\n                        minimap: {\n                            enabled: true\n                        }\n                    });\n                });\n            },\n            generateBuildConfig() {\n                util.ajax({\n                    url: `${config.baseApi}api/v1/build/generateBuildConfig`,\n                    data: {\n                        taskItem: this.taskItem,\n                        assetsList: this.resultList,\n                    },\n                    success: data => {\n                        popup.miss({ title: '操作成功!' })\n                    }\n                })\n            },\n            updageAppConfigs(){\n                location.href=\"/commtask?taskId=\" + this.taskItem._id\n            },\n            deployApplications(type){\n                if(this.taskItem.type == 1){\n                    this.handleTask(this.resultList[0],this.taskItem);\n                    return;\n                }\n\n                let startType = '';\n\n                if (!xteamlist && !this.is_build) {\n                    // 第一次点击发布\n                    startType = 'new';\n                } else if (this.is_build) {\n                    // 第二次点击发布\n                    startType = 'agein';\n                } else if (!this.is_build && xteamlist) {\n                    // 切换了发布方式\n                    startType = 'switch';\n                }\n\n                // 弹出框\n                this.dialogBuildVisible = true;\n                this.is_build = true;\n\n                setTimeout(() => {\n                    // 开始\n                    const result = util.startSocketXteam({\n                        taskItem: this.taskItem,\n                        buildType: 'buildtasks',\n                        assetsList: this.resultList,\n                        startType,\n                    });\n                    socket = result.socket;\n                    xteamlist = result.xteamList;\n                })\n            },\n            handleShellModel(type) {\n                if (type === 3) {\n                    if (this.model_icon === 1) {\n                        this.model_icon = 2;\n                        type = 3;\n                    } else {\n                        this.model_icon = 1;\n                        type = 4;\n                    }\n                }\n                util.setSocketXteam(type, xteamlist, socket);\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/build.html",
    "content": "<style scoped>\n    .com_top .inp {\n        width: 180px;\n    }\n\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n\n    .model_assets_list .btns {\n        border-top: solid 1px #eee;\n        padding: 35px 0 0;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n    }\n\n    .model_assets_list .ml {\n        margin-left: 20px;\n    }\n\n    .model_assets_list .common_pages {\n        justify-content: flex-start;\n    }\n\n    .assets_item_list:nth-child(odd) {\n        border-bottom: solid 1px #eee;\n        padding-bottom: 5px;\n        margin-bottom: 5px;\n    }\n\n    .assets_item_list.first {\n        border-bottom: none;\n        padding-bottom: 0px;\n        margin-bottom: 0px;\n    }\n\n    .assets_item_item {\n        font-size: 12px;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n\n    .assets_item_item .as_1 {\n        display: inline-block;\n        width: 250px;\n    }\n\n    .assets_item_item .as_2 {\n        display: inline-block;\n        width: 150px;\n    }\n\n    .model_assets_list .b_t {\n        padding-top: 15px;\n        border-top: solid 1px #eee;\n    }\n    .select_app{\n        font-size:22px;\n        text-align:center;\n        margin:50px auto;\n        text-align:center;\n        color:#999;\n        font-weight:300;\n    }\n    .el-tabs-content{\n        background:#fff;\n        padding:15px;\n        box-shadow: 5px 5px 10px #eee;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">应用构建</div>\n        </el-col>\n    </el-row>\n    <div class=\"com_top\">\n        <el-select v-model=\"team_code\" class=\"inp\" @change=\"getList\" placeholder=\"请选择所属团队\">\n            <el-option v-for=\"item in teamlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-input v-model=\"app_name\" class=\"inp ml20\" placeholder=\"按应用名称查询\"></el-input>\n        <el-button @click=\"getList\" type=\"primary\">搜索</el-button>\n    </div>\n    <el-tabs class=\"el-tabs-content\" v-model=\"activeName\" @tab-click=\"handleClick\">\n        <el-tab-pane v-for=\"(item,index) in environlist\" :key=\"index\" :label=\"item.label\" :name=\"item._id\">\n            <!-- table -->\n            <div class=\"com_table_block mt20\" v-if=\"datalist.length\">\n                <div class=\"table_header\">\n                    <div class=\"l\">应用构建</div>\n                </div>\n                <div class=\"table_body\">\n                    <el-table :data=\"datalist\" style=\"width: 100%\">\n                        <el-table-column prop=\"name\" label=\"应用名称\">\n                        </el-table-column>\n                        <el-table-column prop=\"code\" label=\"应用编码\">\n                        </el-table-column>\n                        <el-table-column label=\"所属团队\">\n                            <template slot-scope=\"scope\">\n                                <div>{{scope.row.teamlist[0]?scope.row.teamlist[0].name:''}}</div>\n                            </template>\n                        </el-table-column>\n                        <el-table-column label=\"所属环境\">\n                            <template slot-scope=\"scope\">\n                                <div class=\"success\">{{scope.row.environlist[0]?scope.row.environlist[0].name:''}}</div>\n                            </template>\n                        </el-table-column>\n                        <el-table-column label=\"部署网络方式\">\n                            <template slot-scope=\"scope\">\n                                <div>{{scope.row.net_type == 1?'外网IP':'内网IP'}}</div>\n                            </template>\n                        </el-table-column>\n                        <el-table-column label=\"操作\" fixed=\"right\" width=\"220\">\n                            <template slot-scope=\"scope\">\n                                <div class=\"mb10\">\n                                    <el-button @click=\"buildProcess(scope.row)\" type=\"primary\" size=\"small\">应用部署</el-button>\n                                    <el-button @click=\"goToReduction(scope.row)\" type=\"success\" size=\"small\">应用还原</el-button>\n                                </div>\n                                <div>\n                                    <el-button @click=\"applicationConsole(scope.row)\" type=\"primary\" size=\"small\">控制台</el-button>\n                                    <el-button @click=\"setAppConfig(scope.row)\" type=\"warning\" size=\"small\">构建配置</el-button>\n                                </div>\n                            </template>\n                        </el-table-column>\n                    </el-table>\n                </div>\n                <div class=\"common_pages\">\n                    <el-pagination background @current-change=\"handleCurrentChange\" :current-page.sync=\"currentPage\"\n                        :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\" :total=\"totalCount\">\n                    </el-pagination>\n                </div>\n            </div>\n            <!-- empty -->\n            <div class=\"select_app\" v-if=\"!datalist.length\">\n                <div>暂无需要部署的应用,请新建环境和应用!</div>\n            </div>\n        </el-tab-pane>\n    </el-tabs>\n</div>\n<script>\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                team_code: '',\n                environ_code: '',\n                net_type: '',\n                app_name:'',\n                teamlist: [],\n                environlist: [],\n                datalist: [],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                activeName: '',\n            }\n        },\n        mounted() {\n            this.getEnvironList();\n            this.getTeamList();\n        },\n        methods: {\n            handleClick(tab, event) {\n                this.environ_code = this.environlist[tab.index].code;\n                this.getList();\n            },\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/list`,\n                    data: {\n                        team_code: this.team_code,\n                        environ_code: this.environ_code,\n                        net_type: this.net_type,\n                        app_name:this.app_name,\n                        status: 1,\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                    },\n                    success: data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            getTeamList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/team/list`,\n                    data: {\n                        pageNo: 1,\n                        pageSize: 100,\n                        status: 1,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.teamlist = options;\n                    }\n                })\n            },\n            getEnvironList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/environment/list`,\n                    data: {\n                        pageNo: 1,\n                        pageSize: 100,\n                    },\n                    success: data => {\n                        if(!data.data.datalist || !data.data.datalist.length) return;\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                _id: item._id,\n                                code: item.code,\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.environlist = options;\n                        this.activeName = datas[0]['_id'];\n                        this.environ_code = datas[0]['code'];\n                        this.getList();\n                    }\n                })\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            buildProcess(item){\n                location.href = \"/buildprocess?id=\" + item._id;\n            },\n            applicationConsole(item) {\n                let ids = '';\n                item.assets_list && item.assets_list.forEach(item => {\n                    ids = ids + item._id + ','\n                })\n                ids = ids ? ids.slice(0, -1) : '';\n                window.open(\"/console?ids=\" + ids);\n            },\n            setAppConfig(item) {\n                location.href = \"/appconfig?id=\" + item._id;\n            },\n            goToReduction(item){\n                location.href = \"/reduction?id=\" + item._id;\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/buildprocess.html",
    "content": "<style scoped>\n    .config_main {\n        padding: 20px;\n        background: #fff;\n        box-shadow: 5px 5px 10px #eee;\n    }\n    .config_main .row-col{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n    .config_main .block {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        flex-wrap: wrap;\n        font-size:14px;\n    }\n\n    .config_main .block .left {\n        width: 150px;\n        color: #999;\n        font-size:12px;\n    }\n\n    .config_main .block .right {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n\n    .comm_build_textarea{\n        width:950px;\n        height:300px;\n    }\n    .btns{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n        padding-top:30px;\n    }\n    .item_task{\n        width:1200px;\n    }\n    .item_task .title{\n        font-size:25px;\n        font-weight: 300;\n    }\n    .tab-btns{\n        display: flex;\n        flex-direction: row;\n        flex-wrap: wrap;\n    }\n    .tab-btns .btn-item{\n        margin-right:10px;\n    }\n    .build_product{\n        \n    }\n    .build_product .el-dialog__body{\n        padding-top:5px;\n    }\n    .build_content{\n        display: flex;\n        flex-direction: column;\n    }\n    .build_content .top{\n    }\n    .build_content .top .table .cell{\n        white-space: nowrap;\n    }\n    .build_content .top .table td, .el-table th {\n        padding: 5px 0;\n    }\n    .build_content .bottom{\n        width:calc(100%);\n        height:calc(100%);\n    }\n    .build_content .bottom .shell-terminal{\n        width:calc(100%);\n        height:650px;\n        background:#000;\n        position: relative;\n    }\n    .build_content .bottom .shell-terminal .terminal{\n        position: absolute;\n        left:10px;\n        top:10px;\n        width:calc(100% - 40px);\n        height:calc(100% - 40px);\n        z-index:1;\n    }\n   \n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">开始构建</div>\n        </el-col>\n    </el-row>\n    <div class=\"config_main mt30\">\n        <div class=\"block\">\n            <div class=\"bottom\" style=\"width:100%;\">\n                <div class=\"top mb20\">资产列表</div>\n                <div class=\"mb10\">\n                    <el-button @click=\"serverConfigs()\" type=\"primary\" icon=\"el-icon-tickets\" size=\"medium\">脚本任务</el-button>\n                    <el-button @click=\"applicationConsole()\" type=\"danger\" icon=\"el-icon-s-tools\" size=\"medium\">控制台</el-button>\n                    <span class=\"ml10 light\"><i class=\"el-icon-warning fs-16 mr5 primary\"></i>默认执行所有资产项操作，若有选择则执行选中资产项的相关操作(例如：部署、生成构建配置等操作)。\n                    </span>\n                </div>\n                <el-table \n                    :data=\"assetslist\" \n                    ref=\"assetsTable\" \n                    border \n                    @selection-change=\"handleSelectionChange\" \n                    style=\"width:auto;font-size:13px;\">\n                    <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n                    <el-table-column prop=\"name\" label=\"资产名称\" width=\"200\"></el-table-column>\n                    <el-table-column prop=\"code\" label=\"资产编码\" width=\"200\"></el-table-column>\n                    <el-table-column prop=\"outer_ip\" label=\"外网IP\" width=\"150\"></el-table-column>\n                    <el-table-column prop=\"lan_ip\" label=\"内网IP\" width=\"150\"></el-table-column>\n                    <el-table-column prop=\"user\" label=\"用户名\" width=\"80\"></el-table-column>\n                    <el-table-column label=\"操作\">\n                        <template slot-scope=\"scope\">\n                            <div class=\"tab-btns\">\n                                <el-button \n                                    class=\"btn-item\" \n                                    @click=\"serverConfigs(scope.row)\"\n                                    size=\"small\"\n                                    type=\"primary\" size=\"mini\">脚本任务</el-button>\n                                <div v-for=\"(item,index) in tasklist\" :key=\"index\">\n                                    <el-button \n                                        class=\"btn-item\" \n                                        @click=\"deployApplications(item, 3, scope.row)\" \n                                        :type=\"item.btntype\" \n                                        size=\"small\"\n                                        size=\"mini\">{{item.task_name}}</el-button>\n                                </div>\n                            </div>\n                        </template>\n                    </el-table-column>\n                </el-table>\n            </div>\n        </div>\n    </div>\n    <!-- main -->\n    <div class=\"config_main mt30\" v-for=\"(item,index) in tasklist\" :key=\"index\">\n        <div class=\"item_task\">\n            <div class=\"title\">{{item.task_name}}</div>\n            <el-row class=\"mt30\">\n                <el-col :span=\"12\">\n                    <div class=\"block\">\n                        <div class=\"left\">任务类型</div>\n                        <div class=\"right\">\n                            {{item.task_type=='git'?'git hooks任务':''}}\n                            {{item.task_type=='shell'?'shell脚本任务':''}}\n                            {{item.task_type=='command'?'命令行运行任务':''}}\n                        </div>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\" v-if=\"item.task_type!='command'?true:false\">\n                    <div class=\"block\">\n                        <div class=\"left\">git部署方式</div>\n                        <div class=\"right\">{{item.task_type=='git'?'https方式':''}}{{item.task_type=='ssh'?'ssh方式':''}}</div>\n                    </div>\n                </el-col>\n            </el-row>\n            <div v-if=\"item.task_type!='command'?true:false\">\n                <el-row class=\"mt20\">\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">shell脚本路径</div>\n                            <div class=\"right\">{{item.shell_path}}</div>\n                        </div>\n                    </el-col>\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">shell脚本参数</div>\n                            <div class=\"right\">{{item.shell_opction}}</div>\n                        </div>\n                    </el-col>\n                </el-row>\n                <el-row class=\"mt20\">\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">shell脚本运行命令</div>\n                            <div class=\"right\">{{'sh ' + item.shell_path +' '+ (item.shell_opction?item.shell_opction:'')}}</div>\n                        </div>\n                    </el-col>\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">shell脚本上传方式</div>\n                            <div class=\"right\">\n                                {{item.shell_write_type == 1 ? '新建文件并上传方式' :''}}\n                                {{item.shell_write_type == 2 ? 'shell窗口命令行创建方式' :''}}\n                            </div>\n                        </div>\n                    </el-col>\n                </el-row>\n                <el-row class=\"mt20\">\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">发布前是否先做备份</div>\n                            <div class=\"right\">\n                                <el-switch v-model=\"item.is_backups\"></el-switch>\n                            </div>\n                        </div>\n                    </el-col>\n                </el-row>\n                <el-row class=\"mt20\">\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">需要备份的资源</div>\n                            <div class=\"right\">{{item.project_path}}</div>\n                        </div>\n                    </el-col>\n                    <el-col :span=\"12\">\n                        <div class=\"block\">\n                            <div class=\"left\">备份文件的存放位置</div>\n                            <div class=\"right\">{{item.backups_path}}</div>\n                        </div>\n                    </el-col>\n                </el-row>\n            </div>\n            <el-row class=\"mt20\" v-if=\"item.remarks?true:false\">\n                <el-col :span=\"24\">\n                    <div class=\"block\">\n                        <div class=\"left\">备注</div>\n                        <div class=\"right\">{{item.remarks}}</div>\n                    </div>\n                </el-col>\n            </el-row>\n            <div class=\"block mt30\">\n                <div class=\"left\">shell脚本内容</div>\n                <div class=\"right\">\n                    <div :id=\"'editor_'+index\" class=\"comm_build_textarea\" :style=\"{height:item.task_type=='command'?'40px':'300px'}\"></div>\n                </div>\n            </div>\n            <div class=\"ml10 mt30 light tc\" v-if=\"item.task_type!='command'?true:false\">\n                <i class=\"el-icon-warning fs-16 mr5 primary\"></i>\n                请确保已生成构建配置，若未生成构建配置，则先执行生成构建配置按钮，再执行部署任务。\n            </div>\n            <div class=\"btns mb20\">\n                <el-button \n                    type=\"primary\" \n                    @click=\"deployApplications(item,1)\">\n                    {{item.task_type=='command'?'开始运行':'开始部署'}}\n                </el-button>\n                <el-button \n                    v-if=\"item.task_type!='command'?true:false\" \n                    type=\"success\" \n                    @click=\"backupApplications(item)\" plain>\n                    服务备份\n                </el-button>\n                <el-button \n                    v-if=\"item.task_type!='command'?true:false\" \n                    type=\"danger\" @click=\"generateBuildConfig(item)\" plain>\n                    生成构建配置\n                </el-button>\n                <el-button \n                    type=\"primary\" \n                    @click=\"updageAppConfigs(index)\" plain>\n                    修改构建配置\n                </el-button>\n                <el-button type=\"default\" @click=\"goBack\">返回</el-button>\n            </div>\n        </div>\n    </div>\n\n    <!-- 开始构建 -->\n    <el-dialog class=\"build_product\" :title=\"buildLogs.task_name\" :fullscreen=\"true\" :close-on-click-modal=\"false\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"build_content\">\n            <div class=\"top\" v-if=\"taskType!=='command'\">\n                <el-table class=\"table\" border max-height=\"250\" :data=\"buildLogs.content\">\n                    <el-table-column property=\"assets_name\" label=\"资产名称\" width=\"200\"></el-table-column>\n                    <el-table-column property=\"outer_ip\" label=\"外网IP\" width=\"180\"></el-table-column>\n                    <el-table-column property=\"lan_ip\" label=\"内网IP\" width=\"150\"></el-table-column>\n                    <el-table-column label=\"构建状态\" width=\"200\">\n                        <template slot-scope=\"scope\">\n                            <div v-if=\"scope.row.code==-100\" class=\"red\"><i class=\"el-icon-loading\"></i> 应用构建中...</div>\n                            <div v-if=\"scope.row.code!=-100\" :class=\"scope.row.code==0?'success':'red'\">\n                                {{scope.row.code==0?'构建成功':'构建失败'}}\n                            </div>\n                        </template>\n                    </el-table-column>\n                    <el-table-column property=\"shell\" label=\"执行shell内容\"></el-table-column>\n                </el-table>\n            </div>\n            <div class=\"bottom mt30\">\n                <div class=\"navs\">\n                    <el-tabs v-model=\"activeName\">\n                        <el-tab-pane v-for=\"(item,index) in assetslist\" :label=\"item.name+'-'+item.outer_ip\" :name=\"index+''\"></el-tab-pane>\n                    </el-tabs>\n                </div>\n                <div class=\"shell-terminal\">\n                    <div class=\"terminal\" v-for=\"(item,index) in assetslist\" :key=\"index\" :style=\"{zIndex:activeName==index?10:1}\"\n                        :id=\"'terminal'+index\"></div>\n                </div>\n            </div>\n        </div>\n    </el-dialog>\n\n    <!-- 备份|构建日志 -->\n    <el-dialog width=\"80%\" :title=\"logsData.task_name\" :visible.sync=\"dialogModelVisible\">\n        <!-- <div></div> -->\n        <el-table :data=\"logsData.content\">\n            <el-table-column property=\"assets_name\" label=\"资产名称\" width=\"200\"></el-table-column>\n            <el-table-column property=\"outer_ip\" label=\"外网IP\" width=\"150\"></el-table-column>\n            <el-table-column property=\"lan_ip\" label=\"内网IP\" width=\"100\"></el-table-column>\n            <el-table-column label=\"执行状态\" width=\"100\">\n                <template slot-scope=\"scope\">\n                    <div v-if=\"scope.row.code==-100\"><i class=\"el-icon-loading\"></i> 构建中...</div>\n                    <div \n                        v-if=\"scope.row.code!=-100\"\n                        :class=\"scope.row.code==0?'success':'red'\">\n                        {{scope.row.code==0?'执行成功':'执行失败'}}\n                    </div>\n                </template>\n            </el-table-column>\n            <el-table-column v-if=\"logsData.type===2 || logsData.type===1\" property=\"shell\" label=\"执行shell内容\"></el-table-column>\n            <el-table-column v-if=\"logsData.type!==1\" property=\"data\" label=\"执行日志\"></el-table-column>\n        </el-table>\n    </el-dialog>\n\n</div>\n<script>var require = { paths: { 'vs': 'https://cdn.bootcss.com/monaco-editor/0.16.2/min/vs' } };</script>\n<script src=\"//cdn.bootcss.com/monaco-editor/0.16.2/min/vs/loader.js\"></script>\n<script>\n    let xteamlist = null;\n    let socket = null;\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                id: util.getQueryString('id'),\n                assetslist: [],\n                dialogTableVisible: false,\n                tasklist: [],\n                multipleSelection:[],\n                type:'',\n                shell_model_item: 0,\n                model_icon:1,\n                taskName:'',\n                dialogModelVisible:false,\n                logsData: { content:[] },\n                configLogs:{},\n                backupLogs:{},\n                buildLogs:{},\n                activeName: '0',\n                taskType:'git',\n            }\n        },\n        mounted() {\n            this.buildListEnd = [];\n            this.itemdetail();\n            this.fullscreen();\n        },\n        methods: {\n            runMonaco(id, data){\n                //载入Monaco\n                let editor = null;\n                const _this = this;\n                const moId = `editor_${id}`;\n                require(['vs/editor/editor.main'], function () {\n                    //得到支持的语言\n                    var modesIds = monaco.languages.getLanguages().map(function (lang) { return lang.id }).sort();\n                    //创建编辑器\n                    editor = monaco.editor.create(document.getElementById(moId), {\n                        //内容\n                        value: data,\n                        //语言\n                        language: 'shell',\n                        scrollBeyondLastLine: false,\n                        readOnly: true,\n                        automaticLayout: true,\n                        //主题，三款：vs、vs-dark、hc-black\n                        theme: 'vs-dark',\n                        //代码略缩图\n                        minimap: {\n                            enabled: false\n                        }\n                    });\n                });\n            },\n            itemdetail() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/itemdetail`,\n                    data: {\n                        id: this.id,\n                    },\n                    success: data => {\n                        this.assetslist = data.data.assets_list || [];\n                        const tasklist = data.data.task_list || [];\n                        if (tasklist && tasklist.length) {\n                            tasklist.forEach((item, index) => {\n                                item.btntype = index % 3 == 0 ? 'danger' : '' + index % 2 == 0 ? 'primary' : '' + index % 1 == 0 ? 'success' : '';\n                                item.is_backups = item.is_backups == 1 ? true : false;\n                                this.runMonaco(index, item.shell_body)\n                            })\n                        }\n                        this.tasklist = tasklist;\n                        const assetsId = util.getQueryString('assetsId');\n                        setTimeout(() => { \n                            if(assetsId){\n                                this.assetslist.forEach(item => {if(assetsId == item._id) this.$refs.assetsTable.toggleRowSelection(item, true)})\n                            }else{\n                                this.assetslist.forEach(item => {this.$refs.assetsTable.toggleRowSelection(item, true);})\n                            }\n                        })\n                    }\n                })\n            },\n            goBack() {\n                location.href = \"/build\"\n            },\n            deployApplications(taskItem, type, assetsItem) {\n                if (!taskItem.shell_path && taskItem.task_type != 'command') {\n                    popup.alert({ title: 'shell脚本路径不能为空!' })\n                    return;\n                }\n                if (!taskItem.shell_body) {\n                    popup.alert({ title: 'shell脚本内容不能为空!' })\n                    return;\n                }\n\n                if(!this.multipleSelection.length){\n                    popup.alert({title:'请先勾选需要部署的服务器!'});\n                    return;\n                }\n\n                let startType = '';\n\n                if(!xteamlist) {\n                    // 第一次点击发布\n                    startType = 'new';\n                } else if(type === this.type && this.taskName === taskItem.task_name && xteamlist){\n                    // 第二次点击发布\n                    startType = 'agein';\n                } else if(type !== this.type && this.taskName !== taskItem.task_name && xteamlist) {\n                    // 切换了发布方式\n                    startType = 'switch';\n                }\n\n                this.type = type;\n                this.taskName = taskItem.task_name;\n                this.taskType = taskItem.task_type;\n                // 弹出框\n                this.dialogTableVisible = true;\n                if(!this.buildLogs.task_name){\n                    const assetslist = [];\n                    this.assetslist.forEach(item => {\n                        assetslist.push({\n                            assets_name: item.name,\n                            code: -100,\n                            data: '',\n                            lan_ip: item.lan_ip,\n                            outer_ip: item.outer_ip,\n                            shell: taskItem.shell_path ? 'sh ' + taskItem.shell_path : '',\n                        })\n                    })\n                    this.buildLogs = {\n                        task_name: taskItem.task_name + '任务-构建应用服务',\n                        type: 1,\n                        content: assetslist,\n                    }\n                }\n\n                setTimeout(()=>{\n                    let assetsList = this.multipleSelection;\n                    if(type === 3) assetsList = [assetsItem];\n\n                    // 开始\n                    const result = util.startSocketXteam({\n                        id: this.id,\n                        buildType: 'buildprocess',\n                        taskItem,\n                        assetsList,\n                        startType,\n                        callback: this.callback,\n                    });\n                    socket = result.socket;\n                    xteamlist = result.xteamList;\n                })\n            },\n            callback(data = {}){\n                this.buildListEnd.push(1);\n                if(!this.buildLogs.date) this.buildLogs.date = data.date || new Date();\n                this.buildLogs.content[data.index].code = data.isSuccess ? 0 : 1;\n\n                // 构建完成\n                if(this.buildListEnd.length === this.buildLogs.content.length){\n                    this.buildApplicationed();\n                }\n            },\n            buildApplicationed(){\n                this.buildLogs.application_id = this.id;\n                util.ajax({\n                    nohideloading: true,\n                    url: `${config.baseApi}api/v1/build/buildApplicationed`,\n                    data: this.buildLogs,\n                    success: data => {}\n                })\n            },\n            backupApplications(item = {}){\n                if(!item.project_path){\n                    popup.alert({title:'需要备份的资源|目录不能为空!'})\n                    return;\n                }\n                if (!item.backups_path) {\n                    popup.alert({ title: '备份文件的存放位置不能为空!' })\n                    return;\n                }\n\n                this.dialogModelVisible = true;\n                if (this.backupLogs.isLoad) {\n                    this.logsData = this.backupLogs;\n                    return;\n                }\n\n                const assetslist = [];\n                this.assetslist.forEach(item => {\n                    assetslist.push({\n                        assets_name: item.name,\n                        code: -100,\n                        data: '',\n                        lan_ip: item.lan_ip,\n                        outer_ip: item.outer_ip,\n                    })\n                })\n                this.backupLogs = this.logsData = {\n                    task_name: item.task_name + '任务-服务备份',\n                    type: 3,\n                    content: assetslist,\n                }\n                let userMsg = util.getStorage('local', 'userMsg');\n                let user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n                util.ajax({\n                    nohideloading: true,\n                    notimeout: true,\n                    url: `${config.baseApi}api/v1/build/backupApplications`,\n                    data: {\n                        id: this.id,\n                        taskItem: item,\n                        user_name,\n                        assetsList: this.assetslist,\n                    },\n                    success: data => {\n                        this.backupLogs = this.logsData = data.data || {};\n                    }\n                })\n            },\n            handleShellModel(type){\n                if(type === 3){\n                    if(this.model_icon === 1){\n                        this.model_icon = 2;\n                        type = 3;\n                    }else{\n                        this.model_icon = 1;\n                        type = 4;\n                    }\n                }\n                util.setSocketXteam(type, xteamlist, socket);\n            },\n            generateBuildConfig(item){\n                if (!item.shell_path) {\n                    popup.alert({ title: 'shell脚本路径不能为空!' })\n                    return;\n                }\n                if (!item.shell_body) {\n                    popup.alert({ title: 'shell脚本内容不能为空!' })\n                    return;\n                }\n\n                this.dialogModelVisible = true;\n                if(this.configLogs.isLoad){\n                    this.logsData = this.configLogs;\n                    return;\n                }\n\n                const assetslist = [];\n                this.assetslist.forEach(item=>{\n                    assetslist.push({\n                        assets_name: item.name,\n                        code: -100,\n                        data: '',\n                        lan_ip: item.lan_ip,\n                        outer_ip: item.outer_ip,\n                    })\n                })\n                this.configLogs = this.logsData = {\n                    task_name: item.task_name + '任务-生成构建配置',\n                    type: 3,\n                    content: assetslist,\n                }\n                let userMsg = util.getStorage('local', 'userMsg');\n                let user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n                util.ajax({\n                    nohideloading:true,\n                    notimeout: true,\n                    url: `${config.baseApi}api/v1/build/generateBuildConfig`,\n                    data: {\n                        id: this.id,\n                        taskItem: item,\n                        user_name,\n                        assetsList:this.assetslist,\n                    },\n                    success: data => {\n                        this.configLogs = this.logsData = data.data || {};\n                    }\n                })\n            },\n            handleSelectionChange(val) {\n                this.multipleSelection = val;\n            },\n            serverConfigs(item) {\n                if (!item && !this.multipleSelection.length) {\n                    popup.alert({ title: '请选择需要配置的资产!' })\n                    return;\n                }\n                const assestsList = item ? [item] : this.multipleSelection;\n                util.setStorage('session', 'assets_list_configs', JSON.stringify(assestsList));\n                location.href = \"/assetsconfig\";\n            },\n            updageAppConfigs(index){\n                location.href = \"/appconfig?id=\"+this.id+'&tab='+index;\n            },\n            applicationConsole(){\n                if (!this.multipleSelection.length) {\n                    popup.alert({ title: '请选择进入控制台的资产列表!' })\n                    return;\n                }\n                let ids = '';\n                this.multipleSelection && this.multipleSelection.forEach(item => {\n                    ids = ids + item._id + ','\n                })\n                ids = ids ? ids.slice(0, -1) : '';\n                window.open(\"/console?ids=\" + ids);\n            },\n            fullscreen() {\n                const _this = this;\n                $(document).keyup(function (event) {\n                    console.log(event.keyCode)\n                    if (event.keyCode === 112) {\n                        if (xteamlist && xteamlist[_this.activeName]) {\n                            xteamlist[_this.activeName].toggleFullScreen();\n                        }\n                    }\n                });\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/commtask.html",
    "content": "<style scoped>\n    .comm_build_textarea {\n        width: 840px;\n        height: 180px;\n    }\n    .com_model_main .com_model_block .left{\n        width:130px;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">资产脚本任务</div>\n        </el-col>\n    </el-row>\n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">脚本任务管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\" @click=\"handleCommTask(1)\">新建脚本任务</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\">\n                <el-table-column prop=\"name\" label=\"任务名称\">\n                </el-table-column>\n                <el-table-column label=\"任务类型\">\n                    <template slot-scope=\"scope\">\n                        <div>{{scope.row.type == 1?'窗口运行命令':'shell脚本命令'}}</div>\n                    </template>\n                </el-table-column>\n                <el-table-column prop=\"shell_path\" label=\"shell脚本地址\">\n                </el-table-column>\n                <el-table-column prop=\"shell_opction\" label=\"shell脚本参数\">\n                </el-table-column>\n                <el-table-column prop=\"user_name\" label=\"操作人\">\n                </el-table-column>\n                <el-table-column label=\"按钮样式\">\n                    <template slot-scope=\"scope\">\n                        <div><el-button :type=\"scope.row.btn_color\" :plain=\"scope.row.is_plain==1?true:false\" size=\"small\">按钮样式</el-button></div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"操作\">\n                    <template slot-scope=\"scope\">\n                        <div>\n                            <el-button @click=\"handleCommTask(2,scope.row)\" type=\"primary\" size=\"small\">查看|编辑</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" type=\"danger\" size=\"small\" plain>删除</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog width=\"1000px\" title=\"脚本任务编辑\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">任务名称<span class=\"red\">*</span></div>\n                        <el-input class=\"inp\" v-model=\"team.name\" placeholder=\"请输任务名称\"></el-input>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">任务类型<span class=\"red\">*</span></div>\n                        <el-select v-model=\"team.type\" class=\"inp\" placeholder=\"请选择\">\n                            <el-option v-for=\"item in options\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                            </el-option>\n                        </el-select>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\" v-if=\"team.type==2?true:false\">\n                        <div class=\"left\">shell脚本路径</div>\n                        <el-input class=\"inp\" v-model=\"team.shell_path\" placeholder=\"例如：/usr/src/sh/build.sh\"></el-input>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\" v-if=\"team.type==2?true:false\">\n                        <div class=\"left\">shell脚本参数</div>\n                        <el-input class=\"inp\" v-model=\"team.shell_opction\" placeholder=\"shell脚本参数请以空格隔开\"></el-input>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\" v-if=\"team.type==2?true:false\">\n                        <div class=\"left\">shell脚本写入方式</div>\n                        <el-select v-model=\"team.shell_write_type\" class=\"inp\" placeholder=\"请选择\">\n                            <el-option v-for=\"item in options2\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                            </el-option>\n                        </el-select>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">按钮颜色<span class=\"red\">*</span></div>\n                        <el-select v-model=\"team.btn_color\" class=\"inp\" placeholder=\"请选择\">\n                            <el-option v-for=\"item in options1\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                            </el-option>\n                        </el-select>\n                    </div>\n                </el-col>\n            </el-row>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">是否是plain样式<span class=\"red\">*</span></div>\n                        <el-radio v-model=\"team.is_plain\" :label=\"1\">是</el-radio>\n                        <el-radio v-model=\"team.is_plain\" :label=\"2\">否</el-radio>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">按钮预览</div>\n                        <el-button :type=\"team.btn_color\" :plain=\"team.is_plain==1?true:false\" size=\"mini\">按钮预览</el-button>\n                    </div>\n                </el-col>\n            </el-row>\n            <div class=\"com_model_block\">\n                <div class=\"left\">任务脚本内容<span class=\"red\">*</span></div>\n                <div id=\"editor\" class=\"comm_build_textarea\"></div>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitCommtask\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n</div>\n<script>var require = { paths: { 'vs': 'https://cdn.bootcss.com/monaco-editor/0.16.2/min/vs' } };</script>\n<script src=\"//cdn.bootcss.com/monaco-editor/0.16.2/min/vs/loader.js\"></script>\n<script>\n    let editor = null;\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                datalist:[],\n                dialogTableVisible: false,\n                options:[{\n                    value: 1,\n                    label: '窗口运行命令'\n                }, {\n                    value: 2,\n                    label: 'shell脚本命令'\n                }],\n                options1: [{\n                    value: 'default',\n                    label: 'default'\n                }, {\n                    value: 'primary',\n                    label: 'primary'\n                }, {\n                    value: 'success',\n                    label: 'success'\n                }, {\n                    value: 'info',\n                    label: 'info'\n                }, {\n                    value: 'warning',\n                    label: 'warning'\n                }, {\n                    value: 'danger',\n                    label: 'danger'\n                }],\n                options2: [{\n                    value: 1,\n                    label: '新建文件并上传方式'\n                }, {\n                    value: 2,\n                    label: 'shell窗口命令行创建方式'\n                }],\n                team: {},\n            }\n        },\n        mounted() {\n            this.getList();\n            if(util.getQueryString('model')) this.handleCommTask(1);\n        },\n        methods: {\n            getList(){\n                util.ajax({\n                    type:'get',\n                    url: `${config.baseApi}api/v1/commtask/list`,\n                    success:data=>{\n                        this.datalist = data.data || [];\n                        const taskId = util.getQueryString('taskId');\n                        this.datalist.forEach(item=>{\n                            if(item._id == taskId) this.handleCommTask(2, item);\n                        })\n                    }\n                })\n            },\n            handleCommTask(type, item){\n                if (item) item = JSON.parse(JSON.stringify(item));\n                if (item) {\n                    item.shell_write_type = item.shell_write_type?parseInt(item.shell_write_type):2;\n                    this.team = item;\n                    editor ? editor.setValue(item.shell_body) : setTimeout(() => { this.runMonaco('editor', item.shell_body); });\n                } else {\n                    this.team = {\n                        name: '',\n                        type: 1,\n                        shell_write_type:2,\n                        shell_body: '',\n                        btn_color: 'primary',\n                        is_plain: 2,\n                        shell_opction:'',\n                        shell_path:'',\n                        task_type:'taskshell',\n                    }\n                    editor ? editor.setValue('') : setTimeout(() => { this.runMonaco('editor', ''); });\n                }\n                this.team.handletype = type;\n                this.dialogTableVisible = true;\n            },\n            submitCommtask(){\n                if(!this.team.name){\n                    popup.alert({title:'请输入任务名称!'})\n                    return\n                }\n                const shell_body = editor.getValue()\n                if (!shell_body) {\n                    popup.alert({ title: '请输入任务脚本内容!' })\n                    return\n                }\n                this.team.shell_body = shell_body;\n                const userMsg = util.getStorage('local','userMsg');\n                this.team.user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n                util.ajax({\n                    url:`${config.baseApi}api/v1/commtask/handle`,\n                    data:this.team,\n                    success:data=>{\n                        this.getList();\n                        this.dialogTableVisible = false;\n                        popup.miss({title:'操作成功!'})\n                    }\n                })\n            },\n            hanleDelete(item) {\n                popup.confirm({\n                    title: `确定删除${item.name}邮件吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/commtask/delete`,\n                            data: {\n                                id: item._id,\n                            },\n                            success: data => {\n                                this.getList();\n                                popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            runMonaco(id, data) {\n                //载入Monaco\n                const _this = this;\n                require(['vs/editor/editor.main'], function () {\n                    //得到支持的语言\n                    var modesIds = monaco.languages.getLanguages().map(function (lang) { return lang.id }).sort();\n                    //创建编辑器\n                    editor = monaco.editor.create(document.getElementById(id), {\n                        //内容\n                        value: data,\n                        //语言\n                        language: 'shell',\n                        scrollBeyondLastLine: false,\n                        automaticLayout: true,\n                        //主题，三款：vs、vs-dark、hc-black\n                        theme: 'vs-dark',\n                        //代码略缩图\n                        minimap: {\n                            enabled: false\n                        }\n                    });\n                });\n            },\n            deleteItemTask(item, index) {\n                popup.confirm({\n                    title: `确定删除${item.task_name}任务吗？`, yes: () => {\n                        this.editableTabsValue = '0';\n                        this.tasklist.splice(index, 1);\n                    }\n                })\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/console.html",
    "content": "<style scoped>\n    body{background:#000;}\n    .header,.footer{display:none;}\n    #body_content{overflow:hidden;margin-left: 0px!important;padding-top: 0px;}\n    .side{display: none;}\n    .content{\n        padding:0px;\n        color:#fff;\n        width:calc(100%);\n        height:calc(100%);\n        background:#000;\n    }\n    .console-header{\n        width:100%;\n        height:40px;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        background:#5a5454;\n    }\n    .console-header .home{\n        width:60px;\n        height:40px;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n        cursor:pointer;\n        margin-right:50px;\n    }\n    .console-header .item{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        margin-right: 10px;\n    }\n    .console-header .item .inp1{\n        width:250px;\n    }\n    .el-input__inner {\n        background-color: #5a5454;\n        color: #dedfe2;\n        height: 30px;\n        line-height: 30px;\n        border: none;\n    }\n    .el-input__icon {\n        line-height: 30px;\n    }\n    .console-header-list .empty{\n        padding:15px 10px;\n        font-size:14px;\n    }\n    .console-header-list .child{\n        line-height:30px;\n        cursor: pointer;\n        padding-left:10px;\n    }\n    .console-header-list .child:hover{\n        color:#000;\n        background-color: #F5F7FA;\n    }\n    .console-header-list .select-all{\n        margin:10px 0 0 10px;\n    }\n    .console-header .line{\n        height:20px;\n        width:1px;\n        background:#ccc;\n        margin:0 25px;\n    }\n    .comsole-consten{\n        width:calc(100%);\n        height:calc(100% - 80px);\n        position: relative;\n        overflow:hidden;\n    }\n    .comsole-consten .navs{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        height:30px;\n        font-size:12px;\n        background:#3a3333;\n        color:#aaa;\n        padding:0 10px;\n        width:calc(100%);\n    }\n    .comsole-consten .navs .item{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        padding:0 10px;\n        height:100%;\n        cursor:pointer;\n    }\n    .comsole-consten .navs .item span{\n        display: block;\n        max-width:150px;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        margin-right:5px;\n    }\n    .comsole-consten .navs .active{\n        color:#fff;\n        border-bottom:solid 2px #409eff;\n    }\n    .comsole-consten .navs .item i{\n        cursor:pointer;\n        font-size:16px;\n    }\n    .xterm-list{\n        width:calc(100%);\n        height:calc(100%);\n        position: relative;\n    }\n    .xterm-list .xterm-item{\n        left:10px;\n        top:10px;\n        position: absolute;\n        width:calc(100% - 10px);\n        height:calc(100% - 50px);\n    }\n    .console-side .el-menu-item-group__title{\n        padding:0;\n    }\n    .console-side .el-submenu__title{\n        font-size:12px;\n        height:35px;\n        line-height:35px;\n    }\n    .console-side .el-submenu .el-menu-item{\n        font-size:12px;\n        height: 30px;\n        line-height: 30px;\n        padding-right:10px;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n    }\n    .console-footer{\n        height:40px;\n        background:#5a5454;\n        padding-left:100px;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n    }\n    .console-footer input{\n        width:800px;\n        height:30px;\n        padding-left:10px;\n        font-size:14px;\n        letter-spacing:1px;\n        border:none;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <div class=\"console-header\">\n        <div class=\"home\" @click=\"goHome\"><i class=\"el-icon-s-home fs-22\"></i></div>\n        <div class=\"line\"></div>\n        <div class=\"item\">\n            <!-- <span>通过团队筛选：</span> -->\n            <el-select v-model=\"team_name\" class=\"w-200\" placeholder=\"请选择团队\">\n                <el-option v-for=\"(val, name, index) in assets\" :key=\"name\" :label=\"name\" :value=\"name\" ></el-option>\n            </el-select>\n        </div>\n        <div class=\"item\">\n            <el-popover placement=\"bottom-start\" width=\"300\" trigger=\"hover\">\n                <div class=\"console-header-list\">\n                    <div v-if=\"assetslist.length?true:false\">\n                        <div class=\"child\" v-for=\"item in assetslist\" :key=\"item.code\" @click=\"addXtermForAssets(item)\">{{item.name}}</div>\n                        <el-button class=\"select-all\" type=\"primary\" size=\"small\" @click=\"addAllToXterm(1)\">全部选择</el-button>\n                    </div>\n                    <div v-if=\"!assetslist.length?true:false\" class=\"empty\">请先选择团队信息。</div>\n                </div>\n                <div class=\"select-btn\" slot=\"reference\">请选择需要打开的资产</div>\n            </el-popover>\n        </div>\n        <div class=\"line\"></div>\n        <div class=\"item\">\n            <!-- <span>通过应用筛选：</span> -->\n            <el-select v-model=\"app_name\" class=\"w-200\" placeholder=\"请选择应用\">\n                <el-option v-for=\"(val, name, index) in application\" :key=\"name\" :label=\"name\" :value=\"name\"></el-option>\n            </el-select>\n        </div>\n        <div class=\"item\">\n            <el-popover placement=\"bottom-start\" width=\"300\" trigger=\"hover\">\n                <div class=\"console-header-list\">\n                    <div v-if=\"applicationlist.length?true:false\">\n                        <div class=\"child\" v-for=\"item in applicationlist\" :key=\"item.code\" @click=\"addXtermForAssets(item)\">\n                            {{item.name}}</div>\n                        <el-button class=\"select-all\" type=\"primary\" size=\"small\" @click=\"addAllToXterm(2)\">全部选择</el-button>\n                    </div>\n                    <div v-if=\"!applicationlist.length?true:false\" class=\"empty\">请先选择应用信息。</div>\n                </div>\n                <div class=\"select-btn\" slot=\"reference\">请选择需要打开的资产</div>\n            </el-popover>\n        </div>\n        <div class=\"line\"></div>\n    </div>\n    <div class=\"comsole-consten\">\n        <div class=\"navs\">\n            <div \n                v-for=\"(item,index) in xtermList\" \n                :key=\"index\" \n                class=\"item\"\n                :style=\"{'max-width':(1/xtermList.length)*100+'%'}\"\n                @click=\"selectTab(item)\"\n                :data-i=\"navIndex+'--'+item.xtermId\"\n                :title=\"item.name+'-'+item.outer_ip+'-'+item.lan_ip\"\n                :class=\"{active:navIndex==item.xtermId?true:false}\">\n                <span>{{item.name+'-'+item.outer_ip}}</span>\n                <i @click.stop=\"closeXtermForAssets(index,item)\" class=\"el-icon-close\"></i>\n            </div>\n        </div>\n        <div class=\"xterm-list\">\n            <div \n                class=\"xterm-item\"\n                :style=\"{'z-index':navIndex==item.xtermId?20:1}\"\n                v-for=\"(item,index) in xtermList\" \n                :key=\"item.xtermId\" \n                :id=\"'console_terminal_'+item.xtermId\"></div>\n        </div>\n    </div>\n    <div class=\"console-footer\">\n        <span>全局运行指令：</span>\n        <input type=\"text\" v-model=\"instructions\" @keyup=\"onKeyUp\" @keyup.enter=\"onEnter\" placeholder=\"请输入\">\n    </div>\n</div>\n<script>\n    const xtermRunList = [];\n    let xtermId = -1;\n    const key_storage = util.getStorage('local', 'console_key_history');\n    const keyHistory = key_storage ? JSON.parse(key_storage).splice(-20) : [];\n    let hisBegin = keyHistory.length;\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                assets:{},\n                xtermList:[],\n                navIndex:0,\n                assetslist: [],\n                assets_name: '',\n                team_name:'',\n                instructions:'',\n                app_name: '',\n                application:{},\n                applicationlist:[],\n            }\n        },\n        watch:{\n            'team_name'(newVal,oldVal){\n                if(newVal !== oldVal){\n                    this.assetslist = this.assets[newVal];\n                }\n            },\n            'app_name'(newVal, oldVal) {\n                if (newVal !== oldVal) {\n                    this.applicationlist = this.application[newVal];\n                }\n            },\n        },\n        mounted() {\n            $('#body_content').css('height',$(window).height() +'px');\n            this.assetsList();\n            this.applicationList();\n            this.socket = util.socket();\n            this.fullscreen();\n        },\n        methods: {\n            assetsList(){\n                util.ajax({\n                    type:'get',\n                    url:`${config.baseApi}api/v1/assets/all`,\n                    success:data=>{\n                        const datas = data.data || [];\n                        const assets = {};\n                        const xtermList = [];\n                        let ids = util.getQueryString('ids');\n                        ids = ids ? ids.split(',') : [];\n                        let i = 0;\n                        datas.forEach((item,index) => {\n                            if(ids.includes(item._id)){\n                                item.xtermId = i;\n                                i++;\n                                xtermList.push(item);\n                            }\n                            if(!assets[item.teamlist[0].name]){\n                                assets[item.teamlist[0].name] = [ item ];\n                            } else {\n                                assets[item.teamlist[0].name].push(item);\n                            }\n                        })\n                        this.xtermList = xtermList;\n                        this.assets = assets;\n                        xtermId = this.xtermList.length-1;\n                        setTimeout(()=>{\n                            this.xtermList.forEach((item,index)=>{\n                                this.showConsole(index, item)\n                            })\n                        },200)\n                    }\n                })\n            },\n            applicationList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/all`,\n                    success: data => {\n                        const datas = data.data || [];\n                        const application = {};\n                        const xtermList = [];\n                        datas.forEach((item, index) => {\n                            if (!application[item.name]) {\n                                application[item.name] = item.assets_list;\n                            }\n                        })\n                        this.application = application;\n                    }\n                })\n            },\n            selectTab(item){\n                this.navIndex = item.xtermId;\n            },\n            addAllToXterm(type){\n                const assetslist = type === 1 ? this.assetslist : this.applicationlist;\n                assetslist.forEach( item => this.addXtermForAssets(item) )\n            },\n            addXtermForAssets(item){\n                item = JSON.parse(JSON.stringify(item))\n                xtermId++\n                this.navIndex = xtermId;\n                item.xtermId = xtermId;\n                this.xtermList.push(item);\n                setTimeout((xtermId)=>{ this.showConsole(xtermId, item); }, 200, xtermId);\n            },\n            closeXtermForAssets(index,item){\n                const xterm = xtermRunList[index];\n                xterm.destroy();\n                this.socket.emit('console_close_'+ item.xtermId)\n                this.xtermList.splice(index,1);\n                xtermRunList.splice(index, 1);\n                if(item.xtermId !== this.navIndex) return;\n\n                if(this.xtermList[index]){\n                    this.navIndex = this.xtermList[index].xtermId;\n                }else if(this.xtermList[index-1]){\n                    this.navIndex = this.xtermList[index-1].xtermId;\n                }else{\n                    this.navIndex = -1\n                }\n            },\n            xtrem(){\n                var term = new Terminal();\n                term.open(document.getElementById('console_terminal_0'));\n                term.write('Hello from \\x1B[1;3;31mxterm.js\\x1B[0m $ ')\n            },\n            showConsole(index,assetsItem){\n                const xteam = util.xteam({\n                    index,\n                    socket: this.socket,\n                    assetsItem,\n                })\n                xtermRunList.push(xteam);\n            },\n            goHome(){\n                location.href=\"/\"\n            },\n            onEnter(){\n                const xtermList = this.xtermList;\n                if(!xtermList || !xtermList.length) {\n                    popup.alert({title:'请登录中的资产!'});\n                    return;\n                }\n                \n                xtermList.forEach(item => {\n                    this.socket.emit('console_data_'+ item.xtermId, this.instructions + '\\n')\n                })\n                keyHistory.push(this.instructions);\n                hisBegin = keyHistory.length;\n                util.setStorage('local','console_key_history',JSON.stringify(keyHistory));\n                this.instructions = '';\n            },\n            onKeyUp(e){\n                if(e.code === 'ArrowUp'){\n                    if (hisBegin > 0) {\n                        hisBegin--\n                        this.instructions = keyHistory[hisBegin];\n                    }\n                }else if(e.code === 'ArrowDown'){\n                    if (hisBegin < keyHistory.length-1) {\n                        hisBegin++\n                        this.instructions = keyHistory[hisBegin];\n                    }\n                }\n            },\n            fullscreen(){\n                const _this = this;\n                $(document).keyup(function (event) {\n                    console.log(event.keyCode)\n                    if (event.keyCode === 112) {\n                        if(xtermRunList && xtermRunList[_this.navIndex]){\n                            xtermRunList[_this.navIndex].toggleFullScreen();\n                        }\n                    }\n                });\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/email.html",
    "content": "<style scoped>\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n</style>\n<div class=\"content\" id=\"email\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">邮件管理</div>\n        </el-col>\n    </el-row>\n\n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">邮件管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\"  @click=\"handleEmail(1)\">新增邮件</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\" :row-class-name=\"tableRowClassName\">\n                <el-table-column prop=\"email\" label=\"邮件地址\">\n                </el-table-column>\n                <el-table-column prop=\"name\" label=\"邮件所属人\">\n                </el-table-column>\n                <el-table-column label=\"可用状态\">\n                    <template slot-scope=\"scope\">\n                        <div v-if=\"scope.row.status == 1\" class=\"success\">已启用</div>\n                        <div v-if=\"scope.row.status != 1\" class=\"red\">已禁用</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"操作\">\n                    <template slot-scope=\"scope\">\n                        <div>\n                            <el-button @click=\"handleEmail(2,scope.row)\" type=\"primary\" size=\"small\">编辑</el-button>\n                            <el-button v-if=\"scope.row.status==1?true:false\" @click=\"hanleUse(scope.row)\" type=\"danger\"\n                                size=\"small\" plain>禁用</el-button>\n                            <el-button v-if=\"scope.row.status==0?true:false\" @click=\"hanleUse(scope.row)\" type=\"success\"\n                                size=\"small\">启用</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" type=\"danger\" size=\"small\">删除</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"common_pages\">\n            <el-pagination background @size-change=\"handleSizeChange\" @current-change=\"handleCurrentChange\"\n                :current-page.sync=\"currentPage\" :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\"\n                :total=\"totalCount\">\n            </el-pagination>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog width=\"500px\" title=\"邮件编辑\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"com_model_block\">\n                <div class=\"left\">邮件地址</div>\n                <el-input class=\"inp\" v-model.trim=\"query.email\" placeholder=\"请输入正确的邮件格式\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">邮件所属人</div>\n                <el-input class=\"inp\" v-model.trim=\"query.name\" placeholder=\"请输入邮件所属人\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">启用状态</div>\n                <el-switch v-model=\"query.status\" active-color=\"#409EFF\"></el-switch>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitEmail\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n</div>\n<script>\n    new Vue({\n        el: '#email',\n        data: function () {\n            return {\n                datalist: [],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                dialogTableVisible: false,\n                type: 1,\n                query: {\n                    type: 1,\n                    id: '',\n                    email: '',\n                    name: '',\n                    status: true,\n                },\n            }\n        },\n        filters: {\n        },\n        mounted() {\n            this.getList();\n        },\n        methods: {\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/email/list?pageNo=${this.currentPage}&pageSize=${this.pageSize}`,\n                    success: data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            submitEmail() {\n                if (!util.regCombination('e').test(this.query.email)) {\n                    popup.alert({ title: '请正确填写邮件地址!' });\n                    return;\n                }\n                if (!this.query.name) {\n                    popup.alert({ title: '请填写邮件所属人!' });\n                    return;\n                }\n                this.query.status = this.query.status ? 1 : 0;\n                util.ajax({\n                    url: `${config.baseApi}api/v1/email/handle`,\n                    data: this.query,\n                    success: data => {\n                        this.dialogTableVisible = false;\n                        this.getList();\n                        popup.miss({ title: '操作成功!' });\n                    }\n                })\n            },\n            handleEmail(type, item) {\n                if (item) item = JSON.parse(JSON.stringify(item));\n                if (item) {\n                    this.query = {\n                        type: type,\n                        id: item._id,\n                        email: item.email,\n                        name: item.name,\n                        status: item.status == 1 ? true : false,\n                    }\n                } else {\n                    this.query = {\n                        type: 1,\n                        id: '',\n                        email: '',\n                        name: '',\n                        status: true,\n                    }\n                }\n                this.dialogTableVisible = true;\n            },\n            handleSizeChange(val) {\n                console.log(`每页 ${val} 条`);\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            hanleUse(item) {\n                let str = item.status == 1 ? '禁用' : '启用';\n                popup.confirm({\n                    title: `确定${str}${item.name}邮件吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/email/setStatus`,\n                            data: {\n                                id: item._id,\n                                status: item.status == 1 ? 0 : 1,\n                            },\n                            success: data => {\n                                item.status = item.status == 1 ? 0 : 1,\n                                    popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            hanleDelete(item) {\n                popup.confirm({\n                    title: `确定删除${item.name}邮件吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/email/delete`,\n                            data: {\n                                id: item._id,\n                            },\n                            success: data => {\n                                this.getList();\n                                popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            tableRowClassName({ row, rowIndex }) {\n                if (row.status != 1) {\n                    return 'disabled-row';\n                }\n                return '';\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/environment.html",
    "content": "<style scoped>\n    \n</style>\n<div class=\"content\" id=\"environment\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">环境管理</div>\n        </el-col>\n    </el-row>\n\n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">环境管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\" @click=\"handleEnvironment(1)\">新增环境</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\">\n                <el-table-column prop=\"name\" label=\"环境名称\">\n                </el-table-column>\n                <el-table-column prop=\"code\" label=\"环境编码\">\n                </el-table-column>\n                <el-table-column label=\"操作\">\n                    <template slot-scope=\"scope\">\n                        <div>\n                            <el-button @click=\"handleEnvironment(2,scope.row)\" type=\"primary\" size=\"small\">编辑</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" icon=\"el-icon-delete\" type=\"danger\" size=\"small\">删除</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"common_pages\">\n            <el-pagination background @size-change=\"handleSizeChange\" @current-change=\"handleCurrentChange\"\n                :current-page.sync=\"currentPage\" :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\"\n                :total=\"totalCount\">\n            </el-pagination>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog width=\"500px\" title=\"环境编辑\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"com_model_block\">\n                <div class=\"left\">环境名称<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"environment.name\" placeholder=\"请输环境名称\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">环境编码<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"environment.code\" placeholder=\"以env_开头、例如env_zxhy\" :disabled=\"environment.type==2?true:false\"></el-input>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitEnvironment\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n</div>\n<script>\n    new Vue({\n        el: '#environment',\n        data: function () {\n            return {\n                datalist: [],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                dialogTableVisible: false,\n                type: 1,\n                environment: {\n                    type: 1,\n                    id: '',\n                    name: '',\n                    code: '',\n                },\n            }\n        },\n        filters: {\n        },\n        mounted() {\n            this.getList();\n        },\n        methods: {\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/environment/list`,\n                    data:{\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                    },\n                    success: data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            submitEnvironment() {\n                if (!this.environment.name) {\n                    popup.alert({ title: '请填写环境名称!' });\n                    return;\n                }\n                if (!this.environment.code) {\n                    popup.alert({ title: '请填写环境编码!' });\n                    return;\n                }\n                util.ajax({\n                    url: `${config.baseApi}api/v1/environment/handle`,\n                    data: this.environment,\n                    success: data => {\n                        this.dialogTableVisible = false;\n                        this.getList();\n                        popup.miss({ title: '操作成功!' });\n                    }\n                })\n            },\n            handleEnvironment(type, item) {\n                if (item) item = JSON.parse(JSON.stringify(item));\n                if (item) {\n                    this.environment = item;\n                } else {\n                    this.environment = {\n                        type: 1,\n                        id: '',\n                        name: '',\n                        code: 'env_' + util.randomString(8),\n                    }\n                }\n                this.environment.type = type;\n                this.dialogTableVisible = true;\n            },\n            handleSizeChange(val) {\n                console.log(`每页 ${val} 条`);\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            hanleDelete(item) {\n                popup.confirm({\n                    title: `确定删除${item.name}环境吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/environment/delete`,\n                            data: {\n                                id: item._id,\n                            },\n                            success: data => {\n                                this.getList();\n                                popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/footer.html",
    "content": "<style>\n    .apa_footer .content{\n        position: fixed;\n        left:0;\n        bottom:0;\n        width:100%;\n        height:30px;\n        font-size:12px;\n        color:#aaa;\n        padding-left:220px;\n    }\n</style>\n<div class=\"apa_footer\">\n    <div class=\"content\">\n        Copyright zane © 2019\n    </div>\n</div>\n\n<script>\n    \n</script>"
  },
  {
    "path": "app/view/header.html",
    "content": "<style scoped>\n.header{\n   position: fixed;\n   left:0;\n   top:0;\n   width:100%;\n   height:50px;\n   z-index:10;\n   padding-left:220px;\n   display: flex;\n   flex-direction: row;\n   justify-content: space-between;\n   align-items: center;\n   border-bottom: solid 1px #eee;\n   background:#fff;\n}\n.header .left{\n   padding-left:20px;\n}\n.header .right{\n   display: flex;\n   flex-direction: row;\n   align-items: center;\n   justify-content: flex-end;\n   padding-right:10px;\n}\n.header .right .block{\n   cursor:pointer;\n   padding:0 10px;\n   height:50px;\n   line-height:50px;\n}\n</style>\n\n<div class=\"header\" id=\"header\" v-cloak>\n   <div class=\"left\">\n      <!-- <el-breadcrumb separator=\"/\">\n         <el-breadcrumb-item :to=\"{ path: '/' }\">首页</el-breadcrumb-item>\n         <el-breadcrumb-item><a href=\"/\">活动管理</a></el-breadcrumb-item>\n         <el-breadcrumb-item>活动列表</el-breadcrumb-item>\n         <el-breadcrumb-item>活动详情</el-breadcrumb-item>\n      </el-breadcrumb> -->\n   </div>\n   <div class=\"right\">\n      <!-- <div class=\"block\">个人中心</div> -->\n      <div class=\"block\" @click=\"logout\">退出登录</div>\n   </div>\n</div>\n\n<script>\n   new Vue({\n      el: '#header',\n      data: function () {\n         return {\n            userMsg: {\n               user_name: '游小白',\n               token: ''\n            },\n         }\n      },\n       mounted() {\n         this.setToken()\n      },\n      methods: {\n         setToken() {\n            util.ajax({\n               type: 'get',\n               url: config.baseApi + 'api/v1/set/token',\n               success: function (data) { }\n            })\n         },\n         goToHome() {\n            this.$router.push({ name: 'home' })\n         },\n         logout() {\n            const _this = this;\n            popup.confirm({ title: '确定退出登录吗？', yes() {\n               util.ajax({\n                  type: 'get',\n                  url: `${config.baseApi}api/v1/user/logout?token=${_this.userMsg.token}`,\n                  success: data => {\n                     util.setStorage('local', 'userMsg', '');\n                     location.href = `/login`\n                  }\n               })\n            }})\n         },\n      }\n   })\n</script>\n"
  },
  {
    "path": "app/view/home.html",
    "content": "<style scoped>\n    .content{\n        padding:20px;\n    }\n    \n    .terminal{\n        padding:20px;\n    }\n</style>\n<div class=\"content\" id=\"home\" v-cloak>\n    \n    <div>textarea 修改文件内容</div>\n    <div id=\"result\" style=\"display:none;\"><%=data.result %></div>\n    <textarea class=\"text_comm_tarea mt10\" v-model=\"textarea\"></textarea>\n    <el-button class=\"mt20\" @click=\"update\" type=\"primary\">修改</el-button>\n\n    <div>textarea 新增文件</div>\n    <div id=\"result\" style=\"display:none;\"></div>\n    <div> <span>文件名称</span> <input type=\"text\" v-model.trim=\"filename\" placeholder=\"请填写需要新增文件的名称\"></div>\n    <textarea class=\"text_comm_tarea mt10\" v-model=\"textarea_1\"></textarea>\n    <el-button class=\"mt20\" @click=\"add\" type=\"primary\">新增</el-button>\n\n    <div class=\"mb20\"></div>\n    <div>xterm框效果</div>\n    <div class=\"mt10\" id=\"terminal\"></div>\n\n</div>\n<script>\nnew Vue({\n    el: '#home',\n    data: function () {\n        return {\n            name:'zane',\n            textarea: '',\n            textarea_1:'',\n            filename:'',\n        }\n    },\n    filters: {\n    },\n    mounted() {\n        this.textarea = $('#result').text()\n\n        var term = new Terminal();\n        term.open(document.getElementById('terminal'));\n        \n        function runFakeTerminal() {\n            if (term._initialized) {\n                return;\n            }\n\n            term._initialized = true;\n\n            term.prompt = () => {\n                term.write('\\r\\n$ ');\n            };\n\n            term.prompt();\n\n            term.on('key', function (key, ev) {\n                const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey;\n\n                if (ev.keyCode === 13) {\n                    term.prompt();\n                } else if (ev.keyCode === 8) {\n                    if (term._core.buffer.x > 2) {\n                        term.write('\\b \\b');\n                    }\n                } else if (printable) {\n                    term.write(key);\n                }\n            });\n\n            term.on('paste', function (data) {\n                term.write(data);\n            });\n        }\n        runFakeTerminal();\n    },\n    methods: {\n        update(){\n            util.ajax({\n                url:`${config.baseApi}api/v1/files/updatefile`,\n                data:{\n                    content: this.textarea,\n                },\n                success:data=>{\n\n                }\n            })\n        },\n        add(){\n            util.ajax({\n                url: `${config.baseApi}api/v1/files/addfile`,\n                data: {\n                    filename:this.filename,\n                    content: this.textarea_1,\n                    remotePath:'/data/down',\n                },\n                success: data => {\n\n                }\n            })\n        },\n    }    \n})\n</script>"
  },
  {
    "path": "app/view/layout.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <!-- meta -->\n        <title>自动化发布系统</title>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <link rel=\"icon\" href=\"/public/img/favicon.ico\" type=\"image/x-icon\">\n        <!-- css -->\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"/public/css/base.css\">\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"/public/lib/popup/popup.css\">\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"/public/lib/page/page.css\">\n        <link rel=\"stylesheet\" href=\"//unpkg.com/element-ui/lib/theme-chalk/index.css\">\n        <link href=\"//cdn.bootcss.com/xterm/3.12.2/xterm.min.css\" rel=\"stylesheet\">\n        <!-- js -->\n        <script src=\"//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js\"></script>\n        <script src=\"//cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js\"></script>\n        <script src=\"//cdnjs.cloudflare.com/ajax/libs/vue/2.5.0/vue.min.js\"></script>\n        <script src=\"//cdn.bootcss.com/socket.io/2.2.0/socket.io.slim.js\"></script>\n        <script src=\"//unpkg.com/element-ui/lib/index.js\"></script>\n        <script src=\"//cdn.bootcss.com/xterm/3.12.2/xterm.min.js\"></script>\n        <script src=\"//cdn.bootcss.com/xterm/3.12.2/addons/fit/fit.min.js\"></script>\n        <script src=\"//cdn.bootcss.com/xterm/3.12.2/addons/fullscreen/fullscreen.min.js\"></script>\n        <script src=\"//cdn.bootcss.com/xterm/3.12.2/addons/attach/attach.min.js\"></script>\n        <script src=\"/public/lib/popup/popup.js\"></script>\n        <script src=\"/public/lib/page/page.js\"></script>\n        <script src=\"/public/js/config.js\"></script>\n        <script src=\"/public/js/util.js\"></script>\n        <script src=\"/public/js/vue-filters.js\"></script>\n        <script src=\"/public/js/vue-components.js\"></script>\n    </head>\n    <body>\n        <div id=\"loading\"></div>\n        <div id='main'>\n            <% include ./header.html %>\n            <% include ./side.html %>\n            <div id=\"body_content\"><%- body %></div>\n            <% include ./footer.html %>\n        </div>\n    </body>\n</html>"
  },
  {
    "path": "app/view/login.html",
    "content": "<style scoped>\n    .header,.side,.apa_footer{display: none;}\n    #body_content{margin-left:0px;}\n    .main{\n        display: flex;\n        flex-direction: row;\n        justify-content: center;\n        margin-top:100px;\n    }\n    .main .left{\n        width:350px;\n        margin-right:30px;\n    }\n    .main .left .title{\n        font-size:30px;\n        font-weight:300;\n        margin-bottom:10px;\n    }\n    .main .left .desc{\n        line-height:30px;\n        font-size:13px;\n        color:#666;\n\n    }\n    .main .left .banquan{\n        margin-top:30px;\n        color:#aaa;\n    }\n    .main .right{\n        width:380px;\n        background:#fff;\n        padding:20px;\n        box-shadow: 5px 5px 10px #eee;\n    }\n    .main .right .h1{\n        font-size:28px;\n        font-weight:300;\n        margin:10px 0 20px;\n        text-align:center;\n    }\n    .main .right .item{\n        display: flex;\n        flex-direction: row;\n        align-content: center;\n        margin-bottom:20px;\n    }\n    .main .right .item span{\n        font-size:30px;\n        color:#409EFF;\n        margin-right:10px;\n    }\n    .main .right .item .inp{\n        width:300px;\n    }\n    .main .right .submit{\n        width:300px;\n        margin-left:40px;\n\n    }\n    .main .right .texts{\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n        margin-top:15px;\n    }\n</style>\n<div class=\"content\" id=\"login\" v-cloak>\n    <div class=\"main\">\n        <div class=\"left\">\n            <div class=\"title\">APubPlat自动化部署平台</div>\n            <div class=\"desc\">开源免费的自动化部署、运维平台，也可做堡垒机来使用。</div>\n            <div class=\"desc\">实现了Web界面话的Terminal，可替换xshell等工具。可开启多窗口和批量命令的运行。</div>\n            <div class=\"desc\">友好的持续集成，支持web前端node、java、php等后端servers的发布，并支持单机和多机的同时发布能力。</div>\n            <div class=\"desc\">对我有帮助，去star支持 <a target=\"_blank\" href=\"https://github.com/wangweianger/APubPlat\">github address</a></div>\n            <div class=\"banquan\">Copyright Zane © 2019</div>\n        </div>\n        <div class=\"right\">\n            <div class=\"h1\">用户登录</div>\n            <div class=\"item\">\n                <span class=\"el-icon-user\"></span>\n                <el-input class=\"inp\" type=\"text\" v-model.trim=\"username\" placeholder=\"请输入账号\"></el-input>\n            </div>\n            <div class=\"item\">\n                <span class=\"el-icon-lock\"></span>\n                <el-input class=\"inp\" type=\"password\" v-model.trim=\"password\" placeholder=\"请输入密码\"></el-input>\n            </div>\n            <div class=\"item\" v-show=\"type===2\">\n                <span class=\"el-icon-lock\"></span>\n                <el-input class=\"inp\" type=\"password\" v-model.trim=\"typassword\" placeholder=\"请确认密码\"></el-input>\n            </div>\n            <div class=\"btn\">\n                <el-button class=\"submit\" v-show=\"type===1\" type=\"primary\" @click=\"login\">登录</el-button>\n                <el-button class=\"submit\" v-show=\"type===2\" type=\"primary\" @click=\"register\">注册</el-button>\n            </div>\n            <div class=\"texts\">\n                <el-button class=\"bt\" type=\"text\" @click=\"login\">忘记密码</el-button>\n                <el-button class=\"bt\" type=\"text\" @click=\"type=1\" v-show=\"type===2\">返回登录</el-button>\n                <el-button class=\"bt\" type=\"text\" @click=\"type=2\" v-show=\"type===1\">注册账号</el-button>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n    new Vue({\n        el: '#login',\n        data: function () {\n            return {\n                type: 1,   //1:登录  2：注册\n                username:'',\n                password:'',\n                typassword:'',\n            } \n        },\n        mounted() {\n        },\n        methods: {\n            login() {\n                if (!this.username) {\n                    popup.alert({ type: 'msg', title: '用户名有误!' }); return false;\n                }\n                if (!this.password) {\n                    popup.alert({ type: 'msg', title: '用户密码有误!' }); return false;\n                }\n                util.ajax({\n                    url: config.baseApi + 'api/v1/user/login',\n                    data: {\n                        userName: this.username,\n                        passWord: this.password\n                    },\n                    success: function (data) {\n                        popup.miss({ title: \"登录成功！\" });\n                        util.setStorage('local', 'userMsg', JSON.stringify(data.data))\n                        setTimeout(() => { location.href = '/' }, 500)\n                    }\n                })\n            },\n            register() {\n                if (!this.username) {\n                    popup.alert({ type: 'msg', title: '用户名有误!' }); return false;\n                }\n                if (!this.password) {\n                    popup.alert({ type: 'msg', title: '用户密码有误!' }); return false;\n                }\n                if (this.password !== this.typassword) {\n                    popup.alert({ type: 'msg', title: '两次密码输入不一致!' }); return false;\n                }\n\n                util.ajax({\n                    url: config.baseApi + 'api/v1/user/register',\n                    data: {\n                        userName: this.username,\n                        passWord: this.password\n                    },\n                    success(data) {\n                        popup.miss({ title: \"注册成功！\" });\n                        util.setStorage('local', 'userMsg', JSON.stringify(data.data))\n                        setTimeout(() => { location.href = '/' }, 500)\n                    }\n                })\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/logs.html",
    "content": "<style scoped>\n    .com_top .inp {\n        width: 180px;\n    }\n\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n\n    .model_assets_list .btns {\n        border-top: solid 1px #eee;\n        padding: 35px 0 0;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n    }\n\n    .model_assets_list .ml {\n        margin-left: 20px;\n    }\n\n    .model_assets_list .common_pages {\n        justify-content: flex-start;\n    }\n\n    .assets_item_list:nth-child(odd) {\n        border-bottom: solid 1px #eee;\n        padding-bottom: 5px;\n        margin-bottom: 5px;\n    }\n\n    .assets_item_list.first {\n        border-bottom: none;\n        padding-bottom: 0px;\n        margin-bottom: 0px;\n    }\n\n    .assets_item_item {\n        font-size: 12px;\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n\n    .assets_item_item .as_1 {\n        display: inline-block;\n        width: 250px;\n    }\n\n    .assets_item_item .as_2 {\n        display: inline-block;\n        width: 150px;\n    }\n\n    .model_assets_list .b_t {\n        padding-top: 15px;\n        border-top: solid 1px #eee;\n    }\n\n    .select_app {\n        font-size: 22px;\n        text-align: center;\n        margin: 50px auto;\n        text-align: center;\n        color: #999;\n        font-weight: 300;\n    }\n\n    .el-tabs-content {\n        background: #fff;\n        padding: 15px;\n        box-shadow: 5px 5px 10px #eee;\n    }\n    .assets-item{\n        margin-bottom:0px;\n    }\n    .assets-item .t0{\n        display: inline-block;\n        font-size:12px;\n        margin-right:10px;\n    }\n    .assets-item .t1{\n        width:180px;\n    }\n    .assets-item .t2{\n        width:130px;\n    }\n    .assets-item .t3{\n        width:130px;\n    }\n    .assets-item .t4{\n        width:100px;\n    }\n    .el-table__expanded-cell[class*=cell] {\n        padding: 10px 50px;\n    }\n    .el-form-item__content{\n        line-height:25px;\n    }\n    .demo-table-expand{\n        padding-left:60px;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">构建日志</div>\n        </el-col>\n    </el-row>\n    <div class=\"com_top\">\n        <el-select v-model=\"environ_code\" class=\"inp\" @change=\"environChange\" placeholder=\"部署环境筛选\">\n            <el-option v-for=\"item in environlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-select v-model=\"team_code\" class=\"inp\" @change=\"teamChange\" placeholder=\"所属团队筛选\">\n            <el-option v-for=\"item in teamlist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-select v-model=\"application_id\" class=\"inp\" @change=\"getList\" placeholder=\"请选择所属应用\">\n            <el-option v-for=\"item in applist\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n            </el-option>\n        </el-select>\n        <el-input v-model=\"name\" class=\"inp ml20\" placeholder=\"按应用名称查询\"></el-input>\n        <el-button @click=\"getList\" type=\"primary\">搜索</el-button>\n    </div>\n    <el-tabs class=\"el-tabs-content\" v-model=\"activeName\" @tab-click=\"handleClick\">\n        <el-tab-pane v-for=\"(item,index) in logtypelist\" :key=\"index\" :label=\"item.label\" :name=\"item.value\">\n            <!-- table -->\n            <div class=\"com_table_block mt20\" v-if=\"datalist.length\">\n                <div class=\"table_body\">\n                    <el-table :data=\"datalist\" style=\"width: 100%\">\n                        <el-table-column type=\"expand\">\n                            <template slot-scope=\"scope\">\n                                <el-form label-position=\"left\" inline class=\"demo-table-expand\">\n                                    <div v-for=\"(item,index) in scope.row.content\" :key=\"index\">\n                                        <el-form-item class=\"assets-item\">\n                                            <span class='t0 t1'>资产名称:{{item.assets_name}}</span>\n                                            <span class='t0 t2'>外网IP:{{item.outer_ip}}</span>\n                                            <span class='t0 t3'>内网IP:{{item.lan_ip}}</span>\n                                            <span v-if=\"item.code==0?true:false\" class='t0 t4 success'>{{typetext}}成功</span>\n                                            <span v-if=\"item.code!=0?true:false\" class='t0 t4 red'>{{typetext}}失败</span>\n                                        </el-form-item>\n                                    </div>\n                                </el-form>\n                            </template>\n                        </el-table-column>\n                        <el-table-column prop=\"name\" label=\"任务名称\" width=\"320\">\n                        </el-table-column>\n                        <el-table-column :label=\"typetext+'状态'\" width=\"150\">\n                            <template slot-scope=\"scope\">\n                                <div class=\"success\" v-if=\"scope.row.status==0\">{{typetext}}成功</div>\n                                <div class=\"warning\" v-if=\"scope.row.status==1\">部分{{typetext}}成功</div>\n                                <div class=\"red\" v-if=\"scope.row.status==2\">{{typetext}}失败</div>\n                            </template>\n                        </el-table-column>\n                        <el-table-column prop=\"user_name\" label=\"操作人\" width=\"120\">\n                        </el-table-column>\n                        <el-table-column :label=\"typetext+'时间'\" width=\"200\">\n                            <template slot-scope=\"scope\">\n                                <div>{{scope.row.create_time | date('-',true)}}</div>\n                            </template>\n                        </el-table-column>\n                        <el-table-column label=\"操作\">\n                            <template slot-scope=\"scope\">\n                                <div>\n                                    <el-button @click=\"buildProcess(scope.row)\" type=\"text\" size=\"mini\">重新{{typetext}}</el-button>\n                                    <el-button v-if=\"activeName==2\" @click=\"goToReduction(scope.row)\" type=\"text\" size=\"mini\">应用还原</el-button>\n                                </div>\n                            </template>\n                        </el-table-column>\n                    </el-table>\n                </div>\n                <div class=\"common_pages\">\n                    <el-pagination background @current-change=\"handleCurrentChange\" :current-page.sync=\"currentPage\"\n                        :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\" :total=\"totalCount\">\n                    </el-pagination>\n                </div>\n            </div>\n            <!-- empty -->\n            <div class=\"select_app\" v-if=\"!datalist.length\">\n                <div>暂无需要部署的应用,请新建环境和应用!</div>\n            </div>\n        </el-tab-pane>\n    </el-tabs>\n</div>\n<script>\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                team_code:'',\n                environ_code:'',\n                application_id:'',\n                name:'',\n                activeName: '1',\n                typetext:'构建',\n                teamlist: [],\n                datalist: [],\n                environlist:[],\n                applist:[],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                logtypelist: [{\n                    value: '1',\n                    label: '应用构建日志',\n                }, {\n                    value: '2',\n                    label: '应用备份日志',\n                }, {\n                    value: '3',\n                    label: '生成应用配置日志',\n                },{\n                    value: '5',\n                    label: '应用还原日志',\n                }],\n            }\n        },\n        watch:{\n            \"activeName\"(val){\n                if (val == 1) this.typetext = '构建';\n                if (val == 2) this.typetext = '备份';\n                if (val == 3) this.typetext = '配置';\n                if (val == 5) this.typetext = '还原';\n            },\n        },\n        mounted() {\n            this.getList();\n            this.getTeamList();\n            this.getEnvironList();\n            this.getAppList();\n        },\n        filters: {\n            date:Filter.date,\n        },\n        methods: {\n            handleClick(tab, event) {\n                this.getList();\n            },\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/logs/list`,\n                    data: {\n                        application_id: this.application_id,\n                        name: this.name,\n                        type: this.activeName,\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        datas.forEach(item=>{\n                            if(item.content && item.content.length) {\n                                let begin = 0;\n                                len = item.content.length;\n                                item.content.forEach(item1=>{\n                                    if(item1.code ==0) begin++\n                                })\n                                if(item.content.length == begin){\n                                    item.status = 0;\n                                }else if(begin == 0){\n                                    item.status = 2;\n                                }else {\n                                    item.status = 1;\n                                }\n                            }\n                        });\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = datas;\n                    }\n                })\n            },\n            getAppList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/list`,\n                    data: {\n                        team_code: this.team_code,\n                        environ_code: this.environ_code,\n                        status: 1,\n                        pageNo: 1,\n                        pageSize: 100,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                value: item._id,\n                                label: item.name\n                            })\n                        })\n                        this.applist = options;\n                    }\n                })\n            },\n            getTeamList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/team/list`,\n                    data: {\n                        pageNo: 1,\n                        pageSize: 100,\n                        status: 1,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.teamlist = options;\n                    }\n                })\n            },\n            getEnvironList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/environment/list`,\n                    data: {\n                        pageNo: 1,\n                        pageSize: 100,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        const options = [];\n                        datas.forEach(item => {\n                            options.push({\n                                value: item.code,\n                                label: item.name\n                            })\n                        })\n                        this.environlist = options;\n                    }\n                })\n            },\n            environChange(){\n                this.application_id = '';\n                this.getAppList();\n            },\n            teamChange(){\n                this.application_id = '';\n                this.getAppList();\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            buildProcess(item) {\n                location.href = \"/buildprocess?id=\" + item.application_id;\n            },\n            goToReduction(item) {\n                location.href = \"/reduction?id=\" + item.application_id;\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/reduction.html",
    "content": "<style scoped>\n    .config_main {\n        padding: 20px;\n        background: #fff;\n        box-shadow: 5px 5px 10px #eee;\n    }\n\n    .config_main .row-col {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n\n    .config_main .block {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        flex-wrap: wrap;\n        font-size: 14px;\n    }\n\n    .config_main .block .left {\n        width: 150px;\n        color: #999;\n        font-size: 12px;\n    }\n\n    .config_main .block .right {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n    }\n\n\n    .btns {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n        justify-content: center;\n        padding-top: 30px;\n    }\n\n    .item_task {\n        width: 1200px;\n    }\n\n    .item_task .title {\n        font-size: 25px;\n        font-weight: 300;\n    }\n\n    .tab-btns {\n        display: flex;\n        flex-direction: row;\n        flex-wrap: wrap;\n    }\n\n    .tab-btns .btn-item {\n        margin-right: 10px;\n    }\n    .assets-item{\n        margin-bottom:0px;\n    }\n    .assets-item .t0{\n        display: inline-block;\n        font-size:12px;\n        margin-right:10px;\n    }\n    .assets-item .t1{\n        width:180px;\n    }\n    .assets-item .t2{\n        width:130px;\n    }\n    .assets-item .t3{\n        width:130px;\n    }\n    .assets-item .t4{\n        width:100px;\n    }\n    .el-table__expanded-cell[class*=cell] {\n        padding: 10px 50px;\n    }\n    .el-form-item__content{\n        line-height:25px;\n    }\n    .com_model_block{\n        margin-bottom:12px;\n    }\n    .com_model_block .left{\n        width:130px;\n    }\n    .com_model_block .inp1{\n        width:300px;\n    }\n    .comm_build_textarea {\n        width: 1000px;\n        height: 220px;\n    }\n    .com_model_main .btns{\n        margin-top:0;\n    }\n    .demo-table-expand{\n        padding-left:60px;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">应用还原</div>\n        </el-col>\n    </el-row>\n    <div class=\"config_main mt30 fs-18\">\n        应用名称: {{datas.name}}\n    </div>\n    <div class=\"config_main mt30\">\n        <div class=\"block\">\n            <div class=\"bottom\" style=\"width:100%;\">\n                <div class=\"top mb20 fs-16\">资产列表</div>\n                <div class=\"mb10\">\n                    <el-button @click=\"serverConfigs()\" type=\"primary\" icon=\"el-icon-tickets\" size=\"small\">脚本任务\n                    </el-button>\n                    <el-button @click=\"applicationConsole()\" type=\"danger\" icon=\"el-icon-s-tools\" size=\"small\">控制台\n                    </el-button>\n                    <span class=\"ml10 light\"><i\n                            class=\"el-icon-warning fs-16 mr5 primary\"></i>默认执行所有资产项操作，若有选择则执行选中资产项的相关操作(例如：部署、生成构建配置等操作)。\n                    </span>\n                </div>\n                <el-table :data=\"assetslist\" ref=\"assetsTable\" @selection-change=\"handleSelectionChange\"\n                    style=\"width:auto;font-size:13px;\">\n                    <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n                    <el-table-column prop=\"name\" label=\"资产名称\" width=\"200\"></el-table-column>\n                    <el-table-column prop=\"code\" label=\"资产编码\" width=\"200\"></el-table-column>\n                    <el-table-column prop=\"outer_ip\" label=\"外网IP\" width=\"150\"></el-table-column>\n                    <el-table-column prop=\"lan_ip\" label=\"内网IP\" width=\"150\"></el-table-column>\n                    <el-table-column prop=\"user\" label=\"用户名\" width=\"80\"></el-table-column>\n                    <el-table-column label=\"操作\">\n                        <template slot-scope=\"scope\">\n                            <div class=\"tab-btns\">\n                                <el-button class=\"btn-item\" @click=\"serverConfigs(scope.row)\" type=\"default\"\n                                    size=\"mini\">脚本任务</el-button>\n                            </div>\n                        </template>\n                    </el-table-column>\n                </el-table>\n            </div>\n        </div>\n    </div>\n    <div class=\"config_main mt30\">\n        <div class=\"block\">\n            <div class=\"bottom\" style=\"width:100%;\">\n                <div class=\"top mb20 fs-16\">应用备份列表</div>\n                <el-table :data=\"datalist\" style=\"width:auto;font-size:13px;\">\n                    <el-table-column type=\"expand\">\n                        <template slot-scope=\"scope\">\n                            <el-form label-position=\"left\" inline class=\"demo-table-expand\">\n                                <div v-for=\"(item,index) in scope.row.content\" :key=\"index\">\n                                    <el-form-item class=\"assets-item\">\n                                        <span class='t0 t1'>资产名称:{{item.assets_name}}</span>\n                                        <span class='t0 t2'>外网IP:{{item.outer_ip}}</span>\n                                        <span class='t0 t3'>内网IP:{{item.lan_ip}}</span>\n                                        <span v-if=\"item.code==0?true:false\" class='t0 t4 success'>备份成功</span>\n                                        <span v-if=\"item.code!=0?true:false\" class='t0 t4 red'>备份失败</span>\n                                    </el-form-item>\n                                </div>\n                            </el-form>\n                        </template>\n                    </el-table-column>\n                    <el-table-column prop=\"name\" label=\"备份名称\" width=\"250\"></el-table-column>\n                    <el-table-column label=\"备份状态\" width=\"100\">\n                        <template slot-scope=\"scope\">\n                            <div class=\"success\" v-if=\"scope.row.status==0\">备份成功</div>\n                            <div class=\"warning\" v-if=\"scope.row.status==1\">部分备份成功</div>\n                            <div class=\"red\" v-if=\"scope.row.status==2\">备份失败</div>\n                        </template>\n                    </el-table-column>\n                    <el-table-column label=\"备份时间\" width=\"160\">\n                        <template slot-scope=\"scope\">\n                            <div>{{scope.row.create_time | date('-',true)}}</div>\n                        </template>\n                    </el-table-column>\n                    <el-table-column label=\"应用资源位置\">\n                        <template slot-scope=\"scope\">\n                            <div>{{scope.row.content[0].project_path}}</div>\n                        </template>\n                    </el-table-column>\n                    <el-table-column label=\"备份文件位置\">\n                        <template slot-scope=\"scope\">\n                            <div>{{scope.row.content[0].backup_path}}</div>\n                        </template>\n                    </el-table-column>\n                    <el-table-column label=\"操作\" width=\"150\">\n                        <template slot-scope=\"scope\">\n                            <div class=\"tab-btns\">\n                                <el-button \n                                    class=\"btn-item\" \n                                    v-if=\"(scope.row.status==0 || scope.row.status==1) && !scope.row.delete\"\n                                    @click=\"appReduction(scope.row)\" \n                                    type=\"primary\" \n                                    size=\"small\">还原为此版本</el-button>\n                            </div>\n                        </template>\n                    </el-table-column>\n                </el-table>\n                <div class=\"common_pages\">\n                    <el-pagination background @current-change=\"handleCurrentChange\" :current-page.sync=\"currentPage\"\n                        :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\" :total=\"totalCount\">\n                    </el-pagination>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- model弹出框 -->\n    <el-dialog width=\"80%\" :title=\"taskItem.name+'任务-应用还原'\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"com_model_block\">\n                <div class=\"left\">应用资源位置</div>\n                <div>{{taskItem.project_path}}</div>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">备份文件位置</div>\n                <div>{{taskItem.backup_path}}</div>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">shell脚本写入方式</div>\n                <el-select v-model=\"taskItem.reduction_shell_write_type\" class=\"inp1\" placeholder=\"请选择\">\n                    <el-option v-for=\"item in options2\" :key=\"item.value\" :label=\"item.label\" :value=\"item.value\">\n                    </el-option>\n                </el-select>\n            </div>\n            <el-row>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">shell脚本路径<span class=\"red\">*</span></div>\n                        <div>\n                            <el-input v-model=\"taskItem.reduction_shell_path\" class=\"inp1\" placeholder=\"例如：/usr/src/sh/build.sh\"></el-input>\n                            <div class=\"ml20 light\">备注：若服务器目录或文件不存在则自动创建。</div>\n                        </div>\n                    </div>\n                </el-col>\n                <el-col :span=\"12\">\n                    <div class=\"com_model_block\">\n                        <div class=\"left\">shell脚本参数</div>\n                        <div><el-input v-model=\"taskItem.reduction_shell_opction\" class=\"inp1\" placeholder=\"shell脚本参数请以空格隔开\"></el-input></div>\n                    </div>\n                </el-col>\n            </el-row>\n            <div class=\"com_model_block\">\n                <div class=\"left\">任务脚本内容<span class=\"red\">*</span></div>\n                <div id=\"editor\" class=\"comm_build_textarea\"></div>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"deployBackupApp\">执行任务</el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n    <!-- 备份|构建日志 -->\n    <el-dialog width=\"80%\" :title=\"logsData.task_name\" :visible.sync=\"dialogModelVisible\">\n        <!-- <div></div> -->\n        <el-table :data=\"logsData.content\">\n            <el-table-column property=\"assets_name\" label=\"资产名称\" width=\"200\"></el-table-column>\n            <el-table-column property=\"outer_ip\" label=\"外网IP\" width=\"150\"></el-table-column>\n            <el-table-column property=\"lan_ip\" label=\"内网IP\" width=\"100\"></el-table-column>\n            <el-table-column label=\"执行状态\" width=\"150\">\n                <template slot-scope=\"scope\">\n                    <div v-if=\"scope.row.code==-100\"><i class=\"el-icon-loading\"></i> 应用还原中...</div>\n                    <div v-if=\"scope.row.code!=-100\" :class=\"scope.row.code==0?'success':'red'\">\n                        {{scope.row.code==0?'执行成功':'执行失败'}}\n                    </div>\n                </template>\n            </el-table-column>\n            <el-table-column property=\"shell\" label=\"执行shell内容\"></el-table-column>\n            <!-- <el-table-column v-if=\"logsData.type!==1\" property=\"data\" label=\"执行日志\"></el-table-column> -->\n        </el-table>\n    </el-dialog>\n    <script>var require = { paths: { 'vs': 'https://cdn.bootcss.com/monaco-editor/0.16.2/min/vs' } };</script>\n    <script src=\"//cdn.bootcss.com/monaco-editor/0.16.2/min/vs/loader.js\"></script>\n</div>\n<script>\n    let editor = null;\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                id: util.getQueryString('id'),\n                assetslist: [],\n                multipleSelection: [],\n                datas:{},\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                datalist:[],\n                logsData: { content: [] },\n                dialogModelVisible: false,\n                dialogTableVisible: false,\n                taskItem:{},\n                tasklist:[],\n                taskname:'',\n                options2: [{\n                    value: 1,\n                    label: '新建文件并上传方式'\n                }, {\n                    value: 2,\n                    label: 'shell窗口命令行创建方式'\n                }],\n            }\n        },\n        mounted() {\n            this.itemdetail();\n            this.getList();\n        },\n        filters: {\n            date: Filter.date,\n        },\n        methods: {\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/logs/list`,\n                    data: {\n                        application_id: this.id,\n                        type: 2,\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                    },\n                    success: data => {\n                        const datas = data.data.datalist || [];\n                        datas.forEach(item => {\n                            if (item.content && item.content.length) {\n                                let begin = 0;\n                                len = item.content.length;\n                                item.content.forEach(item1 => {\n                                    if (item1.code == 0) begin++\n                                })\n                                if (item.content.length == begin) {\n                                    item.status = 0;\n                                } else if (begin == 0) {\n                                    item.status = 2;\n                                } else {\n                                    item.status = 1;\n                                }\n                            }\n                        });\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = datas;\n                    }\n                })\n            },\n            itemdetail() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/application/itemdetail`,\n                    data: {\n                        id: this.id,\n                    },\n                    success: data => {\n                        this.datas = data.data;\n                        this.assetslist = data.data.assets_list || [];\n                        this.tasklist = data.data.task_list || [];\n                        const assetsId = util.getQueryString('assetsId');\n                        setTimeout(() => {\n                            if (assetsId) {\n                                this.assetslist.forEach(item => { if (assetsId == item._id) this.$refs.assetsTable.toggleRowSelection(item, true) })\n                            } else {\n                                this.assetslist.forEach(item => { this.$refs.assetsTable.toggleRowSelection(item, true); })\n                            }\n                        })\n                    }\n                })\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            handleSelectionChange(val) {\n                this.multipleSelection = val;\n            },\n            serverConfigs(item) {\n                if (!item && !this.multipleSelection.length) {\n                    popup.alert({ title: '请选择需要配置的资产!' })\n                    return;\n                }\n                const assestsList = item ? [item] : this.multipleSelection;\n                util.setStorage('session', 'assets_list_configs', JSON.stringify(assestsList));\n                location.href = \"/assetsconfig\";\n            },\n            applicationConsole() {\n                if (!this.multipleSelection.length) {\n                    popup.alert({ title: '请选择进入控制台的资产列表!' })\n                    return;\n                }\n                let ids = '';\n                this.multipleSelection && this.multipleSelection.forEach(item => {\n                    ids = ids + item._id + ','\n                })\n                ids = ids ? ids.slice(0, -1) : '';\n                window.open(\"/console?ids=\" + ids);\n            },\n            appReduction(item){\n                const backupPath = item.content[0].backup_path;\n                const projectPath = item.content[0].project_path;\n                if(!this.multipleSelection.length){\n                    popup.alert({ title: '请选择需要还原的资产!' })\n                    return \n                }\n                if(!backupPath){\n                    popup.alert({ title: '无备份记录!' })\n                    return \n                }\n                this.dialogTableVisible = true;\n\n                const taskname = this.taskname = item.name.replace('任务-服务备份', '') || '';\n                this.taskItem = Object.assign({}, this.taskItem, {\n                    name: taskname,\n                    backup_path: backupPath,\n                    project_path: projectPath,\n                })\n                const projectpth = projectPath.split('/'); projectpth.splice(-1);\n                const backup_exec = `\\cp -frp ${backupPath} ${projectpth.join('/')}`;\n                let backup_body = this.default_shell_body = `#!/bin/bash\\r\\n${backup_exec}\\r\\n#若后续还需运行其他命令请自行补充（例如：文件还原之后重启服务等操作）`;\n                let taskItem = null;\n                this.tasklist.forEach(item=>{\n                    if(item.task_name == taskname && item.reduction_shell_path) taskItem = item;\n                })\n                if(taskItem){\n                    this.taskItem.reduction_shell_path = taskItem.reduction_shell_path;\n                    this.taskItem.reduction_shell_opction = taskItem.reduction_shell_opction || '';\n                    this.taskItem.reduction_shell_write_type = taskItem.reduction_shell_write_type ? parseInt(taskItem.reduction_shell_write_type) : 1;\n                    backup_body = backup_body + taskItem.reduction_shell_body;\n                }else{\n                    backup_body = this.default_shell_body;\n                }\n                editor ? editor.setValue(backup_body) : setTimeout(() => { this.runMonaco('editor', backup_body); });\n            },\n            runMonaco(id, data) {\n                //载入Monaco\n                const _this = this;\n                require(['vs/editor/editor.main'], function () {\n                    //得到支持的语言\n                    var modesIds = monaco.languages.getLanguages().map(function (lang) { return lang.id }).sort();\n                    //创建编辑器\n                    editor = monaco.editor.create(document.getElementById(id), {\n                        //内容\n                        value: data,\n                        //语言\n                        language: 'shell',\n                        scrollBeyondLastLine: false,\n                        automaticLayout: true,\n                        //主题，三款：vs、vs-dark、hc-black\n                        theme: 'vs-dark',\n                        //代码略缩图\n                        minimap: {\n                            enabled: false\n                        }\n                    });\n                });\n            },\n            deployBackupApp(){\n                if (!this.taskItem.reduction_shell_path) {\n                    popup.alert({ title: '请填写shell脚本路径' });\n                    return;\n                }\n                const tasklist = this.tasklist;\n                tasklist.forEach(item => {\n                    if (item.task_name == this.taskname) {\n                        item.reduction_shell_path = this.taskItem.reduction_shell_path;\n                        item.reduction_shell_opction = this.taskItem.reduction_shell_opction;\n                        item.reduction_shell_write_type = this.taskItem.reduction_shell_write_type;\n                        item.reduction_shell_body = editor.getValue().replace(this.default_shell_body,'');\n                    }\n                })\n                util.ajax({\n                    url:`${config.baseApi}api/v1/application/updateConfigs`,\n                    data:{\n                        id:this.id,\n                        tasklist: tasklist,\n                    },\n                    success:data => {\n                        this.runBackupShell();\n                    }\n                })\n            },\n            runBackupShell(){\n                this.dialogModelVisible = true;\n                const assetslist = [];\n                this.multipleSelection.forEach(item => {\n                    assetslist.push({\n                        assets_name: item.name,\n                        code: -100,\n                        data: '',\n                        lan_ip: item.lan_ip,\n                        outer_ip: item.outer_ip,\n                        password: item.password,\n                        port: item.port,\n                        user: item.user\n                    })\n                })\n                this.logsData = {\n                    task_name: this.taskname + '任务-备份还原',\n                    type: 5,\n                    content: assetslist,\n                }\n                this.taskItem.reduction_shell_body = editor.getValue();\n                const userMsg = util.getStorage('local', 'userMsg');\n                const user_name = userMsg ? JSON.parse(userMsg).user_name : '';\n                util.ajax({\n                    nohideloading: true,\n                    notimeout: true,\n                    url: `${config.baseApi}api/v1/build/reductionApplications`,\n                    data: {\n                        id: this.id,\n                        taskItem: this.taskItem,\n                        user_name,\n                        assetsList: assetslist,\n                    },\n                    success: data => {\n                        this.logsData = data.data || {};\n                    }\n                })\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/side.html",
    "content": "<style scoped>\n  .el-menu-vertical-demo:not(.el-menu--collapse) {\n    width: 180px;\n    min-height: 400px;\n}\n.side{\n\tposition: fixed;\n\tleft:0;\n\ttop:0;\n\twidth:179px;\n    height:100%;\n    border-right:solid 1px #eee;\n    background:#fff;\n    z-index:11;\n}  \n.side .switch-nav{\n    width: 280px;\n    height:50px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    background:#fff;\n    color:#000;\n    font-size:16px;\n    border-bottom:solid 1px #eee;\n}\n.side .switch-nav .icon{\n    width:50px;\n    height:50px;\n    text-align:center;\n    line-height:50px;\n    border-right:solid 1px #eee;\n    margin-right:10px;\n    \n}\n.side .switch-nav .icon i{\n    font-size:20px;\n}\n.side .side_nav{\n    padding-top:5px;\n}\n.side .side_item{\n    height:50px;\n    line-height:50px;\n}\n/*滚动条样式*/\n.side .label::-webkit-scrollbar {\n    width: 4px;     \n    height: 4px;\n}\n.side .label::-webkit-scrollbar-thumb {\n    border-radius: 5px;\n    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\n    background: rgba(0,0,0,0.6);\n}\n.side .label::-webkit-scrollbar-track {\n    -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\n    border-radius: 0;\n    background: rgba(0,0,0,.1);\n}\n.side .el-menu{\n    border-right:none;\n}\n.curpor-check{\n    cursor:pointer;\n}\n</style>\n\n<div class=\"side\" id=\"side\" :style=\"{width:width+'px'}\" v-cloak>\n    <div class=\"switch-nav\">\n        <div class=\"icon curpor-check\" :style=\"{width:icon_width+'px'}\" @click=\"checkSide\">\n            <i class=\"el-icon-menu\"></i>\n            <!-- <span class=\"iconfont\">&#xe626;</span> -->\n        </div>\n        <div class=\"text\">APubPlat自动化部署平台</div>\n    </div>\n    <div class=\"label\" :style=\"{'height':nav_h+'px'}\">\n        <el-menu \n            class=\"side_nav\"\n            :collapse-transition='false'\n            :default-active=\"active\" \n            class=\"el-menu-vertical-demo\" \n            @open=\"handleOpen\" \n            @close=\"handleClose\"\n            @select=\"handleSelect\" \n            :collapse=\"isCollapse\">\n            <!-- <el-menu-item class=\"side_item\" index=\"1\" @click=\"location('/')\">\n                <i class=\"el-icon-house\"></i>\n                <span slot=\"title\">控制面板</span>\n            </el-menu-item> -->\n            <el-menu-item class=\"side_item\" index=\"2\" @click=\"location('/team')\">\n                <i class=\"el-icon-apple\"></i>\n                <span slot=\"title\">团队管理</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"4\" @click=\"location('/environment')\">\n                <i class=\"el-icon-notebook-2\"></i>\n                <span slot=\"title\">环境管理</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"10\" @click=\"location('/commtask')\">\n                <i class=\"el-icon-shopping-bag-2\"></i>\n                <span slot=\"title\">脚本任务</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"3\" @click=\"location('/assets')\">\n                <i class=\"el-icon-monitor\"></i>\n                <span slot=\"title\">资产管理</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"5\" @click=\"location('/application')\">\n                <i class=\"el-icon-notebook-2\"></i>\n                <span slot=\"title\">应用管理</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"6\" @click=\"location('/build')\">\n                <i class=\"el-icon-news\"></i>\n                <span slot=\"title\">应用构建</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"7\" @click=\"location('/logs')\">\n                <i class=\"el-icon-document\"></i>\n                <span slot=\"title\">构建日志</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"8\" @click=\"location('/emails')\">\n                <i class=\"el-icon-message\"></i>\n                <span slot=\"title\">邮件管理</span>\n            </el-menu-item>\n            <el-menu-item class=\"side_item\" index=\"9\" @click=\"location('/user')\">\n                <i class=\"el-icon-user\"></i>\n                <span slot=\"title\">用户管理</span>\n            </el-menu-item>\n        </el-menu>\n    </div>\n</div>\n\n<script>\n    new Vue({\n        el: '#side',\n        data: function () {\n            return {\n                isCollapse: false,\n                nav_h: 500,\n                active: '0',\n                width:179,\n                icon_width:50,\n            }\n        },\n        filters: {\n        },\n        mounted() {\n            const isCollapse =  util.getStorage('local', 'com_side_slide_type')\n            if(isCollapse == 1){\n                this.isCollapse = true;\n                this.icon_width = this.width = 64;\n                setTimeout(()=>{ $('#body_content').css('margin-left', this.width + 1 + 'px')})\n            }\n            const win_h = document.documentElement.clientHeight || document.body.clientHeight;\n            this.nav_h = win_h - 50;\n            const active = util.getStorage('session', 'side-default-active');\n            this.active = active ? active : '0';\n        },\n        methods: {\n            checkSide() {\n                if(this.isCollapse){\n                    this.isCollapse = false;\n                    this.width = 200;\n                    this.icon_width = 50;\n                } else {\n                    this.isCollapse = true;\n                    this.icon_width = this.width = 64;\n                }\n                util.setStorage('local','com_side_slide_type', this.isCollapse?1:0)\n                setTimeout(() => { \n                    $('#body_content').css('margin-left', this.width + 1 + 'px') \n                })\n            },\n            handleOpen(key, keyPath) {\n                console.log(key, keyPath);\n                util.setStorage('session', 'side-default-active', key);\n            },\n            handleClose(key, keyPath) {\n                console.log(key, keyPath);\n            },\n            handleSelect(key, keyPath) {\n                this.active = key;\n                util.setStorage('session', 'side-default-active', key);\n            },\n            location(url) {\n                location.href = url;\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/team.html",
    "content": "<style scoped>\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">团队管理</div>\n        </el-col>\n    </el-row>\n    \n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">团队管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\" @click=\"handleTeam(1)\">新增团队</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\" :row-class-name=\"tableRowClassName\">\n                <el-table-column prop=\"name\" label=\"团队名称\">\n                </el-table-column>\n                <el-table-column prop=\"code\" label=\"团队编码\">\n                </el-table-column>\n                <el-table-column label=\"可用状态\">\n                    <template slot-scope=\"scope\">\n                        <div v-if=\"scope.row.status == 1\" class=\"success\">已启用</div>\n                        <div v-if=\"scope.row.status != 1\" class=\"red\">已禁用</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"操作\">\n                    <template slot-scope=\"scope\">\n                        <div>\n                            <el-button @click=\"handleTeam(2,scope.row)\" type=\"primary\" size=\"small\">编辑</el-button>\n                            <el-button v-if=\"scope.row.status==1?true:false\" @click=\"hanleUse(scope.row)\" type=\"danger\" size=\"small\" plain>禁用</el-button>\n                            <el-button v-if=\"scope.row.status==0?true:false\" @click=\"hanleUse(scope.row)\" type=\"success\" size=\"small\">启用</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" type=\"danger\" size=\"small\">删除</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"common_pages\">\n            <el-pagination background \n                @size-change=\"handleSizeChange\" \n                @current-change=\"handleCurrentChange\"\n                :current-page.sync=\"currentPage\" \n                :page-size=\"pageSize\" \n                layout=\"prev, pager, next, jumper\" \n                :total=\"totalCount\">\n            </el-pagination>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog \n        width=\"500px\"\n        title=\"团队编辑\" \n        :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"com_model_block\">\n                <div class=\"left\">团队名称<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"team.name\" placeholder=\"请输团队名称\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">团队编码<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"team.code\" placeholder=\"以team_开头、例如team_zxhy\" :disabled=\"team.type==2?true:false\"></el-input>\n            </div>\n            <div class=\"com_model_block\">\n                <div class=\"left\">启用状态</div>\n                <el-switch v-model=\"team.status\" active-color=\"#409EFF\"></el-switch>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitTeam\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n</div>\n<script>\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                datalist:[],\n                pageSize:30,\n                totalCount:0,\n                currentPage:1,\n                dialogTableVisible:false,\n                type:1,\n                team:{\n                    type:1,\n                    _id:'',\n                    name:'',\n                    code:'',\n                    status:true,\n                },\n            }\n        },\n        filters: {\n        },\n        mounted() {\n            this.getList();\n        },\n        methods: {\n            getList(){\n                util.ajax({\n                    type:'get',\n                    url:`${config.baseApi}api/v1/team/list?pageNo=${this.currentPage}&pageSize=${this.pageSize}`,\n                    success:data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            submitTeam(){\n                if (!this.team.name) {\n                    popup.alert({ title: '请填写团队名称!' });\n                    return;\n                }\n                if (!this.team.code) {\n                    popup.alert({ title: '请填写团队编码!' });\n                    return;\n                }\n                this.team.status = this.team.status ? 1 : 0;\n                util.ajax({\n                    url: `${config.baseApi}api/v1/team/handle`,\n                    data: this.team,\n                    success: data => {\n                        this.dialogTableVisible = false;\n                        this.getList();\n                        popup.miss({title:'操作成功!'});\n                    }\n                })\n            },\n            handleTeam(type, item) {\n                if (item) item = JSON.parse(JSON.stringify(item));\n                if (item) {\n                    this.team = item;\n                    this.team.status = item.status == 1 ? true : false;\n                } else {\n                    this.team = {\n                        type: 1,\n                        _id: '',\n                        name: '',\n                        code: 'team_'+util.randomString(12),\n                        status: true,\n                    }\n                }\n                this.team.type = type;\n                this.dialogTableVisible = true;\n            },\n            handleSizeChange(val) {\n                console.log(`每页 ${val} 条`);\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            hanleUse(item){\n                let str = item.status == 1 ? '禁用' : '启用';\n                popup.confirm({title: `确定${str}${item.name}团队吗？`, yes: () => {\n                    util.ajax({\n                        url: `${config.baseApi}api/v1/team/setStatus`,\n                        data: {\n                            _id: item._id,\n                            status: item.status == 1 ? 0 : 1,\n                        },\n                        success: data => {\n                            item.status = item.status == 1 ? 0 : 1,\n                            popup.miss({ title: '操作成功!' });\n                        }\n                    })\n                }})\n            },\n            hanleDelete(item){\n                popup.confirm({title: `确定删除${item.name}团队吗？`, yes: () => {\n                    util.ajax({\n                        url: `${config.baseApi}api/v1/team/delete`,\n                        data: {\n                            _id: item._id,\n                        },\n                        success: data => {\n                            this.getList();\n                            popup.miss({ title: '操作成功!' });\n                        }\n                    })\n                }})\n            },\n            tableRowClassName({ row, rowIndex }) {\n                if (row.status != 1) {\n                    return 'disabled-row';\n                }\n                return '';\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app/view/user.html",
    "content": "<style scoped>\n    .el-table .disabled-row {\n        background: oldlace;\n    }\n</style>\n<div class=\"content\" id=\"team\" v-cloak>\n    <el-row>\n        <el-col :span=\"12\">\n            <div class=\"grid-content bg-purple com_title\">用户管理</div>\n        </el-col>\n    </el-row>\n    <div class=\"com_top\">\n        <el-input v-model=\"username\" class=\"inp\" placeholder=\"按用户名称查询\"></el-input>\n        <el-button @click=\"getList\" type=\"primary\">搜索</el-button>\n    </div>\n    <!-- table -->\n    <div class=\"com_table_block mt20\">\n        <div class=\"table_header\">\n            <div class=\"l\">用户管理</div>\n            <div class=\"r\">\n                <el-button type=\"primary\" size=\"medium\" class=\"ml20\" icon=\"el-icon-plus\" @click=\"handleUser(1)\">新增用户</el-button>\n            </div>\n        </div>\n        <div class=\"table_body\">\n            <el-table :data=\"datalist\" style=\"width: 100%\" :row-class-name=\"tableRowClassName\">\n                <el-table-column prop=\"user_name\" label=\"用户名称\">\n                </el-table-column>\n                <el-table-column label=\"可用状态\">\n                    <template slot-scope=\"scope\">\n                        <div v-if=\"scope.row.status == 1\" class=\"success\">已启用</div>\n                        <div v-if=\"scope.row.status != 1\" class=\"red\">已禁用</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"创建时间\">\n                    <template slot-scope=\"scope\">\n                        <div>{{scope.row.create_time | date('-',true)}}</div>\n                    </template>\n                </el-table-column>\n                <el-table-column label=\"操作\">\n                    <template slot-scope=\"scope\">\n                        <div>\n                            <el-button @click=\"handleUser(2,scope.row)\" type=\"primary\" size=\"small\">编辑</el-button>\n                            <el-button @click=\"handleUser(3,scope.row)\" type=\"primary\" size=\"small\" plain>修改密码</el-button>\n                            <el-button v-if=\"scope.row.status==1?true:false\" @click=\"hanleUse(scope.row)\" type=\"danger\"\n                                size=\"small\" plain>禁用</el-button>\n                            <el-button v-if=\"scope.row.status==0?true:false\" @click=\"hanleUse(scope.row)\" type=\"success\"\n                                size=\"small\">启用</el-button>\n                            <el-button @click=\"hanleDelete(scope.row)\" type=\"danger\" size=\"small\">删除</el-button>\n                        </div>\n                    </template>\n                </el-table-column>\n            </el-table>\n        </div>\n        <div class=\"common_pages\">\n            <el-pagination background @current-change=\"handleCurrentChange\"\n                :current-page.sync=\"currentPage\" :page-size=\"pageSize\" layout=\"prev, pager, next, jumper\"\n                :total=\"totalCount\">\n            </el-pagination>\n        </div>\n    </div>\n    <!-- model弹出框 -->\n    <el-dialog width=\"500px\" title=\"用户编辑\" :visible.sync=\"dialogTableVisible\">\n        <div class=\"com_model_main\">\n            <div class=\"com_model_block\" v-if=\"team.type!=3?true:false\">\n                <div class=\"left\">用户名称<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"team.user_name\" placeholder=\"请输用户名称\"></el-input>\n            </div>\n            <div class=\"com_model_block\" v-if=\"team.type!=2?true:false\">\n                <div class=\"left\">用户密码<span class=\"red\">*</span></div>\n                <el-input class=\"inp\" v-model=\"team.pass_word\" placeholder=\"请输用户密码\"></el-input>\n            </div>\n            <div class=\"com_model_block\" v-if=\"team.type!=3?true:false\">\n                <div class=\"left\">启用状态</div>\n                <el-switch v-model=\"team.status\" active-color=\"#409EFF\"></el-switch>\n            </div>\n            <div class=\"btns\">\n                <el-button type=\"primary\" @click=\"submitUser\"> 提交 </el-button>\n            </div>\n        </div>\n    </el-dialog>\n\n</div>\n<script>\n    new Vue({\n        el: '#team',\n        data: function () {\n            return {\n                username:'',\n                datalist: [],\n                pageSize: 30,\n                totalCount: 0,\n                currentPage: 1,\n                dialogTableVisible: false,\n                type: 1,\n                team: {\n                    type: 1,\n                    _id: '',\n                    user_name: '',\n                    pass_word: '',\n                    status: true,\n                },\n            }\n        },\n        filters: {\n            date: Filter.date,\n        },\n        mounted() {\n            this.getList();\n        },\n        methods: {\n            getList() {\n                util.ajax({\n                    type: 'get',\n                    url: `${config.baseApi}api/v1/user/getUserList`,\n                    data:{\n                        pageNo: this.currentPage,\n                        pageSize: this.pageSize,\n                        username:this.username,\n                    },\n                    success: data => {\n                        this.totalCount = data.data.totalNum;\n                        this.datalist = data.data.datalist;\n                    }\n                })\n            },\n            submitUser() {\n                if (!this.team.user_name && this.team.type === 1) {\n                    popup.alert({ title: '请填写用户名称!' });\n                    return;\n                }\n                if (!this.team.pass_word && this.team.type !== 2) {\n                    popup.alert({ title: '请填写用户密码!' });\n                    return;\n                }\n                this.team.status = this.team.status ? 1 : 0;\n                util.ajax({\n                    url: `${config.baseApi}api/v1/user/handle`,\n                    data: this.team,\n                    success: data => {\n                        this.dialogTableVisible = false;\n                        this.getList();\n                        popup.miss({ title: '操作成功!' });\n                    }\n                })\n            },\n            handleUser(type, item) {\n                if (item) item = JSON.parse(JSON.stringify(item));\n                if(type === 1){\n                    this.team = {\n                        type: 1,\n                        _id: '',\n                        user_name: '',\n                        pass_word: '',\n                        status: true,\n                    }\n                } else if(type === 2){\n                    this.team = {\n                        _id: item._id,\n                        user_name: item.user_name,\n                        status: item.status == 1 ? true : false,\n                    };\n                } else if (type === 3) {\n                    this.team = {\n                        _id: item._id,\n                        pass_word: '',\n                        status: item.status == 1 ? true : false,\n                    };\n                }\n                this.team.type = type;\n                this.dialogTableVisible = true;\n            },\n            handleCurrentChange(val) {\n                this.currentPage = val;\n                this.getList();\n            },\n            hanleUse(item) {\n                let str = item.status == 1 ? '禁用' : '启用';\n                popup.confirm({\n                    title: `确定${str}${item.user_name}用户吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/user/setStatus`,\n                            data: {\n                                _id: item._id,\n                                usertoken:item.usertoken || '',\n                                status: item.status == 1 ? 0 : 1,\n                            },\n                            success: data => {\n                                item.status = item.status == 1 ? 0 : 1,\n                                    popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            hanleDelete(item) {\n                popup.confirm({\n                    title: `确定删除${item.user_name}用户吗？`, yes: () => {\n                        util.ajax({\n                            url: `${config.baseApi}api/v1/user/delete`,\n                            data: {\n                                id: item._id,\n                                usertoken: item.usertoken || '',\n                            },\n                            success: data => {\n                                this.getList();\n                                popup.miss({ title: '操作成功!' });\n                            }\n                        })\n                    }\n                })\n            },\n            tableRowClassName({ row, rowIndex }) {\n                if (row.status != 1) {\n                    return 'disabled-row';\n                }\n                return '';\n            },\n        }\n    })\n</script>"
  },
  {
    "path": "app.js",
    "content": "'use strict';\n\nmodule.exports = async () => {\n};\n"
  },
  {
    "path": "appveyor.yml",
    "content": "environment:\n  matrix:\n    - nodejs_version: '8'\n\ninstall:\n  - ps: Install-Product node $env:nodejs_version\n  - npm i npminstall && node_modules\\.bin\\npminstall\n\ntest_script:\n  - node --version\n  - npm --version\n  - npm run test\n\nbuild: off\n"
  },
  {
    "path": "config/config.default.js",
    "content": "'use strict';\nconst path = require('path');\n\nmodule.exports = appInfo => {\n    const config = exports = {};\n\n    // use for cookie sign key, should change to your own and keep security\n    config.keys = appInfo.name + '_1547778980871_4672';\n\n    // add your config here\n    config.middleware = [];\n\n    // 线上环境此处替换为项目根域名 例如 blog.seosiwei.com\n    config.host = '127.0.0.1';\n\n    config.port = 18888;\n\n    // 用于安全校验和回调域名根路径 开发路径域名（必填）\n    config.origin = `http://${config.host}:${config.port}`;\n\n    // 集群配置（一般默认即可）\n    config.cluster = {\n        listen: {\n            port: config.port,\n            hostname: '127.0.0.1',\n        },\n    };\n\n    // 用户密码加盐随机值\n    config.user_pwd_salt_addition = 'ZANEHELLOBEAUTIFUL';\n\n    // 用户登录态持续时间 1 天\n    config.user_login_timeout = 86400;\n\n    config.pageSize = 50;\n\n    config.email = {\n        client: {\n            // service: '163',\n            host: 'smtp.163.com',\n            port: 465,\n            secure: true,\n            auth: {\n                user: 'xxxxxx@163.com',\n                pass: 'xxxxxx',\n            },\n        },\n    };\n\n    exports.io = {\n        init: {\n            transports: [ 'polling', 'websocket' ],\n            allowUpgrades: true,\n        },\n        namespace: {\n            '/socket': {\n                connectionMiddleware: [],\n                packetMiddleware: [],\n            },\n        },\n    };\n\n    config.view = {\n        defaultExtension: '.html',\n        mapping: {\n            '.html': 'ejs',\n        },\n    };\n\n    config.ejs = {\n        layout: 'layout.html',\n    };\n\n    exports.logger = {\n        dir: path.resolve(__dirname, '../buildlogs'),\n    };\n\n    config.bodyParser = {\n        jsonLimit: '1mb',\n        formLimit: '1mb',\n    };\n\n    config.security = {\n        csrf: {\n            enable: true,\n            // 通过 header 传递 CSRF token 的默认字段为 x-csrf-token\n            headerName: 'x-csrf-token',\n        },\n    };\n\n    // redis配置\n    config.redis = {\n        client: {\n            port: 6379, // Redis port\n            host: '127.0.0.1', // Redis host\n            password: '',\n            db: 0,\n        },\n    };\n\n    // mongodb configs\n    exports.mongoose = {\n        url: 'mongodb://127.0.0.1:27017/APubPlat',\n        options: {\n            autoReconnect: true,\n            poolSize: 5,\n        },\n    };\n\n    config.cors = {\n        origin: config.origin,\n        allowMethods: 'GET,PUT,POST,DELETE',\n    };\n\n    config.onerror = {\n        all(err, ctx) {\n            // 统一错误处理\n            ctx.body = JSON.stringify({\n                code: 1001,\n                desc: err.toString().replace('Error: ', ''),\n            });\n            ctx.status = 200;\n            // 统一错误日志记录\n            ctx.logger.info(`统一错误日志：发现了错误${err}`);\n        },\n    };\n\n    return config;\n};\n"
  },
  {
    "path": "config/config.prod.js",
    "content": "'use strict';\n\nmodule.exports = () => {\n    const config = exports = {};\n\n    config.debug = false;\n\n    // 用于安全校验和回调域名根路径 生产线域名（必填）\n    config.origin = 'https://xxx.xxx.com';\n\n    return config;\n};\n"
  },
  {
    "path": "config/plugin.js",
    "content": "'use strict';\n\nconst path = require('path');\n\nexports.ejs = {\n    enable: true,\n    package: 'egg-view-ejs',\n};\n\nexports.routerPlus = {\n    enable: true,\n    package: 'egg-router-plus',\n};\n\nexports.cors = {\n    enable: true,\n    package: 'egg-cors',\n};\n\nexports.mongoose = {\n    enable: true,\n    package: 'egg-mongoose',\n};\n\nexports.email = {\n    enable: true,\n    path: path.join(__dirname, '../lib/plugin/egg-email'),\n};\n\nexports.redis = {\n    enable: true,\n    package: 'egg-redis',\n};\n\nexports.io = {\n    enable: true,\n    package: 'egg-socket.io',\n};\n"
  },
  {
    "path": "lib/plugin/egg-email/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2019 Alibaba Group Holding Limited and other contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "lib/plugin/egg-email/README.md",
    "content": "# egg-email\n\n"
  },
  {
    "path": "lib/plugin/egg-email/app.js",
    "content": "'use strict';\n\nconst email = require('./lib/email');\n\nmodule.exports = app => {\n    email(app);\n};\n\n"
  },
  {
    "path": "lib/plugin/egg-email/config/config.default.js",
    "content": "'use strict';\n\nexports.email = {\n};\n"
  },
  {
    "path": "lib/plugin/egg-email/lib/email.js",
    "content": "'use strict';\n\nconst nodemailer = require('nodemailer');\n// const assert = require('assert');\n\nmodule.exports = app => {\n    app.addSingleton('email', createOneClient);\n};\n\nfunction createOneClient(config, app) {\n    const isHostProt = config.host && config.port && typeof (config.secure) === 'boolean';\n    const service = config.service;\n\n    if (!(isHostProt || service) || !config.auth) return {};\n\n    // assert((isHostProt || service) && config.auth, '[egg-email] host and prot or service are require on config');\n\n    app.coreLogger.info('[egg-email] connecting success!');\n\n    const smtpTransport = nodemailer.createTransport(config);\n\n    return smtpTransport;\n}\n"
  },
  {
    "path": "lib/plugin/egg-email/package.json",
    "content": "{\n  \"name\": \"egg-email\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"app.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"nodemailer\": \"^5.0.0\"\n  },\n  \"eggPlugin\": {\n    \"name\": \"egg-email\"\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"apubplat\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"dependencies\": {\n    \"debug\": \"^4.1.1\",\n    \"egg\": \"^2.2.1\",\n    \"egg-mongoose\": \"^3.1.1\",\n    \"egg-redis\": \"^2.3.2\",\n    \"egg-scripts\": \"^2.5.0\",\n    \"egg-socket.io\": \"^4.1.5\",\n    \"eslint-plugin-html\": \"^5.0.3\",\n    \"excel-export\": \"^0.5.1\",\n    \"md5\": \"^2.2.1\",\n    \"moment\": \"^2.24.0\",\n    \"nodemailer\": \"^5.1.1\",\n    \"qiniu\": \"^7.2.1\",\n    \"ssh2\": \"^0.8.2\",\n    \"ssh2-sftp-client\": \"^2.5.0\",\n    \"trek-captcha\": \"^0.4.0\",\n    \"xterm\": \"^3.12.2\"\n  },\n  \"devDependencies\": {\n    \"autod\": \"^3.0.1\",\n    \"autod-egg\": \"^1.0.0\",\n    \"egg-bin\": \"^4.3.5\",\n    \"egg-ci\": \"^1.8.0\",\n    \"egg-cors\": \"^2.1.2\",\n    \"egg-mock\": \"^3.14.0\",\n    \"egg-router-plus\": \"^1.3.0\",\n    \"egg-view-ejs\": \"^2.0.0\",\n    \"eslint\": \"^5.16.0\",\n    \"eslint-config-egg\": \"^6.0.0\",\n    \"webstorm-disable-index\": \"^1.2.0\"\n  },\n  \"engines\": {\n    \"node\": \">=8.9.0\"\n  },\n  \"scripts\": {\n    \"start\": \"egg-scripts start --port 18888  --daemon --sticky\",\n    \"stop\": \"egg-scripts stop\",\n    \"dev\": \"egg-bin dev --sticky\",\n    \"debug\": \"egg-bin debug\",\n    \"test\": \"npm run lint -- --fix && npm run test-local\",\n    \"test-local\": \"egg-bin test\",\n    \"cov\": \"egg-bin cov\",\n    \"lint\": \"eslint .\",\n    \"ci\": \"npm run lint && npm run cov\",\n    \"autod\": \"autod\"\n  },\n  \"ci\": {\n    \"version\": \"8\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"\"\n  },\n  \"author\": \"\",\n  \"license\": \"MIT\"\n}\n"
  }
]