[
  {
    "path": ".gitattributes",
    "content": "*.js linguist-language=golang\n*.css linguist-language=golang\n*.html linguist-language=golang\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Opening '...'\n2. Click on '....'\n3. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots or logs**\nAdd screenshots or logs to help explain your problem.\n\n**Server (please complete the following information):**\n - OS: [e.g. Centos, Windows]\n - ARCH: [e.g. Amd64, Arm]\n - Tunnel [e.g. TCP, HTTP]\n - Version [e.g. 0.24.0]\n\n**Client (please complete the following information):**\n - OS: [e.g. Centos, Windows]\n - ARCH: [e.g. Amd64, Arm]\n - Tunnel [e.g. TCP, HTTP]\n - Version [e.g. 0.24.0]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  release:\n    types: [published]\n    branches: [ master ]\n\njobs:\n\n  build_assets:\n    \n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v2\n      with:\n        go-version: 1.15\n      id: go\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n    - name: Get dependencies\n      run: |\n        go get -v -t -d ./...\n        if [ -f Gopkg.toml ]; then\n            curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh\n            dep ensure\n        fi\n    - name: Build\n      run: |\n        chmod +x build.assets.sh\n        ./build.assets.sh\n    - name: Upload\n      uses: softprops/action-gh-release@v1\n      if: startsWith(github.ref, 'refs/tags/')\n      with:\n        files: |\n          freebsd_386_client.tar.gz\n          freebsd_386_server.tar.gz\n          freebsd_amd64_client.tar.gz\n          freebsd_amd64_server.tar.gz\n          freebsd_arm_client.tar.gz\n          freebsd_arm_server.tar.gz\n          linux_386_client.tar.gz\n          linux_386_server.tar.gz\n          linux_amd64_client.tar.gz\n          linux_amd64_server.tar.gz\n          linux_arm64_client.tar.gz\n          linux_arm64_server.tar.gz\n          linux_arm_v5_client.tar.gz\n          linux_arm_v6_client.tar.gz\n          linux_arm_v7_client.tar.gz\n          linux_arm_v5_server.tar.gz\n          linux_arm_v6_server.tar.gz\n          linux_arm_v7_server.tar.gz\n          linux_mips64le_client.tar.gz\n          linux_mips64le_server.tar.gz\n          linux_mips64_client.tar.gz\n          linux_mips64_server.tar.gz\n          linux_mipsle_client.tar.gz\n          linux_mipsle_server.tar.gz\n          linux_mips_client.tar.gz\n          linux_mips_server.tar.gz\n          darwin_amd64_client.tar.gz\n          darwin_amd64_server.tar.gz\n          windows_386_client.tar.gz\n          windows_386_server.tar.gz\n          windows_amd64_client.tar.gz\n          windows_amd64_server.tar.gz\n          npc_sdk.tar.gz\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        \n  build_android:\n    \n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n    - name: Build\n      run: |\n        chmod +x build.android.sh\n        docker run --rm -i -w /app -v $(pwd):/app -e ANDROID_HOME=/usr/local/android_sdk -e GOPROXY=direct fyneio/fyne-cross:android-latest /app/build.android.sh\n    - name: Upload\n      uses: softprops/action-gh-release@v1\n      if: startsWith(github.ref, 'refs/tags/')\n      with:\n        files: |\n          android_client.apk\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  build_spk:\n    \n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n    - name: Set env\n      run: echo \"RELEASE_VERSION=${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n    - name: Build\n      run: |\n        git clone https://github.com/cnlh/spksrc.git ~/spksrc\n        mkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/\n        docker run -id --name spksrc --env VERSION=${{ env.RELEASE_VERSION }} -e GOPROXY=direct -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash\n        docker exec spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make'\n        cp ~/spksrc/packages/npc_noarch-all_${{ env.RELEASE_VERSION }}-1.spk ./npc_syno.spk\n    - name: Upload\n      uses: softprops/action-gh-release@v1\n      if: startsWith(github.ref, 'refs/tags/')\n      with:\n        files: |\n          npc_syno.spk\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  build_docker:\n    \n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n    - name: Set env\n      run: echo \"RELEASE_VERSION=${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n    - name: Set up QEMU\n      uses: docker/setup-qemu-action@v1\n    - name: Set up Docker Buildx\n      uses: docker/setup-buildx-action@v1\n    - name: Cache Docker layers\n      uses: actions/cache@v2\n      with:\n        path: /tmp/.buildx-cache\n        key: ${{ runner.os }}-buildx-${{ github.sha }}\n        restore-keys: |\n          ${{ runner.os }}-buildx-\n    - name: Login to DockerHub\n      uses: docker/login-action@v1 \n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n    - name: Build and push nps\n      uses: docker/build-push-action@v2\n      with:\n        context: .\n        file: ./Dockerfile.nps\n        platforms: linux/amd64,linux/arm,linux/arm64\n        push: true\n        tags: |\n          ${{ secrets.DOCKERHUB_USERNAME }}/nps:latest\n          ${{ secrets.DOCKERHUB_USERNAME }}/nps:${{ env.RELEASE_VERSION }}\n    - name: Build and push npc\n      uses: docker/build-push-action@v2\n      with:\n        context: .\n        file: ./Dockerfile.npc\n        platforms: linux/amd64,linux/arm,linux/arm64\n        push: true\n        tags: |\n          ${{ secrets.DOCKERHUB_USERNAME }}/npc:latest\n          ${{ secrets.DOCKERHUB_USERNAME }}/npc:${{ env.RELEASE_VERSION }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\nnps\nnpc\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\n\ngo:\n  - 1.14.x\nservices:\n  - docker\nscript:\n  - GOPROXY=direct go test -v ./cmd/nps/\nos:\n  - linux\nbefore_deploy:\n  - chmod +x ./build.sh && chmod +x ./build.android.sh && ./build.sh\n\ndeploy:\n  provider: releases\n  edge: true\n  token: ${GH_TOKEN}\n  cleanup: false\n  file:\n  - freebsd_386_client.tar.gz\n  - freebsd_386_server.tar.gz\n  - freebsd_amd64_client.tar.gz\n  - freebsd_amd64_server.tar.gz\n  - freebsd_arm_client.tar.gz\n  - freebsd_arm_server.tar.gz\n  - linux_386_client.tar.gz\n  - linux_386_server.tar.gz\n  - linux_amd64_client.tar.gz\n  - linux_amd64_server.tar.gz\n  - linux_arm64_client.tar.gz\n  - linux_arm64_server.tar.gz\n  - linux_arm_v5_client.tar.gz\n  - linux_arm_v6_client.tar.gz\n  - linux_arm_v7_client.tar.gz\n  - linux_arm_v5_server.tar.gz\n  - linux_arm_v6_server.tar.gz\n  - linux_arm_v7_server.tar.gz\n  - linux_mips64le_client.tar.gz\n  - linux_mips64le_server.tar.gz\n  - linux_mips64_client.tar.gz\n  - linux_mips64_server.tar.gz\n  - linux_mipsle_client.tar.gz\n  - linux_mipsle_server.tar.gz\n  - linux_mips_client.tar.gz\n  - linux_mips_server.tar.gz\n  - darwin_amd64_client.tar.gz\n  - darwin_amd64_server.tar.gz\n  - windows_386_client.tar.gz\n  - windows_386_server.tar.gz\n  - windows_amd64_client.tar.gz\n  - windows_amd64_server.tar.gz\n  - npc_syno.spk\n  - npc_sdk.tar.gz\n  - android_client.apk\n  on:\n    tags: true\n    all_branches: true\n"
  },
  {
    "path": "Dockerfile.npc",
    "content": "FROM golang:1.15 as builder\nARG GOPROXY=direct\nWORKDIR /go/src/ehang.io/nps\nCOPY . .\nRUN go get -d -v ./... \nRUN CGO_ENABLED=0 go build -ldflags=\"-w -s -extldflags -static\" ./cmd/npc/npc.go\n\nFROM scratch\nCOPY --from=builder /go/src/ehang.io/nps/npc /\nVOLUME /conf\nENTRYPOINT [\"/npc\"]\n"
  },
  {
    "path": "Dockerfile.nps",
    "content": "FROM golang:1.15 as builder\nARG GOPROXY=direct\nWORKDIR /go/src/ehang.io/nps\nCOPY . .\nRUN go get -d -v ./... \nRUN CGO_ENABLED=0 go build -ldflags=\"-w -s -extldflags -static\" ./cmd/nps/nps.go\n\nFROM scratch\nCOPY --from=builder /go/src/ehang.io/nps/nps /\nCOPY --from=builder /go/src/ehang.io/nps/web /web\nVOLUME /conf\nCMD [\"/nps\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "      GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>."
  },
  {
    "path": "Makefile",
    "content": "SOURCE_FILES?=./...\nTEST_PATTERN?=.\nTEST_OPTIONS?=\n\nexport PATH := ./bin:$(PATH)\nexport GO111MODULE := on\nexport GOPROXY := https://gocenter.io\n\n# Build a beta version of goreleaser\nbuild:\n\tgo build cmd/nps/nps.go\n\tgo build cmd/npc/npc.go\n.PHONY: build\n\n# Install all the build and lint dependencies\nsetup:\n\tcurl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh\n\tcurl -L https://git.io/misspell | sh\n\tgo mod download\n.PHONY: setup\n\n# Run all the tests\ntest:\n\tgo test $(TEST_OPTIONS) -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m\n.PHONY: test\n\n# Run all the tests and opens the coverage report\ncover: test\n\tgo tool cover -html=coverage.txt\n.PHONY: cover\n\n# gofmt and goimports all go files\nfmt:\n\tfind . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s \"$$file\"; goimports -w \"$$file\"; done\n.PHONY: fmt\n\n# Run all the linters\nlint:\n\t# TODO: fix tests and lll issues\n\t./bin/golangci-lint run --tests=false --enable-all --disable=lll ./...\n\t./bin/misspell -error **/*\n.PHONY: lint\n\n# Clean go.mod\ngo-mod-tidy:\n\t@go mod tidy -v\n\t@git diff HEAD\n\t@git diff-index --quiet HEAD\n.PHONY: go-mod-tidy\n\n# Run all the tests and code checks\nci: build test lint go-mod-tidy\n.PHONY: ci\n\n# Generate the static documentation\nstatic:\n\t@hugo --enableGitInfo --source www\n.PHONY: static\n\n# Show to-do items per file.\ntodo:\n\t@grep \\\n\t\t--exclude-dir=vendor \\\n\t\t--exclude-dir=node_modules \\\n\t\t--exclude=Makefile \\\n\t\t--text \\\n\t\t--color \\\n\t\t-nRo -E ' TODO:.*|SkipNow' .\n.PHONY: todo\n\nclean:\n\trm npc nps\n.PHONY: clean\n\n.DEFAULT_GOAL := build\n"
  },
  {
    "path": "README.md",
    "content": "\n# NPS\n![](https://img.shields.io/github/stars/ehang-io/nps.svg)   ![](https://img.shields.io/github/forks/ehang-io/nps.svg)\n[![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n![Release](https://github.com/ehang-io/nps/workflows/Release/badge.svg)\n![GitHub All Releases](https://img.shields.io/github/downloads/ehang-io/nps/total)\n\n[README](https://github.com/ehang-io/nps/blob/master/README.md)|[中文文档](https://github.com/ehang-io/nps/blob/master/README_zh.md)\n\nNPS is a lightweight, high-performance, powerful **intranet penetration** proxy server, with a powerful web management terminal.\n\n\n![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true)\n\n## Feature\n\n- Comprehensive protocol support, compatible with almost all commonly used protocols, such as tcp, udp, http(s), socks5, p2p, http proxy ...\n- Full platform compatibility (linux, windows, macos, Synology, etc.), support installation as a system service simply.\n- Comprehensive control, both client and server control are allowed.\n- Https integration, support to convert backend proxy and web services to https, and support multiple certificates.\n- Just simple configuration on web ui can complete most requirements.\n- Complete information display, such as traffic, system information, real-time bandwidth, client version, etc.\n- Powerful extension functions, everything is available (cache, compression, encryption, traffic limit, bandwidth limit, port reuse, etc.)\n- Domain name resolution has functions such as custom headers, 404 page configuration, host modification, site protection, URL routing, and pan-resolution.\n- Multi-user and user registration support on server.\n\n**Didn't find the feature you want? It doesn't matter, click [Enter the document](https://ehang-io.github.io/nps/) to find it!**\n\n## Quick start\n\n### Installation\n\n> [releases](https://github.com/ehang-io/nps/releases)\n\nDownload the corresponding system version, the server and client are separate.\n\n### Server start\n\nAfter downloading the server compressed package, unzip it, and then enter the unzipped folder.\n\n- execute installation command\n\nFor linux、darwin ```sudo ./nps install```\n\nFor windows, run cmd as administrator and enter the installation directory ```nps.exe install```\n\n- default ports\n\nThe default configuration file of nps use 80，443，8080，8024 ports\n\n80 and 443 ports for host mode default ports\n\n8080 for web management access port\n\n8024 for net bridge port, to communicate between server and client\n\n- start up\n\nFor linux、darwin ```sudo nps start```\n\nFor windows, run cmd as administrator and enter the program directory ```nps.exe start```\n\n```After installation, the windows configuration file is located at C:\\Program Files\\nps, linux or darwin is located at /etc/nps```\n\n**If you don't find it started successfully, you can check the log (Windows log files are located in the current running directory, linux and darwin are located in /var/log/nps.log).**\n\n- Access server IP:web service port (default is 8080).\n- Login with username and password (default is admin/123, must be modified when officially used).\n- Create a client.\n\n### Client connection\n- Click the + sign in front of the client in web management and copy the startup command.\n- Execute the startup command, Linux can be executed directly, Windows will replace ./npc with npc.exe and execute it with cmd.\n\n\nIf you need to register to the system service, you can check [Register to the system service](https://ehang-io.github.io/nps/#/use?id=注册到系统服务)\n\n### Configuration\n- After the client connects, configure the corresponding penetration service in the web.\n- For more advanced usage, see [Complete Documentation](https://ehang-io.github.io/nps/)\n\n## Contribution\n- If you encounter a bug, you can submit it to the dev branch directly.\n- If you encounter a problem, you can feedback through the issue.\n- The project is under development, and there is still a lot of room for improvement. If you can contribute code, please submit PR to the dev branch.\n- If there is feedback on new features, you can feedback via issues or qq group.\n"
  },
  {
    "path": "README_zh.md",
    "content": "\n# nps\n![](https://img.shields.io/github/stars/ehang-io/nps.svg)   ![](https://img.shields.io/github/forks/ehang-io/nps.svg)\n[![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n![Release](https://github.com/ehang-io/nps/workflows/Release/badge.svg)\n![GitHub All Releases](https://img.shields.io/github/downloads/ehang-io/nps/total)\n\n[README](https://github.com/ehang-io/nps/blob/master/README.md)|[中文文档](https://github.com/ehang-io/nps/blob/master/README_zh.md)\n\nnps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**，可支持任何**tcp、udp**上层协议（访问内网网站、本地支付接口调试、ssh访问、远程桌面，内网dns解析等等……），此外还**支持内网http代理、内网socks5代理**、**p2p等**，并带有功能强大的web管理端。\n\n\n## 背景\n![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true)\n\n1. 做微信公众号开发、小程序开发等----> 域名代理模式\n\n2. 想在外网通过ssh连接内网的机器，做云服务器到内网服务器端口的映射，----> tcp代理模式\n\n3. 在非内网环境下使用内网dns，或者需要通过udp访问内网机器等----> udp代理模式\n\n4. 在外网使用HTTP代理访问内网站点----> http代理模式\n\n5. 搭建一个内网穿透ss，在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式\n## 特点\n- 协议支持全面，兼容几乎所有常用协议，例如tcp、udp、http(s)、socks5、p2p、http代理...\n- 全平台兼容(linux、windows、macos、群辉等)，支持一键安装为系统服务\n- 控制全面，同时支持服务端和客户端控制\n- https集成，支持将后端代理和web服务转成https，同时支持多证书\n- 操作简单，只需简单的配置即可在web ui上完成其余操作\n- 展示信息全面，流量、系统信息、即时带宽、客户端版本等\n- 扩展功能强大，该有的都有了（缓存、压缩、加密、流量限制、带宽限制、端口复用等等）\n- 域名解析具备自定义header、404页面配置、host修改、站点保护、URL路由、泛解析等功能\n- 服务端支持多用户和用户注册功能\n\n**没找到你想要的功能？不要紧，点击[进入文档](https://ehang-io.github.io/nps)查找吧**\n## 快速开始\n\n### 安装\n> [releases](https://github.com/ehang-io/nps/releases)\n\n下载对应的系统版本即可，服务端和客户端是单独的\n\n### 服务端启动\n下载完服务器压缩包后，解压，然后进入解压后的文件夹\n\n- 执行安装命令\n\n对于linux|darwin ```sudo ./nps install```\n\n对于windows，管理员身份运行cmd，进入安装目录 ```nps.exe install```\n\n- 默认端口\n\nnps默认配置文件使用了80，443，8080，8024端口\n\n80与443端口为域名解析模式默认端口\n\n8080为web管理访问端口\n\n8024为网桥端口，用于客户端与服务器通信\n\n- 启动\n\n对于linux|darwin ```sudo nps start```\n\n对于windows，管理员身份运行cmd，进入程序目录 ```nps.exe start```\n\n```安装后windows配置文件位于 C:\\Program Files\\nps，linux和darwin位于/etc/nps```\n\n**如果发现没有启动成功，可以查看日志(Windows日志文件位于当前运行目录下，linux和darwin位于/var/log/nps.log)**\n- 访问服务端ip:web服务端口（默认为8080）\n- 使用用户名和密码登陆（默认admin/123，正式使用一定要更改）\n- 创建客户端\n\n### 客户端连接\n- 点击web管理中客户端前的+号，复制启动命令\n- 执行启动命令，linux直接执行即可，windows将./npc换成npc.exe用cmd执行\n\n如果需要注册到系统服务可查看[注册到系统服务](https://ehang-io.github.io/nps/#/use?id=注册到系统服务)\n\n### 配置\n- 客户端连接后，在web中配置对应穿透服务即可\n- 更多高级用法见[完整文档](https://ehang-io.github.io/nps/)\n\n## 贡献\n- 如果遇到bug可以直接提交至dev分支\n- 使用遇到问题可以通过issues反馈\n- 项目处于开发阶段，还有很多待完善的地方，如果可以贡献代码，请提交 PR 至 dev 分支\n- 如果有新的功能特性反馈，可以通过issues或者qq群反馈\n"
  },
  {
    "path": "bridge/bridge.go",
    "content": "package bridge\n\nimport (\n\t\"ehang.io/nps-mux\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/lib/version\"\n\t\"ehang.io/nps/server/connection\"\n\t\"ehang.io/nps/server/tool\"\n\t\"github.com/astaxie/beego\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\ntype Client struct {\n\ttunnel    *nps_mux.Mux\n\tsignal    *conn.Conn\n\tfile      *nps_mux.Mux\n\tVersion   string\n\tretryTime int // it will be add 1 when ping not ok until to 3 will close the client\n}\n\nfunc NewClient(t, f *nps_mux.Mux, s *conn.Conn, vs string) *Client {\n\treturn &Client{\n\t\tsignal:  s,\n\t\ttunnel:  t,\n\t\tfile:    f,\n\t\tVersion: vs,\n\t}\n}\n\ntype Bridge struct {\n\tTunnelPort     int //通信隧道端口\n\tClient         sync.Map\n\tRegister       sync.Map\n\ttunnelType     string //bridge type kcp or tcp\n\tOpenTask       chan *file.Tunnel\n\tCloseTask      chan *file.Tunnel\n\tCloseClient    chan int\n\tSecretChan     chan *conn.Secret\n\tipVerify       bool\n\trunList        sync.Map //map[int]interface{}\n\tdisconnectTime int\n}\n\nfunc NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList sync.Map, disconnectTime int) *Bridge {\n\treturn &Bridge{\n\t\tTunnelPort:     tunnelPort,\n\t\ttunnelType:     tunnelType,\n\t\tOpenTask:       make(chan *file.Tunnel),\n\t\tCloseTask:      make(chan *file.Tunnel),\n\t\tCloseClient:    make(chan int),\n\t\tSecretChan:     make(chan *conn.Secret),\n\t\tipVerify:       ipVerify,\n\t\trunList:        runList,\n\t\tdisconnectTime: disconnectTime,\n\t}\n}\n\nfunc (s *Bridge) StartTunnel() error {\n\tgo s.ping()\n\tif s.tunnelType == \"kcp\" {\n\t\tlogs.Info(\"server start, the bridge type is %s, the bridge port is %d\", s.tunnelType, s.TunnelPort)\n\t\treturn conn.NewKcpListenerAndProcess(beego.AppConfig.String(\"bridge_ip\")+\":\"+beego.AppConfig.String(\"bridge_port\"), func(c net.Conn) {\n\t\t\ts.cliProcess(conn.NewConn(c))\n\t\t})\n\t} else {\n\t\tlistener, err := connection.GetBridgeListener(s.tunnelType)\n\t\tif err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tos.Exit(0)\n\t\t\treturn err\n\t\t}\n\t\tconn.Accept(listener, func(c net.Conn) {\n\t\t\ts.cliProcess(conn.NewConn(c))\n\t\t})\n\t}\n\treturn nil\n}\n\n//get health information form client\nfunc (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {\n\tfor {\n\t\tif info, status, err := c.GetHealthInfo(); err != nil {\n\t\t\tbreak\n\t\t} else if !status { //the status is true , return target to the targetArr\n\t\t\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\t\t\tv := value.(*file.Tunnel)\n\t\t\t\tif v.Client.Id == id && v.Mode == \"tcp\" && strings.Contains(v.Target.TargetStr, info) {\n\t\t\t\t\tv.Lock()\n\t\t\t\t\tif v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {\n\t\t\t\t\t\tv.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, \"\\n\"))\n\t\t\t\t\t}\n\t\t\t\t\tv.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info)\n\t\t\t\t\tif v.HealthRemoveArr == nil {\n\t\t\t\t\t\tv.HealthRemoveArr = make([]string, 0)\n\t\t\t\t\t}\n\t\t\t\t\tv.HealthRemoveArr = append(v.HealthRemoveArr, info)\n\t\t\t\t\tv.Unlock()\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\tfile.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\t\t\tv := value.(*file.Host)\n\t\t\t\tif v.Client.Id == id && strings.Contains(v.Target.TargetStr, info) {\n\t\t\t\t\tv.Lock()\n\t\t\t\t\tif v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {\n\t\t\t\t\t\tv.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, \"\\n\"))\n\t\t\t\t\t}\n\t\t\t\t\tv.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info)\n\t\t\t\t\tif v.HealthRemoveArr == nil {\n\t\t\t\t\t\tv.HealthRemoveArr = make([]string, 0)\n\t\t\t\t\t}\n\t\t\t\t\tv.HealthRemoveArr = append(v.HealthRemoveArr, info)\n\t\t\t\t\tv.Unlock()\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t} else { //the status is false,remove target from the targetArr\n\t\t\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\t\t\tv := value.(*file.Tunnel)\n\t\t\t\tif v.Client.Id == id && v.Mode == \"tcp\" && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) {\n\t\t\t\t\tv.Lock()\n\t\t\t\t\tv.Target.TargetArr = append(v.Target.TargetArr, info)\n\t\t\t\t\tv.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)\n\t\t\t\t\tv.Unlock()\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\n\t\t\tfile.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\t\t\tv := value.(*file.Host)\n\t\t\t\tif v.Client.Id == id && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) {\n\t\t\t\t\tv.Lock()\n\t\t\t\t\tv.Target.TargetArr = append(v.Target.TargetArr, info)\n\t\t\t\t\tv.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)\n\t\t\t\t\tv.Unlock()\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\ts.DelClient(id)\n}\n\n//验证失败，返回错误验证flag，并且关闭连接\nfunc (s *Bridge) verifyError(c *conn.Conn) {\n\tc.Write([]byte(common.VERIFY_EER))\n}\n\nfunc (s *Bridge) verifySuccess(c *conn.Conn) {\n\tc.Write([]byte(common.VERIFY_SUCCESS))\n}\n\nfunc (s *Bridge) cliProcess(c *conn.Conn) {\n\t//read test flag\n\tif _, err := c.GetShortContent(3); err != nil {\n\t\tlogs.Info(\"The client %s connect error\", c.Conn.RemoteAddr(), err.Error())\n\t\treturn\n\t}\n\t//version check\n\tif b, err := c.GetShortLenContent(); err != nil || string(b) != version.GetVersion() {\n\t\tlogs.Info(\"The client %s version does not match\", c.Conn.RemoteAddr())\n\t\tc.Close()\n\t\treturn\n\t}\n\t//version get\n\tvar vs []byte\n\tvar err error\n\tif vs, err = c.GetShortLenContent(); err != nil {\n\t\tlogs.Info(\"get client %s version error\", err.Error())\n\t\tc.Close()\n\t\treturn\n\t}\n\t//write server version to client\n\tc.Write([]byte(crypt.Md5(version.GetVersion())))\n\tc.SetReadDeadlineBySecond(5)\n\tvar buf []byte\n\t//get vKey from client\n\tif buf, err = c.GetShortContent(32); err != nil {\n\t\tc.Close()\n\t\treturn\n\t}\n\t//verify\n\tid, err := file.GetDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String())\n\tif err != nil {\n\t\tlogs.Info(\"Current client connection validation error, close this client:\", c.Conn.RemoteAddr())\n\t\ts.verifyError(c)\n\t\treturn\n\t} else {\n\t\ts.verifySuccess(c)\n\t}\n\tif flag, err := c.ReadFlag(); err == nil {\n\t\ts.typeDeal(flag, c, id, string(vs))\n\t} else {\n\t\tlogs.Warn(err, flag)\n\t}\n\treturn\n}\n\nfunc (s *Bridge) DelClient(id int) {\n\tif v, ok := s.Client.Load(id); ok {\n\t\tif v.(*Client).signal != nil {\n\t\t\tv.(*Client).signal.Close()\n\t\t}\n\t\ts.Client.Delete(id)\n\t\tif file.GetDb().IsPubClient(id) {\n\t\t\treturn\n\t\t}\n\t\tif c, err := file.GetDb().GetClient(id); err == nil {\n\t\t\ts.CloseClient <- c.Id\n\t\t}\n\t}\n}\n\n//use different\nfunc (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {\n\tisPub := file.GetDb().IsPubClient(id)\n\tswitch typeVal {\n\tcase common.WORK_MAIN:\n\t\tif isPub {\n\t\t\tc.Close()\n\t\t\treturn\n\t\t}\n\t\ttcpConn, ok := c.Conn.(*net.TCPConn)\n\t\tif ok {\n\t\t\t// add tcp keep alive option for signal connection\n\t\t\t_ = tcpConn.SetKeepAlive(true)\n\t\t\t_ = tcpConn.SetKeepAlivePeriod(5 * time.Second)\n\t\t}\n\t\t//the vKey connect by another ,close the client of before\n\t\tif v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c, vs)); ok {\n\t\t\tif v.(*Client).signal != nil {\n\t\t\t\tv.(*Client).signal.WriteClose()\n\t\t\t}\n\t\t\tv.(*Client).signal = c\n\t\t\tv.(*Client).Version = vs\n\t\t}\n\t\tgo s.GetHealthFromClient(id, c)\n\t\tlogs.Info(\"clientId %d connection succeeded, address:%s \", id, c.Conn.RemoteAddr())\n\tcase common.WORK_CHAN:\n\t\tmuxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime)\n\t\tif v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil, vs)); ok {\n\t\t\tv.(*Client).tunnel = muxConn\n\t\t}\n\tcase common.WORK_CONFIG:\n\t\tclient, err := file.GetDb().GetClient(id)\n\t\tif err != nil || (!isPub && !client.ConfigConnAllow) {\n\t\t\tc.Close()\n\t\t\treturn\n\t\t}\n\t\tbinary.Write(c, binary.LittleEndian, isPub)\n\t\tgo s.getConfig(c, isPub, client)\n\tcase common.WORK_REGISTER:\n\t\tgo s.register(c)\n\tcase common.WORK_SECRET:\n\t\tif b, err := c.GetShortContent(32); err == nil {\n\t\t\ts.SecretChan <- conn.NewSecret(string(b), c)\n\t\t} else {\n\t\t\tlogs.Error(\"secret error, failed to match the key successfully\")\n\t\t}\n\tcase common.WORK_FILE:\n\t\tmuxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime)\n\t\tif v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil, vs)); ok {\n\t\t\tv.(*Client).file = muxConn\n\t\t}\n\tcase common.WORK_P2P:\n\t\t//read md5 secret\n\t\tif b, err := c.GetShortContent(32); err != nil {\n\t\t\tlogs.Error(\"p2p error,\", err.Error())\n\t\t} else if t := file.GetDb().GetTaskByMd5Password(string(b)); t == nil {\n\t\t\tlogs.Error(\"p2p error, failed to match the key successfully\")\n\t\t} else {\n\t\t\tif v, ok := s.Client.Load(t.Client.Id); !ok {\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\t//向密钥对应的客户端发送与服务端udp建立连接信息，地址，密钥\n\t\t\t\tv.(*Client).signal.Write([]byte(common.NEW_UDP_CONN))\n\t\t\t\tsvrAddr := beego.AppConfig.String(\"p2p_ip\") + \":\" + beego.AppConfig.String(\"p2p_port\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogs.Warn(\"get local udp addr error\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tv.(*Client).signal.WriteLenContent([]byte(svrAddr))\n\t\t\t\tv.(*Client).signal.WriteLenContent(b)\n\t\t\t\t//向该请求者发送建立连接请求,服务器地址\n\t\t\t\tc.WriteLenContent([]byte(svrAddr))\n\t\t\t}\n\t\t}\n\t}\n\tc.SetAlive(s.tunnelType)\n\treturn\n}\n\n//register ip\nfunc (s *Bridge) register(c *conn.Conn) {\n\tvar hour int32\n\tif err := binary.Read(c, binary.LittleEndian, &hour); err == nil {\n\t\ts.Register.Store(common.GetIpByAddr(c.Conn.RemoteAddr().String()), time.Now().Add(time.Hour*time.Duration(hour)))\n\t}\n}\n\nfunc (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) {\n\t//if the proxy type is local\n\tif link.LocalProxy {\n\t\ttarget, err = net.Dial(\"tcp\", link.Host)\n\t\treturn\n\t}\n\tif v, ok := s.Client.Load(clientId); ok {\n\t\t//If ip is restricted to do ip verification\n\t\tif s.ipVerify {\n\t\t\tip := common.GetIpByAddr(link.RemoteAddr)\n\t\t\tif v, ok := s.Register.Load(ip); !ok {\n\t\t\t\treturn nil, errors.New(fmt.Sprintf(\"The ip %s is not in the validation list\", ip))\n\t\t\t} else {\n\t\t\t\tif !v.(time.Time).After(time.Now()) {\n\t\t\t\t\treturn nil, errors.New(fmt.Sprintf(\"The validity of the ip %s has expired\", ip))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tvar tunnel *nps_mux.Mux\n\t\tif t != nil && t.Mode == \"file\" {\n\t\t\ttunnel = v.(*Client).file\n\t\t} else {\n\t\t\ttunnel = v.(*Client).tunnel\n\t\t}\n\t\tif tunnel == nil {\n\t\t\terr = errors.New(\"the client connect error\")\n\t\t\treturn\n\t\t}\n\t\tif target, err = tunnel.NewConn(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif t != nil && t.Mode == \"file\" {\n\t\t\t//TODO if t.mode is file ,not use crypt or compress\n\t\t\tlink.Crypt = false\n\t\t\tlink.Compress = false\n\t\t\treturn\n\t\t}\n\t\tif _, err = conn.NewConn(target).SendInfo(link, \"\"); err != nil {\n\t\t\tlogs.Info(\"new connect error ,the target %s refuse to connect\", link.Host)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\terr = errors.New(fmt.Sprintf(\"the client %d is not connect\", clientId))\n\t}\n\treturn\n}\n\nfunc (s *Bridge) ping() {\n\tticker := time.NewTicker(time.Second * 5)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tarr := make([]int, 0)\n\t\t\ts.Client.Range(func(key, value interface{}) bool {\n\t\t\t\tv := value.(*Client)\n\t\t\t\tif v.tunnel == nil || v.signal == nil {\n\t\t\t\t\tv.retryTime += 1\n\t\t\t\t\tif v.retryTime >= 3 {\n\t\t\t\t\t\tarr = append(arr, key.(int))\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif v.tunnel.IsClose {\n\t\t\t\t\tarr = append(arr, key.(int))\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\tfor _, v := range arr {\n\t\t\t\tlogs.Info(\"the client %d closed\", v)\n\t\t\t\ts.DelClient(v)\n\t\t\t}\n\t\t}\n\t}\n}\n\n//get config and add task from client config\nfunc (s *Bridge) getConfig(c *conn.Conn, isPub bool, client *file.Client) {\n\tvar fail bool\nloop:\n\tfor {\n\t\tflag, err := c.ReadFlag()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tswitch flag {\n\t\tcase common.WORK_STATUS:\n\t\t\tif b, err := c.GetShortContent(32); err != nil {\n\t\t\t\tbreak loop\n\t\t\t} else {\n\t\t\t\tvar str string\n\t\t\t\tid, err := file.GetDb().GetClientIdByVkey(string(b))\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak loop\n\t\t\t\t}\n\t\t\t\tfile.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\t\t\t\tv := value.(*file.Host)\n\t\t\t\t\tif v.Client.Id == id {\n\t\t\t\t\t\tstr += v.Remark + common.CONN_DATA_SEQ\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\t\t\t\tv := value.(*file.Tunnel)\n\t\t\t\t\t//if _, ok := s.runList[v.Id]; ok && v.Client.Id == id {\n\t\t\t\t\tif _, ok := s.runList.Load(v.Id); ok && v.Client.Id == id {\n\t\t\t\t\t\tstr += v.Remark + common.CONN_DATA_SEQ\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\tbinary.Write(c, binary.LittleEndian, int32(len([]byte(str))))\n\t\t\t\tbinary.Write(c, binary.LittleEndian, []byte(str))\n\t\t\t}\n\t\tcase common.NEW_CONF:\n\t\t\tvar err error\n\t\t\tif client, err = c.GetConfigInfo(); err != nil {\n\t\t\t\tfail = true\n\t\t\t\tc.WriteAddFail()\n\t\t\t\tbreak loop\n\t\t\t} else {\n\t\t\t\tif err = file.GetDb().NewClient(client); err != nil {\n\t\t\t\t\tfail = true\n\t\t\t\t\tc.WriteAddFail()\n\t\t\t\t\tbreak loop\n\t\t\t\t}\n\t\t\t\tc.WriteAddOk()\n\t\t\t\tc.Write([]byte(client.VerifyKey))\n\t\t\t\ts.Client.Store(client.Id, NewClient(nil, nil, nil, \"\"))\n\t\t\t}\n\t\tcase common.NEW_HOST:\n\t\t\th, err := c.GetHostInfo()\n\t\t\tif err != nil {\n\t\t\t\tfail = true\n\t\t\t\tc.WriteAddFail()\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t\th.Client = client\n\t\t\tif h.Location == \"\" {\n\t\t\t\th.Location = \"/\"\n\t\t\t}\n\t\t\tif !client.HasHost(h) {\n\t\t\t\tif file.GetDb().IsHostExist(h) {\n\t\t\t\t\tfail = true\n\t\t\t\t\tc.WriteAddFail()\n\t\t\t\t\tbreak loop\n\t\t\t\t} else {\n\t\t\t\t\tfile.GetDb().NewHost(h)\n\t\t\t\t\tc.WriteAddOk()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tc.WriteAddOk()\n\t\t\t}\n\t\tcase common.NEW_TASK:\n\t\t\tif t, err := c.GetTaskInfo(); err != nil {\n\t\t\t\tfail = true\n\t\t\t\tc.WriteAddFail()\n\t\t\t\tbreak loop\n\t\t\t} else {\n\t\t\t\tports := common.GetPorts(t.Ports)\n\t\t\t\ttargets := common.GetPorts(t.Target.TargetStr)\n\t\t\t\tif len(ports) > 1 && (t.Mode == \"tcp\" || t.Mode == \"udp\") && (len(ports) != len(targets)) {\n\t\t\t\t\tfail = true\n\t\t\t\t\tc.WriteAddFail()\n\t\t\t\t\tbreak loop\n\t\t\t\t} else if t.Mode == \"secret\" || t.Mode == \"p2p\" {\n\t\t\t\t\tports = append(ports, 0)\n\t\t\t\t}\n\t\t\t\tif len(ports) == 0 {\n\t\t\t\t\tfail = true\n\t\t\t\t\tc.WriteAddFail()\n\t\t\t\t\tbreak loop\n\t\t\t\t}\n\t\t\t\tfor i := 0; i < len(ports); i++ {\n\t\t\t\t\ttl := new(file.Tunnel)\n\t\t\t\t\ttl.Mode = t.Mode\n\t\t\t\t\ttl.Port = ports[i]\n\t\t\t\t\ttl.ServerIp = t.ServerIp\n\t\t\t\t\tif len(ports) == 1 {\n\t\t\t\t\t\ttl.Target = t.Target\n\t\t\t\t\t\ttl.Remark = t.Remark\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttl.Remark = t.Remark + \"_\" + strconv.Itoa(tl.Port)\n\t\t\t\t\t\ttl.Target = new(file.Target)\n\t\t\t\t\t\tif t.TargetAddr != \"\" {\n\t\t\t\t\t\t\ttl.Target.TargetStr = t.TargetAddr + \":\" + strconv.Itoa(targets[i])\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttl.Target.TargetStr = strconv.Itoa(targets[i])\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\ttl.Id = int(file.GetDb().JsonDb.GetTaskId())\n\t\t\t\t\ttl.Status = true\n\t\t\t\t\ttl.Flow = new(file.Flow)\n\t\t\t\t\ttl.NoStore = true\n\t\t\t\t\ttl.Client = client\n\t\t\t\t\ttl.Password = t.Password\n\t\t\t\t\ttl.LocalPath = t.LocalPath\n\t\t\t\t\ttl.StripPre = t.StripPre\n\t\t\t\t\ttl.MultiAccount = t.MultiAccount\n\t\t\t\t\tif !client.HasTunnel(tl) {\n\t\t\t\t\t\tif err := file.GetDb().NewTask(tl); err != nil {\n\t\t\t\t\t\t\tlogs.Notice(\"Add task error \", err.Error())\n\t\t\t\t\t\t\tfail = true\n\t\t\t\t\t\t\tc.WriteAddFail()\n\t\t\t\t\t\t\tbreak loop\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != \"secret\" && t.Mode != \"p2p\" {\n\t\t\t\t\t\t\tfail = true\n\t\t\t\t\t\t\tc.WriteAddFail()\n\t\t\t\t\t\t\tbreak loop\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts.OpenTask <- tl\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tc.WriteAddOk()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif fail && client != nil {\n\t\ts.DelClient(client.Id)\n\t}\n\tc.Close()\n}\n"
  },
  {
    "path": "build.android.sh",
    "content": "#/bin/bash\n\ncd /go\napt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev -y\ngo get -u fyne.io/fyne/v2/cmd/fyne fyne.io/fyne/v2\n#mkdir -p /go/src/fyne.io\n#cd src/fyne.io\n#git clone https://github.com/fyne-io/fyne.git\n#cd fyne\n#git checkout v1.2.0\n#go install -v ./cmd/fyne\n#fyne package -os android fyne.io/fyne/cmd/hello\necho \"fyne install success\"\nmkdir -p /go/src/ehang.io/nps\ncp -R /app/* /go/src/ehang.io/nps\ncd /go/src/ehang.io/nps\n#go get -u fyne.io/fyne fyne.io/fyne/cmd/fyne\nrm cmd/npc/sdk.go\n#go get -u ./...\n#go mod tidy\n#rm -rf /go/src/golang.org/x/mobile\necho \"tidy success\"\ncd /go/src/ehang.io/nps\ngo mod vendor\ncd vendor\ncp -R * /go/src\ncd ..\nrm -rf vendor\n#rm -rf ~/.cache/*\necho \"vendor success\"\ncd gui/npc\nfyne package -appID org.nps.client -os android -icon ../../docs/logo.png\nmv npc.apk /app/android_client.apk\necho \"android build success\"\n"
  },
  {
    "path": "build.assets.sh",
    "content": "export GOPROXY=direct\n\nsudo apt-get update\nsudo apt-get install gcc-mingw-w64-i686 gcc-multilib\nenv GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -ldflags \"-s -w -extldflags -static -extldflags -static\" -buildmode=c-shared -o npc_sdk.dll cmd/npc/sdk.go\nenv GOOS=linux GOARCH=386 CGO_ENABLED=1 CC=gcc go build -ldflags \"-s -w -extldflags -static -extldflags -static\" -buildmode=c-shared -o npc_sdk.so cmd/npc/sdk.go\ntar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.so npc_sdk.h\n\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\"  ./cmd/npc/npc.go\n\ntar -czvf linux_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf freebsd_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf freebsd_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf freebsd_arm_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_arm_v7_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_arm_v6_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_arm_v5_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_mips64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_mips64le_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_mipsle_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf linux_mips_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf windows_386_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf windows_amd64_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\n\ntar -czvf darwin_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\nCGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_arm_v5_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_arm_v6_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_arm_v7_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_arm64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf freebsd_arm_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf freebsd_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf freebsd_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_mips_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_mips64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_mips64le_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf linux_mipsle_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\n\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf darwin_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf windows_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps.exe\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\n\ntar -czvf windows_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps.exe"
  },
  {
    "path": "build.sh",
    "content": "#/bash/sh\nexport VERSION=0.26.10\nexport GOPROXY=direct\n\nsudo apt-get update\nsudo apt-get install gcc-mingw-w64-i686 gcc-multilib\nenv GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -ldflags \"-s -w -extldflags -static -extldflags -static\" -buildmode=c-shared -o npc_sdk.dll cmd/npc/sdk.go\nenv GOOS=linux GOARCH=386 CGO_ENABLED=1 CC=gcc go build -ldflags \"-s -w -extldflags -static -extldflags -static\" -buildmode=c-shared -o npc_sdk.so cmd/npc/sdk.go\ntar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.so npc_sdk.h\n\nwget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz\ntar -xvf upx-3.95-amd64_linux.tar.xz\ncp upx-3.95-amd64_linux/upx ./\n\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\"  ./cmd/npc/npc.go\ntar -czvf linux_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf freebsd_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf freebsd_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf freebsd_arm_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_arm_v7_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_arm_v6_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_arm_v5_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_mips64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_mips64le_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_mipsle_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf linux_mips_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf windows_386_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf windows_amd64_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf darwin_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/npc/npc.go\ntar -czvf darwin_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_arm_v5_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_arm_v6_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_arm_v7_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_arm64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf freebsd_arm_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf freebsd_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf freebsd_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_mips_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_mips64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_mips64le_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf linux_mipsle_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf darwin_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf darwin_arm64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf windows_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps.exe\n\n\nCGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags \"-s -w -extldflags -static -extldflags -static\" ./cmd/nps/nps.go\ntar -czvf windows_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key  conf/server.pem web/views web/static nps.exe\n\ncurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -\nsudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"\nsudo apt-get update\nsudo apt-get -y -o Dpkg::Options::=\"--force-confnew\" install docker-ce\ndocker --version\ndocker run --rm -i -w /app -v $(pwd):/app -e ANDROID_HOME=/usr/local/android_sdk -e GOPROXY=direct lucor/fyne-cross:android-latest /app/build.android.sh\ngit clone https://github.com/cnlh/spksrc.git ~/spksrc\nmkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/\ndocker run -itd --name spksrc --env VERSION=$VERSION -e GOPROXY=direct -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash\ndocker exec -it spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make arch-x64-7.0'\ncp ~/spksrc/packages/npc_x64-7.0_$VERSION-1.spk ./npc_syno.spk\n\n\necho \"$DOCKER_PASSWORD\" | docker login -u \"$DOCKER_USERNAME\" --password-stdin\nexport DOCKER_CLI_EXPERIMENTAL=enabled\ndocker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d\ndocker buildx create --use --name mybuilder\ndocker buildx build --tag ffdfgdfg/nps:$VERSION --tag ffdfgdfg/nps:latest --output type=image,push=true --file Dockerfile.nps --platform=linux/amd64,linux/arm64,linux/386,linux/arm .\ndocker buildx build --tag ffdfgdfg/npc:$VERSION --tag ffdfgdfg/npc:latest --output type=image,push=true --file Dockerfile.npc --platform=linux/amd64,linux/arm64,linux/386,linux/arm .\n"
  },
  {
    "path": "client/client.go",
    "content": "package client\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"ehang.io/nps-mux\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/xtaci/kcp-go\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/config\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/crypt\"\n)\n\ntype TRPClient struct {\n\tsvrAddr        string\n\tbridgeConnType string\n\tproxyUrl       string\n\tvKey           string\n\tp2pAddr        map[string]string\n\ttunnel         *nps_mux.Mux\n\tsignal         *conn.Conn\n\tticker         *time.Ticker\n\tcnf            *config.Config\n\tdisconnectTime int\n\tonce           sync.Once\n}\n\n//new client\nfunc NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl string, cnf *config.Config, disconnectTime int) *TRPClient {\n\treturn &TRPClient{\n\t\tsvrAddr:        svraddr,\n\t\tp2pAddr:        make(map[string]string, 0),\n\t\tvKey:           vKey,\n\t\tbridgeConnType: bridgeConnType,\n\t\tproxyUrl:       proxyUrl,\n\t\tcnf:            cnf,\n\t\tdisconnectTime: disconnectTime,\n\t\tonce:           sync.Once{},\n\t}\n}\n\nvar NowStatus int\nvar CloseClient bool\n\n//start\nfunc (s *TRPClient) Start() {\n\tCloseClient = false\nretry:\n\tif CloseClient {\n\t\treturn\n\t}\n\tNowStatus = 0\n\tc, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl)\n\tif err != nil {\n\t\tlogs.Error(\"The connection server failed and will be reconnected in five seconds, error\", err.Error())\n\t\ttime.Sleep(time.Second * 5)\n\t\tgoto retry\n\t}\n\tif c == nil {\n\t\tlogs.Error(\"Error data from server, and will be reconnected in five seconds\")\n\t\ttime.Sleep(time.Second * 5)\n\t\tgoto retry\n\t}\n\tlogs.Info(\"Successful connection with server %s\", s.svrAddr)\n\t//monitor the connection\n\tgo s.ping()\n\ts.signal = c\n\t//start a channel connection\n\tgo s.newChan()\n\t//start health check if the it's open\n\tif s.cnf != nil && len(s.cnf.Healths) > 0 {\n\t\tgo heathCheck(s.cnf.Healths, s.signal)\n\t}\n\tNowStatus = 1\n\t//msg connection, eg udp\n\ts.handleMain()\n}\n\n//handle main connection\nfunc (s *TRPClient) handleMain() {\n\tfor {\n\t\tflags, err := s.signal.ReadFlag()\n\t\tif err != nil {\n\t\t\tlogs.Error(\"Accept server data error %s, end this service\", err.Error())\n\t\t\tbreak\n\t\t}\n\t\tswitch flags {\n\t\tcase common.NEW_UDP_CONN:\n\t\t\t//read server udp addr and password\n\t\t\tif lAddr, err := s.signal.GetShortLenContent(); err != nil {\n\t\t\t\tlogs.Warn(err)\n\t\t\t\treturn\n\t\t\t} else if pwd, err := s.signal.GetShortLenContent(); err == nil {\n\t\t\t\tvar localAddr string\n\t\t\t\t//The local port remains unchanged for a certain period of time\n\t\t\t\tif v, ok := s.p2pAddr[crypt.Md5(string(pwd)+strconv.Itoa(int(time.Now().Unix()/100)))]; !ok {\n\t\t\t\t\ttmpConn, err := common.GetLocalUdpAddr()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogs.Error(err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlocalAddr = tmpConn.LocalAddr().String()\n\t\t\t\t} else {\n\t\t\t\t\tlocalAddr = v\n\t\t\t\t}\n\t\t\t\tgo s.newUdpConn(localAddr, string(lAddr), string(pwd))\n\t\t\t}\n\t\t}\n\t}\n\ts.Close()\n}\n\nfunc (s *TRPClient) newUdpConn(localAddr, rAddr string, md5Password string) {\n\tvar localConn net.PacketConn\n\tvar err error\n\tvar remoteAddress string\n\tif remoteAddress, localConn, err = handleP2PUdp(localAddr, rAddr, md5Password, common.WORK_P2P_PROVIDER); err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\tl, err := kcp.ServeConn(nil, 150, 3, localConn)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\tlogs.Trace(\"start local p2p udp listen, local address\", localConn.LocalAddr().String())\n\tfor {\n\t\tudpTunnel, err := l.AcceptKCP()\n\t\tif err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tl.Close()\n\t\t\treturn\n\t\t}\n\t\tif udpTunnel.RemoteAddr().String() == string(remoteAddress) {\n\t\t\tconn.SetUdpSession(udpTunnel)\n\t\t\tlogs.Trace(\"successful connection with client ,address %s\", udpTunnel.RemoteAddr().String())\n\t\t\t//read link info from remote\n\t\t\tconn.Accept(nps_mux.NewMux(udpTunnel, s.bridgeConnType, s.disconnectTime), func(c net.Conn) {\n\t\t\t\tgo s.handleChan(c)\n\t\t\t})\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n//pmux tunnel\nfunc (s *TRPClient) newChan() {\n\ttunnel, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl)\n\tif err != nil {\n\t\tlogs.Error(\"connect to \", s.svrAddr, \"error:\", err)\n\t\treturn\n\t}\n\ts.tunnel = nps_mux.NewMux(tunnel.Conn, s.bridgeConnType, s.disconnectTime)\n\tfor {\n\t\tsrc, err := s.tunnel.Accept()\n\t\tif err != nil {\n\t\t\tlogs.Warn(err)\n\t\t\ts.Close()\n\t\t\tbreak\n\t\t}\n\t\tgo s.handleChan(src)\n\t}\n}\n\nfunc (s *TRPClient) handleChan(src net.Conn) {\n\tlk, err := conn.NewConn(src).GetLinkInfo()\n\tif err != nil || lk == nil {\n\t\tsrc.Close()\n\t\tlogs.Error(\"get connection info from server error \", err)\n\t\treturn\n\t}\n\t//host for target processing\n\tlk.Host = common.FormatAddress(lk.Host)\n\t//if Conn type is http, read the request and log\n\tif lk.ConnType == \"http\" {\n\t\tif targetConn, err := net.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil {\n\t\t\tlogs.Warn(\"connect to %s error %s\", lk.Host, err.Error())\n\t\t\tsrc.Close()\n\t\t} else {\n\t\t\tsrcConn := conn.GetConn(src, lk.Crypt, lk.Compress, nil, false)\n\t\t\tgo func() {\n\t\t\t\tcommon.CopyBuffer(srcConn, targetConn)\n\t\t\t\tsrcConn.Close()\n\t\t\t\ttargetConn.Close()\n\t\t\t}()\n\t\t\tfor {\n\t\t\t\tif r, err := http.ReadRequest(bufio.NewReader(srcConn)); err != nil {\n\t\t\t\t\tsrcConn.Close()\n\t\t\t\t\ttargetConn.Close()\n\t\t\t\t\tbreak\n\t\t\t\t} else {\n\t\t\t\t\tlogs.Trace(\"http request, method %s, host %s, url %s, remote address %s\", r.Method, r.Host, r.URL.Path, r.RemoteAddr)\n\t\t\t\t\tr.Write(targetConn)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\tif lk.ConnType == \"udp5\" {\n\t\tlogs.Trace(\"new %s connection with the goal of %s, remote address:%s\", lk.ConnType, lk.Host, lk.RemoteAddr)\n\t\ts.handleUdp(src)\n\t}\n\t//connect to target if conn type is tcp or udp\n\tif targetConn, err := net.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil {\n\t\tlogs.Warn(\"connect to %s error %s\", lk.Host, err.Error())\n\t\tsrc.Close()\n\t} else {\n\t\tlogs.Trace(\"new %s connection with the goal of %s, remote address:%s\", lk.ConnType, lk.Host, lk.RemoteAddr)\n\t\tconn.CopyWaitGroup(src, targetConn, lk.Crypt, lk.Compress, nil, nil, false, nil)\n\t}\n}\n\nfunc (s *TRPClient) handleUdp(serverConn net.Conn) {\n\t// bind a local udp port\n\tlocal, err := net.ListenUDP(\"udp\", nil)\n\tdefer serverConn.Close()\n\tif err != nil {\n\t\tlogs.Error(\"bind local udp port error \", err.Error())\n\t\treturn\n\t}\n\tdefer local.Close()\n\tgo func() {\n\t\tdefer serverConn.Close()\n\t\tb := common.BufPoolUdp.Get().([]byte)\n\t\tdefer common.BufPoolUdp.Put(b)\n\t\tfor {\n\t\t\tn, raddr, err := local.ReadFrom(b)\n\t\t\tif err != nil {\n\t\t\t\tlogs.Error(\"read data from remote server error\", err.Error())\n\t\t\t}\n\t\t\tbuf := bytes.Buffer{}\n\t\t\tdgram := common.NewUDPDatagram(common.NewUDPHeader(0, 0, common.ToSocksAddr(raddr)), b[:n])\n\t\t\tdgram.Write(&buf)\n\t\t\tb, err := conn.GetLenBytes(buf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\tlogs.Warn(\"get len bytes error\", err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, err := serverConn.Write(b); err != nil {\n\t\t\t\tlogs.Error(\"write data to remote  error\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tb := common.BufPoolUdp.Get().([]byte)\n\tdefer common.BufPoolUdp.Put(b)\n\tfor {\n\t\tn, err := serverConn.Read(b)\n\t\tif err != nil {\n\t\t\tlogs.Error(\"read udp data from server error \", err.Error())\n\t\t\treturn\n\t\t}\n\n\t\tudpData, err := common.ReadUDPDatagram(bytes.NewReader(b[:n]))\n\t\tif err != nil {\n\t\t\tlogs.Error(\"unpack data error\", err.Error())\n\t\t\treturn\n\t\t}\n\t\traddr, err := net.ResolveUDPAddr(\"udp\", udpData.Header.Addr.String())\n\t\tif err != nil {\n\t\t\tlogs.Error(\"build remote addr err\", err.Error())\n\t\t\tcontinue // drop silently\n\t\t}\n\t\t_, err = local.WriteTo(udpData.Data, raddr)\n\t\tif err != nil {\n\t\t\tlogs.Error(\"write data to remote \", raddr.String(), \"error\", err.Error())\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Whether the monitor channel is closed\nfunc (s *TRPClient) ping() {\n\ts.ticker = time.NewTicker(time.Second * 5)\nloop:\n\tfor {\n\t\tselect {\n\t\tcase <-s.ticker.C:\n\t\t\tif s.tunnel != nil && s.tunnel.IsClose {\n\t\t\t\ts.Close()\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *TRPClient) Close() {\n\ts.once.Do(s.closing)\n}\n\nfunc (s *TRPClient) closing() {\n\tCloseClient = true\n\tNowStatus = 0\n\tif s.tunnel != nil {\n\t\t_ = s.tunnel.Close()\n\t}\n\tif s.signal != nil {\n\t\t_ = s.signal.Close()\n\t}\n\tif s.ticker != nil {\n\t\ts.ticker.Stop()\n\t}\n}\n"
  },
  {
    "path": "client/control.go",
    "content": "package client\n\nimport (\n\t\"bufio\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/config\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/version\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/xtaci/kcp-go\"\n\t\"golang.org/x/net/proxy\"\n)\n\nfunc GetTaskStatus(path string) {\n\tcnf, err := config.NewConfig(path)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tc, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tif _, err := c.Write([]byte(common.WORK_STATUS)); err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\t//read now vKey and write to server\n\tif f, err := common.ReadAllFromFile(filepath.Join(common.GetTmpPath(), \"npc_vkey.txt\")); err != nil {\n\t\tlog.Fatalln(err)\n\t} else if _, err := c.Write([]byte(crypt.Md5(string(f)))); err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tvar isPub bool\n\tbinary.Read(c, binary.LittleEndian, &isPub)\n\tif l, err := c.GetLen(); err != nil {\n\t\tlog.Fatalln(err)\n\t} else if b, err := c.GetShortContent(l); err != nil {\n\t\tlog.Fatalln(err)\n\t} else {\n\t\tarr := strings.Split(string(b), common.CONN_DATA_SEQ)\n\t\tfor _, v := range cnf.Hosts {\n\t\t\tif common.InStrArr(arr, v.Remark) {\n\t\t\t\tlog.Println(v.Remark, \"ok\")\n\t\t\t} else {\n\t\t\t\tlog.Println(v.Remark, \"not running\")\n\t\t\t}\n\t\t}\n\t\tfor _, v := range cnf.Tasks {\n\t\t\tports := common.GetPorts(v.Ports)\n\t\t\tif v.Mode == \"secret\" {\n\t\t\t\tports = append(ports, 0)\n\t\t\t}\n\t\t\tfor _, vv := range ports {\n\t\t\t\tvar remark string\n\t\t\t\tif len(ports) > 1 {\n\t\t\t\t\tremark = v.Remark + \"_\" + strconv.Itoa(vv)\n\t\t\t\t} else {\n\t\t\t\t\tremark = v.Remark\n\t\t\t\t}\n\t\t\t\tif common.InStrArr(arr, remark) {\n\t\t\t\t\tlog.Println(remark, \"ok\")\n\t\t\t\t} else {\n\t\t\t\t\tlog.Println(remark, \"not running\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tos.Exit(0)\n}\n\nvar errAdd = errors.New(\"The server returned an error, which port or host may have been occupied or not allowed to open.\")\n\nfunc StartFromFile(path string) {\n\tfirst := true\n\tcnf, err := config.NewConfig(path)\n\tif err != nil || cnf.CommonConfig == nil {\n\t\tlogs.Error(\"Config file %s loading error %s\", path, err.Error())\n\t\tos.Exit(0)\n\t}\n\tlogs.Info(\"Loading configuration file %s successfully\", path)\n\nre:\n\tif first || cnf.CommonConfig.AutoReconnection {\n\t\tif !first {\n\t\t\tlogs.Info(\"Reconnecting...\")\n\t\t\ttime.Sleep(time.Second * 5)\n\t\t}\n\t} else {\n\t\treturn\n\t}\n\tfirst = false\n\tc, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\tgoto re\n\t}\n\tvar isPub bool\n\tbinary.Read(c, binary.LittleEndian, &isPub)\n\n\t// get tmp password\n\tvar b []byte\n\tvkey := cnf.CommonConfig.VKey\n\tif isPub {\n\t\t// send global configuration to server and get status of config setting\n\t\tif _, err := c.SendInfo(cnf.CommonConfig.Client, common.NEW_CONF); err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tgoto re\n\t\t}\n\t\tif !c.GetAddStatus() {\n\t\t\tlogs.Error(\"the web_user may have been occupied!\")\n\t\t\tgoto re\n\t\t}\n\n\t\tif b, err = c.GetShortContent(16); err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tgoto re\n\t\t}\n\t\tvkey = string(b)\n\t}\n\tioutil.WriteFile(filepath.Join(common.GetTmpPath(), \"npc_vkey.txt\"), []byte(vkey), 0600)\n\n\t//send hosts to server\n\tfor _, v := range cnf.Hosts {\n\t\tif _, err := c.SendInfo(v, common.NEW_HOST); err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tgoto re\n\t\t}\n\t\tif !c.GetAddStatus() {\n\t\t\tlogs.Error(errAdd, v.Host)\n\t\t\tgoto re\n\t\t}\n\t}\n\n\t//send  task to server\n\tfor _, v := range cnf.Tasks {\n\t\tif _, err := c.SendInfo(v, common.NEW_TASK); err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tgoto re\n\t\t}\n\t\tif !c.GetAddStatus() {\n\t\t\tlogs.Error(errAdd, v.Ports, v.Remark)\n\t\t\tgoto re\n\t\t}\n\t\tif v.Mode == \"file\" {\n\t\t\t//start local file server\n\t\t\tgo startLocalFileServer(cnf.CommonConfig, v, vkey)\n\t\t}\n\t}\n\n\t//create local server secret or p2p\n\tfor _, v := range cnf.LocalServer {\n\t\tgo StartLocalServer(v, cnf.CommonConfig)\n\t}\n\n\tc.Close()\n\tif cnf.CommonConfig.Client.WebUserName == \"\" || cnf.CommonConfig.Client.WebPassword == \"\" {\n\t\tlogs.Notice(\"web access login username:user password:%s\", vkey)\n\t} else {\n\t\tlogs.Notice(\"web access login username:%s password:%s\", cnf.CommonConfig.Client.WebUserName, cnf.CommonConfig.Client.WebPassword)\n\t}\n\tNewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl, cnf, cnf.CommonConfig.DisconnectTime).Start()\n\tCloseLocalServer()\n\tgoto re\n}\n\n// Create a new connection with the server and verify it\nfunc NewConn(tp string, vkey string, server string, connType string, proxyUrl string) (*conn.Conn, error) {\n\tvar err error\n\tvar connection net.Conn\n\tvar sess *kcp.UDPSession\n\tif tp == \"tcp\" {\n\t\tif proxyUrl != \"\" {\n\t\t\tu, er := url.Parse(proxyUrl)\n\t\t\tif er != nil {\n\t\t\t\treturn nil, er\n\t\t\t}\n\t\t\tswitch u.Scheme {\n\t\t\tcase \"socks5\":\n\t\t\t\tn, er := proxy.FromURL(u, nil)\n\t\t\t\tif er != nil {\n\t\t\t\t\treturn nil, er\n\t\t\t\t}\n\t\t\t\tconnection, err = n.Dial(\"tcp\", server)\n\t\t\tdefault:\n\t\t\t\tconnection, err = NewHttpProxyConn(u, server)\n\t\t\t}\n\t\t} else {\n\t\t\tconnection, err = net.Dial(\"tcp\", server)\n\t\t}\n\t} else {\n\t\tsess, err = kcp.DialWithOptions(server, nil, 10, 3)\n\t\tif err == nil {\n\t\t\tconn.SetUdpSession(sess)\n\t\t\tconnection = sess\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconnection.SetDeadline(time.Now().Add(time.Second * 10))\n\tdefer connection.SetDeadline(time.Time{})\n\tc := conn.NewConn(connection)\n\tif _, err := c.Write([]byte(common.CONN_TEST)); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := c.WriteLenContent([]byte(version.GetVersion())); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := c.WriteLenContent([]byte(version.VERSION)); err != nil {\n\t\treturn nil, err\n\t}\n\tb, err := c.GetShortContent(32)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn nil, err\n\t}\n\tif crypt.Md5(version.GetVersion()) != string(b) {\n\t\tlogs.Error(\"The client does not match the server version. The current core version of the client is\", version.GetVersion())\n\t\treturn nil, err\n\t}\n\tif _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil {\n\t\treturn nil, err\n\t}\n\tif s, err := c.ReadFlag(); err != nil {\n\t\treturn nil, err\n\t} else if s == common.VERIFY_EER {\n\t\treturn nil, errors.New(fmt.Sprintf(\"Validation key %s incorrect\", vkey))\n\t}\n\tif _, err := c.Write([]byte(connType)); err != nil {\n\t\treturn nil, err\n\t}\n\tc.SetAlive(tp)\n\n\treturn c, nil\n}\n\n//http proxy connection\nfunc NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {\n\treq, err := http.NewRequest(\"CONNECT\", \"http://\"+remoteAddr, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpassword, _ := url.User.Password()\n\treq.Header.Set(\"Authorization\", \"Basic \"+basicAuth(strings.Trim(url.User.Username(), \" \"), password))\n\t// we make a http proxy request\n\tproxyConn, err := net.Dial(\"tcp\", url.Host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := req.Write(proxyConn); err != nil {\n\t\treturn nil, err\n\t}\n\tres, err := http.ReadResponse(bufio.NewReader(proxyConn), req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_ = res.Body.Close()\n\tif res.StatusCode != 200 {\n\t\treturn nil, errors.New(\"Proxy error \" + res.Status)\n\t}\n\treturn proxyConn, nil\n}\n\n//get a basic auth string\nfunc basicAuth(username, password string) string {\n\tauth := username + \":\" + password\n\treturn base64.StdEncoding.EncodeToString([]byte(auth))\n}\n\nfunc getRemoteAddressFromServer(rAddr string, localConn *net.UDPConn, md5Password, role string, add int) error {\n\trAddr, err := getNextAddr(rAddr, add)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn err\n\t}\n\taddr, err := net.ResolveUDPAddr(\"udp\", rAddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, err := localConn.WriteTo(common.GetWriteStr(md5Password, role), addr); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc handleP2PUdp(localAddr, rAddr, md5Password, role string) (remoteAddress string, c net.PacketConn, err error) {\n\tlocalConn, err := newUdpConnByAddr(localAddr)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 0)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\terr = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 1)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\terr = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 2)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\tvar remoteAddr1, remoteAddr2, remoteAddr3 string\n\tfor {\n\t\tbuf := make([]byte, 1024)\n\t\tif n, addr, er := localConn.ReadFromUDP(buf); er != nil {\n\t\t\terr = er\n\t\t\treturn\n\t\t} else {\n\t\t\trAddr2, _ := getNextAddr(rAddr, 1)\n\t\t\trAddr3, _ := getNextAddr(rAddr, 2)\n\t\t\tswitch addr.String() {\n\t\t\tcase rAddr:\n\t\t\t\tremoteAddr1 = string(buf[:n])\n\t\t\tcase rAddr2:\n\t\t\t\tremoteAddr2 = string(buf[:n])\n\t\t\tcase rAddr3:\n\t\t\t\tremoteAddr3 = string(buf[:n])\n\t\t\t}\n\t\t}\n\t\tif remoteAddr1 != \"\" && remoteAddr2 != \"\" && remoteAddr3 != \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\tif remoteAddress, err = sendP2PTestMsg(localConn, remoteAddr1, remoteAddr2, remoteAddr3); err != nil {\n\t\treturn\n\t}\n\tc, err = newUdpConnByAddr(localAddr)\n\treturn\n}\n\nfunc sendP2PTestMsg(localConn *net.UDPConn, remoteAddr1, remoteAddr2, remoteAddr3 string) (string, error) {\n\tlogs.Trace(remoteAddr3, remoteAddr2, remoteAddr1)\n\tdefer localConn.Close()\n\tisClose := false\n\tdefer func() { isClose = true }()\n\tinterval, err := getAddrInterval(remoteAddr1, remoteAddr2, remoteAddr3)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tgo func() {\n\t\taddr, err := getNextAddr(remoteAddr3, interval)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tremoteUdpAddr, err := net.ResolveUDPAddr(\"udp\", addr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tlogs.Trace(\"try send test packet to target %s\", addr)\n\t\tticker := time.NewTicker(time.Millisecond * 500)\n\t\tdefer ticker.Stop()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\tif isClose {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\tif interval != 0 {\n\t\tip := common.GetIpByAddr(remoteAddr2)\n\t\tgo func() {\n\t\t\tports := getRandomPortArr(common.GetPortByAddr(remoteAddr3), common.GetPortByAddr(remoteAddr3)+interval*50)\n\t\t\tfor i := 0; i <= 50; i++ {\n\t\t\t\tgo func(port int) {\n\t\t\t\t\ttrueAddress := ip + \":\" + strconv.Itoa(port)\n\t\t\t\t\tlogs.Trace(\"try send test packet to target %s\", trueAddress)\n\t\t\t\t\tremoteUdpAddr, err := net.ResolveUDPAddr(\"udp\", trueAddress)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tticker := time.NewTicker(time.Second * 2)\n\t\t\t\t\tdefer ticker.Stop()\n\t\t\t\t\tfor {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase <-ticker.C:\n\t\t\t\t\t\t\tif isClose {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}(ports[i])\n\t\t\t\ttime.Sleep(time.Millisecond * 10)\n\t\t\t}\n\t\t}()\n\n\t}\n\n\tbuf := make([]byte, 10)\n\tfor {\n\t\tlocalConn.SetReadDeadline(time.Now().Add(time.Second * 10))\n\t\tn, addr, err := localConn.ReadFromUDP(buf)\n\t\tlocalConn.SetReadDeadline(time.Time{})\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tswitch string(buf[:n]) {\n\t\tcase common.WORK_P2P_SUCCESS:\n\t\t\tfor i := 20; i > 0; i-- {\n\t\t\t\tif _, err = localConn.WriteTo([]byte(common.WORK_P2P_END), addr); err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn addr.String(), nil\n\t\tcase common.WORK_P2P_END:\n\t\t\tlogs.Trace(\"Remotely Address %s Reply Packet Successfully Received\", addr.String())\n\t\t\treturn addr.String(), nil\n\t\tcase common.WORK_P2P_CONNECT:\n\t\t\tgo func() {\n\t\t\t\tfor i := 20; i > 0; i-- {\n\t\t\t\t\tlogs.Trace(\"try send receive success packet to target %s\", addr.String())\n\t\t\t\t\tif _, err = localConn.WriteTo([]byte(common.WORK_P2P_SUCCESS), addr); err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t}\n\t\t\t}()\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn \"\", errors.New(\"connect to the target failed, maybe the nat type is not support p2p\")\n}\n\nfunc newUdpConnByAddr(addr string) (*net.UDPConn, error) {\n\tudpAddr, err := net.ResolveUDPAddr(\"udp\", addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tudpConn, err := net.ListenUDP(\"udp\", udpAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn udpConn, nil\n}\n\nfunc getNextAddr(addr string, n int) (string, error) {\n\tarr := strings.Split(addr, \":\")\n\tif len(arr) != 2 {\n\t\treturn \"\", errors.New(fmt.Sprintf(\"the format of %s incorrect\", addr))\n\t}\n\tif p, err := strconv.Atoi(arr[1]); err != nil {\n\t\treturn \"\", err\n\t} else {\n\t\treturn arr[0] + \":\" + strconv.Itoa(p+n), nil\n\t}\n}\n\nfunc getAddrInterval(addr1, addr2, addr3 string) (int, error) {\n\tarr1 := strings.Split(addr1, \":\")\n\tif len(arr1) != 2 {\n\t\treturn 0, errors.New(fmt.Sprintf(\"the format of %s incorrect\", addr1))\n\t}\n\tarr2 := strings.Split(addr2, \":\")\n\tif len(arr2) != 2 {\n\t\treturn 0, errors.New(fmt.Sprintf(\"the format of %s incorrect\", addr2))\n\t}\n\tarr3 := strings.Split(addr3, \":\")\n\tif len(arr3) != 2 {\n\t\treturn 0, errors.New(fmt.Sprintf(\"the format of %s incorrect\", addr3))\n\t}\n\tp1, err := strconv.Atoi(arr1[1])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tp2, err := strconv.Atoi(arr2[1])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tp3, err := strconv.Atoi(arr3[1])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tinterVal := int(math.Floor(math.Min(math.Abs(float64(p3-p2)), math.Abs(float64(p2-p1)))))\n\tif p3-p1 < 0 {\n\t\treturn -interVal, nil\n\t}\n\treturn interVal, nil\n}\n\nfunc getRandomPortArr(min, max int) []int {\n\tif min > max {\n\t\tmin, max = max, min\n\t}\n\taddrAddr := make([]int, max-min+1)\n\tfor i := min; i <= max; i++ {\n\t\taddrAddr[max-i] = i\n\t}\n\trand.Seed(time.Now().UnixNano())\n\tvar r, temp int\n\tfor i := max - min; i > 0; i-- {\n\t\tr = rand.Int() % i\n\t\ttemp = addrAddr[i]\n\t\taddrAddr[i] = addrAddr[r]\n\t\taddrAddr[r] = temp\n\t}\n\treturn addrAddr\n}\n"
  },
  {
    "path": "client/health.go",
    "content": "package client\n\nimport (\n\t\"container/heap\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/lib/sheap\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/pkg/errors\"\n)\n\nvar isStart bool\nvar serverConn *conn.Conn\n\nfunc heathCheck(healths []*file.Health, c *conn.Conn) bool {\n\tserverConn = c\n\tif isStart {\n\t\tfor _, v := range healths {\n\t\t\tv.HealthMap = make(map[string]int)\n\t\t}\n\t\treturn true\n\t}\n\tisStart = true\n\th := &sheap.IntHeap{}\n\tfor _, v := range healths {\n\t\tif v.HealthMaxFail > 0 && v.HealthCheckTimeout > 0 && v.HealthCheckInterval > 0 {\n\t\t\tv.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second)\n\t\t\theap.Push(h, v.HealthNextTime.Unix())\n\t\t\tv.HealthMap = make(map[string]int)\n\t\t}\n\t}\n\tgo session(healths, h)\n\treturn true\n}\n\nfunc session(healths []*file.Health, h *sheap.IntHeap) {\n\tfor {\n\t\tif h.Len() == 0 {\n\t\t\tlogs.Error(\"health check error\")\n\t\t\tbreak\n\t\t}\n\t\trs := heap.Pop(h).(int64) - time.Now().Unix()\n\t\tif rs <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\ttimer := time.NewTimer(time.Duration(rs) * time.Second)\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tfor _, v := range healths {\n\t\t\t\tif v.HealthNextTime.Before(time.Now()) {\n\t\t\t\t\tv.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second)\n\t\t\t\t\t//check\n\t\t\t\t\tgo check(v)\n\t\t\t\t\t//reset time\n\t\t\t\t\theap.Push(h, v.HealthNextTime.Unix())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// work when just one port and many target\nfunc check(t *file.Health) {\n\tarr := strings.Split(t.HealthCheckTarget, \",\")\n\tvar err error\n\tvar rs *http.Response\n\tfor _, v := range arr {\n\t\tif t.HealthCheckType == \"tcp\" {\n\t\t\tvar c net.Conn\n\t\t\tc, err = net.DialTimeout(\"tcp\", v, time.Duration(t.HealthCheckTimeout)*time.Second)\n\t\t\tif err == nil {\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t} else {\n\t\t\tclient := &http.Client{}\n\t\t\tclient.Timeout = time.Duration(t.HealthCheckTimeout) * time.Second\n\t\t\trs, err = client.Get(\"http://\" + v + t.HttpHealthUrl)\n\t\t\tif err == nil && rs.StatusCode != 200 {\n\t\t\t\terr = errors.New(\"status code is not match\")\n\t\t\t}\n\t\t}\n\t\tt.Lock()\n\t\tif err != nil {\n\t\t\tt.HealthMap[v] += 1\n\t\t} else if t.HealthMap[v] >= t.HealthMaxFail {\n\t\t\t//send recovery add\n\t\t\tserverConn.SendHealthInfo(v, \"1\")\n\t\t\tt.HealthMap[v] = 0\n\t\t}\n\n\t\tif t.HealthMap[v] > 0 && t.HealthMap[v]%t.HealthMaxFail == 0 {\n\t\t\t//send fail remove\n\t\t\tserverConn.SendHealthInfo(v, \"0\")\n\t\t}\n\t\tt.Unlock()\n\t}\n}\n"
  },
  {
    "path": "client/local.go",
    "content": "package client\n\nimport (\n\t\"ehang.io/nps-mux\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/config\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server/proxy\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/xtaci/kcp-go\"\n)\n\nvar (\n\tLocalServer   []*net.TCPListener\n\tudpConn       net.Conn\n\tmuxSession    *nps_mux.Mux\n\tfileServer    []*http.Server\n\tp2pNetBridge  *p2pBridge\n\tlock          sync.RWMutex\n\tudpConnStatus bool\n)\n\ntype p2pBridge struct {\n}\n\nfunc (p2pBridge *p2pBridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) {\n\tfor i := 0; muxSession == nil; i++ {\n\t\tif i >= 20 {\n\t\t\terr = errors.New(\"p2pBridge:too many times to get muxSession\")\n\t\t\tlogs.Error(err)\n\t\t\treturn\n\t\t}\n\t\truntime.Gosched() // waiting for another goroutine establish the mux connection\n\t}\n\tnowConn, err := muxSession.NewConn()\n\tif err != nil {\n\t\tudpConn = nil\n\t\treturn nil, err\n\t}\n\tif _, err := conn.NewConn(nowConn).SendInfo(link, \"\"); err != nil {\n\t\tudpConnStatus = false\n\t\treturn nil, err\n\t}\n\treturn nowConn, nil\n}\n\nfunc CloseLocalServer() {\n\tfor _, v := range LocalServer {\n\t\tv.Close()\n\t}\n\tfor _, v := range fileServer {\n\t\tv.Close()\n\t}\n}\n\nfunc startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey string) {\n\tremoteConn, err := NewConn(config.Tp, vkey, config.Server, common.WORK_FILE, config.ProxyUrl)\n\tif err != nil {\n\t\tlogs.Error(\"Local connection server failed \", err.Error())\n\t\treturn\n\t}\n\tsrv := &http.Server{\n\t\tHandler: http.StripPrefix(t.StripPre, http.FileServer(http.Dir(t.LocalPath))),\n\t}\n\tlogs.Info(\"start local file system, local path %s, strip prefix %s ,remote port %s \", t.LocalPath, t.StripPre, t.Ports)\n\tfileServer = append(fileServer, srv)\n\tlistener := nps_mux.NewMux(remoteConn.Conn, common.CONN_TCP, config.DisconnectTime)\n\tlogs.Error(srv.Serve(listener))\n}\n\nfunc StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error {\n\tif l.Type != \"secret\" {\n\t\tgo handleUdpMonitor(config, l)\n\t}\n\ttask := &file.Tunnel{\n\t\tPort:     l.Port,\n\t\tServerIp: \"0.0.0.0\",\n\t\tStatus:   true,\n\t\tClient: &file.Client{\n\t\t\tCnf: &file.Config{\n\t\t\t\tU:        \"\",\n\t\t\t\tP:        \"\",\n\t\t\t\tCompress: config.Client.Cnf.Compress,\n\t\t\t},\n\t\t\tStatus:    true,\n\t\t\tRateLimit: 0,\n\t\t\tFlow:      &file.Flow{},\n\t\t},\n\t\tFlow:   &file.Flow{},\n\t\tTarget: &file.Target{},\n\t}\n\tswitch l.Type {\n\tcase \"p2ps\":\n\t\tlogs.Info(\"successful start-up of local socks5 monitoring, port\", l.Port)\n\t\treturn proxy.NewSock5ModeServer(p2pNetBridge, task).Start()\n\tcase \"p2pt\":\n\t\tlogs.Info(\"successful start-up of local tcp trans monitoring, port\", l.Port)\n\t\treturn proxy.NewTunnelModeServer(proxy.HandleTrans, p2pNetBridge, task).Start()\n\tcase \"p2p\", \"secret\":\n\t\tlistener, err := net.ListenTCP(\"tcp\", &net.TCPAddr{net.ParseIP(\"0.0.0.0\"), l.Port, \"\"})\n\t\tif err != nil {\n\t\t\tlogs.Error(\"local listener startup failed port %d, error %s\", l.Port, err.Error())\n\t\t\treturn err\n\t\t}\n\t\tLocalServer = append(LocalServer, listener)\n\t\tlogs.Info(\"successful start-up of local tcp monitoring, port\", l.Port)\n\t\tconn.Accept(listener, func(c net.Conn) {\n\t\t\tlogs.Trace(\"new %s connection\", l.Type)\n\t\t\tif l.Type == \"secret\" {\n\t\t\t\thandleSecret(c, config, l)\n\t\t\t} else if l.Type == \"p2p\" {\n\t\t\t\thandleP2PVisitor(c, config, l)\n\t\t\t}\n\t\t})\n\t}\n\treturn nil\n}\n\nfunc handleUdpMonitor(config *config.CommonConfig, l *config.LocalServer) {\n\tticker := time.NewTicker(time.Second * 1)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tif !udpConnStatus {\n\t\t\t\tudpConn = nil\n\t\t\t\ttmpConn, err := common.GetLocalUdpAddr()\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogs.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\t\tlogs.Notice(\"try to connect to the server\", i+1)\n\t\t\t\t\tnewUdpConn(tmpConn.LocalAddr().String(), config, l)\n\t\t\t\t\tif udpConn != nil {\n\t\t\t\t\t\tudpConnStatus = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc handleSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) {\n\tremoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_SECRET, config.ProxyUrl)\n\tif err != nil {\n\t\tlogs.Error(\"Local connection server failed \", err.Error())\n\t\treturn\n\t}\n\tif _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil {\n\t\tlogs.Error(\"Local connection server failed \", err.Error())\n\t\treturn\n\t}\n\tconn.CopyWaitGroup(remoteConn.Conn, localTcpConn, false, false, nil, nil, false, nil)\n}\n\nfunc handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) {\n\tif udpConn == nil {\n\t\tlogs.Notice(\"new conn, P2P can not penetrate successfully, traffic will be transferred through the server\")\n\t\thandleSecret(localTcpConn, config, l)\n\t\treturn\n\t}\n\tlogs.Trace(\"start trying to connect with the server\")\n\t//TODO just support compress now because there is not tls file in client packages\n\tlink := conn.NewLink(common.CONN_TCP, l.Target, false, config.Client.Cnf.Compress, localTcpConn.LocalAddr().String(), false)\n\tif target, err := p2pNetBridge.SendLinkInfo(0, link, nil); err != nil {\n\t\tlogs.Error(err)\n\t\tudpConnStatus = false\n\t\treturn\n\t} else {\n\t\tconn.CopyWaitGroup(target, localTcpConn, false, config.Client.Cnf.Compress, nil, nil, false, nil)\n\t}\n}\n\nfunc newUdpConn(localAddr string, config *config.CommonConfig, l *config.LocalServer) {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\tremoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_P2P, config.ProxyUrl)\n\tif err != nil {\n\t\tlogs.Error(\"Local connection server failed \", err.Error())\n\t\treturn\n\t}\n\tif _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil {\n\t\tlogs.Error(\"Local connection server failed \", err.Error())\n\t\treturn\n\t}\n\tvar rAddr []byte\n\t//读取服务端地址、密钥 继续做处理\n\tif rAddr, err = remoteConn.GetShortLenContent(); err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\tvar localConn net.PacketConn\n\tvar remoteAddress string\n\tif remoteAddress, localConn, err = handleP2PUdp(localAddr, string(rAddr), crypt.Md5(l.Password), common.WORK_P2P_VISITOR); err != nil {\n\t\tlogs.Error(err)\n\t\treturn\n\t}\n\tudpTunnel, err := kcp.NewConn(remoteAddress, nil, 150, 3, localConn)\n\tif err != nil || udpTunnel == nil {\n\t\tlogs.Warn(err)\n\t\treturn\n\t}\n\tlogs.Trace(\"successful create a connection with server\", remoteAddress)\n\tconn.SetUdpSession(udpTunnel)\n\tudpConn = udpTunnel\n\tmuxSession = nps_mux.NewMux(udpConn, \"kcp\", config.DisconnectTime)\n\tp2pNetBridge = &p2pBridge{}\n}\n"
  },
  {
    "path": "client/register.go",
    "content": "package client\n\nimport (\n\t\"encoding/binary\"\n\t\"log\"\n\t\"os\"\n\n\t\"ehang.io/nps/lib/common\"\n)\n\nfunc RegisterLocalIp(server string, vKey string, tp string, proxyUrl string, hour int) {\n\tc, err := NewConn(tp, vKey, server, common.WORK_REGISTER, proxyUrl)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tif err := binary.Write(c, binary.LittleEndian, int32(hour)); err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tlog.Printf(\"Successful ip registration for local public network, the validity period is %d hours.\", hour)\n\tos.Exit(0)\n}\n"
  },
  {
    "path": "conf/clients.json",
    "content": ""
  },
  {
    "path": "conf/hosts.json",
    "content": ""
  },
  {
    "path": "conf/multi_account.conf",
    "content": "# key -> user | value -> pwd \nnpc=npc.pwd"
  },
  {
    "path": "conf/npc.conf",
    "content": "[common]\nserver_addr=127.0.0.1:8024\nconn_type=tcp\nvkey=123\nauto_reconnection=true\nmax_conn=1000\nflow_limit=1000\nrate_limit=1000\nbasic_username=11\nbasic_password=3\nweb_username=user\nweb_password=1234\ncrypt=true\ncompress=true\n#pprof_addr=0.0.0.0:9999\ndisconnect_timeout=60\n\n[health_check_test1]\nhealth_check_timeout=1\nhealth_check_max_failed=3\nhealth_check_interval=1\nhealth_http_url=/\nhealth_check_type=http\nhealth_check_target=127.0.0.1:8083,127.0.0.1:8082\n\n[health_check_test2]\nhealth_check_timeout=1\nhealth_check_max_failed=3\nhealth_check_interval=1\nhealth_check_type=tcp\nhealth_check_target=127.0.0.1:8083,127.0.0.1:8082\n\n[web]\nhost=c.o.com\ntarget_addr=127.0.0.1:8083,127.0.0.1:8082\n\n[tcp]\nmode=tcp\ntarget_addr=127.0.0.1:8080\nserver_port=10000\n\n[socks5]\nmode=socks5\nserver_port=19009\nmulti_account=multi_account.conf\n\n[file]\nmode=file\nserver_port=19008\nlocal_path=/Users/liuhe/Downloads\nstrip_pre=/web/\n\n[http]\nmode=httpProxy\nserver_port=19004\n\n[udp]\nmode=udp\nserver_port=12253\ntarget_addr=114.114.114.114:53\n\n[ssh_secret]\nmode=secret\npassword=ssh2\ntarget_addr=123.206.77.88:22\n\n[ssh_p2p]\nmode=p2p\npassword=ssh3\n\n[secret_ssh]\nlocal_port=2001\npassword=ssh2\n\n[p2p_ssh]\nlocal_port=2002\npassword=ssh3\ntarget_addr=123.206.77.88:22"
  },
  {
    "path": "conf/nps.conf",
    "content": "appname = nps\n#Boot mode(dev|pro)\nrunmode = dev\n\n#HTTP(S) proxy port, no startup if empty\nhttp_proxy_ip=0.0.0.0\nhttp_proxy_port=80\nhttps_proxy_port=443\nhttps_just_proxy=true\n#default https certificate setting\nhttps_default_cert_file=conf/server.pem\nhttps_default_key_file=conf/server.key\n\n##bridge\nbridge_type=tcp\nbridge_port=8024\nbridge_ip=0.0.0.0\n\n# Public password, which clients can use to connect to the server\n# After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file.\npublic_vkey=123\n\n#Traffic data persistence interval(minute)\n#Ignorance means no persistence\n#flow_store_interval=1\n\n# log level LevelEmergency->0  LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7\nlog_level=7\n#log_path=nps.log\n\n#Whether to restrict IP access, true or false or ignore\n#ip_limit=true\n\n#p2p\n#p2p_ip=127.0.0.1\n#p2p_port=6000\n\n#web\nweb_host=a.o.com\nweb_username=admin\nweb_password=123\nweb_port = 8080\nweb_ip=0.0.0.0\nweb_base_url=\nweb_open_ssl=false\nweb_cert_file=conf/server.pem\nweb_key_file=conf/server.key\n# if web under proxy use sub path. like http://host/nps need this.\n#web_base_url=/nps\n\n#Web API unauthenticated IP address(the len of auth_crypt_key must be 16)\n#Remove comments if needed\n#auth_key=test\nauth_crypt_key =1234567812345678\n\n#allow_ports=9001-9009,10001,11000-12000\n\n#Web management multi-user login\nallow_user_login=false\nallow_user_register=false\nallow_user_change_username=false\n\n\n#extension\nallow_flow_limit=false\nallow_rate_limit=false\nallow_tunnel_num_limit=false\nallow_local_proxy=false\nallow_connection_num_limit=false\nallow_multi_ip=false\nsystem_info_display=false\n\n#cache\nhttp_cache=false\nhttp_cache_length=100\n\n#get origin ip\nhttp_add_origin_header=false\n\n#pprof debug options\n#pprof_ip=0.0.0.0\n#pprof_port=9999\n\n#client disconnect timeout\ndisconnect_timeout=60\n"
  },
  {
    "path": "conf/server.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7\nLbt3q5Knz8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeK\nFZM0Gp/WhSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0\naAMqJEm88jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQ\npRUWqZeJY4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAe\nyAHsAwmaP8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABAoIBAD40x/RKoEKIyE8B\nD6g0pB1EQo+CePFoN3SYewO1uR4WgtVmtxWVoa7r5BpdZGLe3uCWhpMX7z7W6bGs\nf1LFQOckjkHIfMIfTGfecRjO5Yqu+Pbxtq+gUah+S/plJr3IzdC+SUVNvzBnBMeX\neU3Vmg2UQ2nQ+9GWu8D/c/vDwxx0X8oQ2G8QaxX0tUurlSMNA3M7xySwEvhx54fO\nUrDF3Q4yF48eA4butxVLFWf3cnlY+nR8uYd2vKfmp689/8C6kkfoM9igB78e93sm\nuDM2eRLm4kU5WLl301T42n6AF7w8J0MhLLVOIeLs4l5gZPa3uKvYFmuHQao7e/5R\nU/jHKrECgYEA8alPXuxFSVOvdhIsSN//Frj9CdExVdYmaLkt/2LO4FMnOaWh1xh7\n5iCY1bJT8D9dhfbqRg3qW2oguZD8gu04R8fTRegQ89qmAIwsEYqVf9salR41lZU4\nRc+5yc7O11WIe9Lzu+ONFBFkAh3UFMR4zVZ/JhKIG/P5Srm7SUdKW2cCgYEA5aHo\nx2LR+yKhjkrBzHG3Qrfy1PtlYHjOpYYAKHQcBFuiG08W3CK/vkYl+mhv0uyhT7mn\nq6NDqrpZPRnDlOoEqgRS1X/QWKN6Pgd4HNLIawvp0vK9jYXDPcAXFzVthXCIwFcn\n3a3m4cHiuLdRNOHkydiHQyTOF6eEneN07TDvwvkCgYEApzOd1u9igPmFzQuF2GYi\n+HXFnaU/nUQuDwcQ7EJRIKRn31raPxiRoQesty5LJU6yRp4wOYgnPliPi9Tk4TGA\nXynC4/tMv2vorzhMxVY9Wdke602bhYNZC/RNd3O/aP2lEQdD3Bv04I2nxE8fDb9i\nVbAjCRSJV83WDf2zt1+78sECgYEAzezjRiKdcZu9y0/I+WEk2cUCE/MaF2he0FsZ\nuy1cjp/qAJltQ5452xUnK6cKWNlxU4CHF0mC/hC8xCldliZCZoEYE3PaUBLSJdwm\n35o6tpxpZI3gZJCG5NJlIp/8BkVDrVC7ZHV17hAkFEf4n/bPaB8wNYtE8jt8luaK\nTcarzGkCgYBn2alN0RLN2PHDurraFZB6GuCvh/arEjSCY3SDFQPF10CVjTDV7sx3\neqJkwJ81syTmfJwZIceWbOFGgsuSx37UrQAVlHZSvzeqEg9dA5HqSoOACyidJI7j\nRG2+HB+KpsIZjGgLrEM4i7VOpYUDRdaouIXngFq/t9HNT+MDck5/Lw==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "conf/server.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAPXRSiP0Fs7sMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX\naWRnaXRzIFB0eSBMdGQwHhcNMTcxMTA3MDg1MzQ2WhcNMjcxMTA1MDg1MzQ2WjBF\nMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50\nZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7Lbt3q5Kn\nz8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeKFZM0Gp/W\nhSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0aAMqJEm8\n8jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQpRUWqZeJ\nY4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAeyAHsAwma\nP8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABo4GnMIGkMB0GA1UdDgQWBBQdPc0R\na8alY6Ab7voidkTGaH4PxzB1BgNVHSMEbjBsgBQdPc0Ra8alY6Ab7voidkTGaH4P\nx6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV\nBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPXRSiP0Fs7sMAwGA1UdEwQF\nMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAH1IZNkjuvt2nZPzXsuiVNyCE1vm346z\nnaE0Uzt3aseAN9m/iiB8mLz+ryvWc2aFMX5lTdsHdm2rqmqBCBXeRwTLf4OeHIju\nZQW6makWt6PxANEo6gbdPbQXbS420ssUhnR2irIH1SdI31iikVFPdiS0baRRE/gS\n+440M1jOOOnKm0Qin92ejsshmji/0qaD2+6D5TNw4HmIZaFTBw+kfjxCL6trfeBn\n4fT0RJ121V3G3+AtG5sWQ93B3pCg+jtD+fGKkNSLhphq84bD1Zv7l73QGOoylkEn\nSc0ajTLOXFBb83yRdlgV3Da95jH9rDZ4jSod48m+KemoZTDQw0vSwAU=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "conf/tasks.json",
    "content": ""
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "# nps\n![](https://img.shields.io/github/stars/cnlh/nps.svg)   ![](https://img.shields.io/github/forks/cnlh/nps.svg)\n[![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n[![Build Status](https://travis-ci.org/ehang-io/nps.svg?branch=master)](https://travis-ci.org/cnlh/nps)\n\nnps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**，可支持任何**tcp、udp**上层协议（访问内网网站、本地支付接口调试、ssh访问、远程桌面，内网dns解析等等……），此外还**支持内网http代理、内网socks5代理**、**p2p等**，并带有功能强大的web管理端。\n\n\n## 背景\n![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true)\n\n1. 做微信公众号开发、小程序开发等----> 域名代理模式\n\n\n2. 想在外网通过ssh连接内网的机器，做云服务器到内网服务器端口的映射，----> tcp代理模式\n\n3. 在非内网环境下使用内网dns，或者需要通过udp访问内网机器等----> udp代理模式\n\n4. 在外网使用HTTP代理访问内网站点----> http代理模式\n\n5. 搭建一个内网穿透ss，在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式\n"
  },
  {
    "path": "docs/_coverpage.md",
    "content": "![logo](logo.svg)\n\n# NPS <small>0.26.10</small>\n\n> 一款轻量级、高性能、功能强大的内网穿透代理服务器\n\n- 几乎支持所有协议\n- 支持内网http代理、内网socks5代理、p2p等\n- 简洁但功能强大的WEB管理界面\n- 支持服务端、客户端同时控制\n- 扩展功能强大\n- 全平台兼容，一键注册为服务\n\n\n[GitHub](https://github.com/ehang-io/nps/)\n[开始使用](#nps)\n"
  },
  {
    "path": "docs/_navbar.md",
    "content": "* [![GitHub stars](https://img.shields.io/github/stars/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/stargazers)\n\n* [![GitHub forks](https://img.shields.io/github/forks/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/network)"
  },
  {
    "path": "docs/_sidebar.md",
    "content": "* 入门\n  * [安装](install.md)\n  * [启动](run.md)\n  * [使用示例](example.md)\n* 服务端\n  * [介绍](introduction.md)\n  * [使用](nps_use.md)\n  * [配置文件](server_config.md)\n  * [增强功能](nps_extend.md)\n\n* 客户端\n\n  * [基本使用](use.md)\n  * [增强功能](npc_extend.md)\n\n* 扩展\n\n  * [功能](feature.md)\n  * [说明](description.md)\n  * [web api](api.md)\n  * [sdk](npc_sdk.md)\n\n* 其他\n\n  * [FAQ](faq.md)\n  * [贡献](contribute.md)\n  * [捐助](donate.md)\n  * [致谢](thanks.md)\n  * [交流](discuss.md)\n"
  },
  {
    "path": "docs/api.md",
    "content": "# web api\n\n需要开启请先去掉`nps.conf`中`auth_key`的注释并配置一个合适的密钥\n## webAPI验证说明\n- 采用auth_key的验证方式\n- 在提交的每个请求后面附带两个参数，`auth_key` 和`timestamp`\n\n```\nauth_key的生成方式为：md5(配置文件中的auth_key+当前时间戳)\n```\n\n```\ntimestamp为当前时间戳\n```\n```\ncurl --request POST \\\n  --url http://127.0.0.1:8080/client/list \\\n  --data 'auth_key=2a0000d9229e7dbcf79dd0f5e04bb084&timestamp=1553045344&start=0&limit=10'\n```\n**注意：** 为保证安全，时间戳的有效范围为20秒内，所以每次提交请求必须重新生成。\n\n## 获取服务端时间\n由于服务端与api请求的客户端时间差异不能太大，所以提供了一个可以获取服务端时间的接口\n\n```\nPOST /auth/gettime\n```\n\n## 获取服务端authKey\n\n如果想获取authKey，服务端提供获取authKey的接口\n\n```\nPOST /auth/getauthkey\n```\n将返回加密后的authKey，采用aes cbc加密，请使用与服务端配置文件中cryptKey相同的密钥进行解密\n\n**注意：** nps配置文件中`auth_crypt_key`需为16位\n- 解密密钥长度128\n- 偏移量与密钥相同\n- 补码方式pkcs5padding\n- 解密串编码方式 十六进制\n\n## 详细文档\n- **[详见](webapi.md)** (感谢@avengexyz)\n"
  },
  {
    "path": "docs/contribute.md",
    "content": "# 贡献\n\n- 如果遇到bug可以直接提交至dev分支\n- 使用遇到问题可以通过issues反馈\n- 项目处于开发阶段，还有很多待完善的地方，如果可以贡献代码，请提交 PR 至 dev 分支\n- 如果有新的功能特性反馈，可以通过issues或者qq群反馈\n"
  },
  {
    "path": "docs/description.md",
    "content": "# 说明\n## 获取用户真实ip\n如需使用需要在`nps.conf`中设置`http_add_origin_header=true`\n\n在域名代理模式中，可以通过request请求 header 中的 X-Forwarded-For 和 X-Real-IP 来获取用户真实 IP。\n\n**本代理前会在每一个http(s)请求中添加了这两个 header。**\n\n## 热更新支持\n对于绝大多数配置，在web管理中的修改将实时使用，无需重启客户端或者服务端\n\n## 客户端地址显示\n在web管理中将显示客户端的连接地址\n\n## 流量统计\n可统计显示每个代理使用的流量，由于压缩和加密等原因，会和实际环境中的略有差异\n\n## 当前客户端带宽\n可统计每个客户端当前的带宽，可能和实际有一定差异，仅供参考。\n\n## 客户端与服务端版本对比\n为了程序正常运行，客户端与服务端的核心版本必须一致，否则将导致客户端无法成功连接致服务端。\n\n## Linux系统限制\n默认情况下linux对连接数量有限制，对于性能好的机器完全可以调整内核参数以处理更多的连接。\n`tcp_max_syn_backlog` `somaxconn`\n酌情调整参数，增强网络性能\n\n## web管理保护\n当一个ip连续登陆失败次数超过10次，将在一分钟内禁止该ip再次尝试。\n"
  },
  {
    "path": "docs/discuss.md",
    "content": "# 交流群\n\n![二维码.jpeg](https://i.loli.net/2019/02/15/5c66c32a42074.jpeg)\n"
  },
  {
    "path": "docs/donate.md",
    "content": "# 捐助\n如果您觉得nps对你有帮助，欢迎给予我们一定捐助，也是帮助nps更好的发展。\n\n## 支付宝\n![image](https://github.com/ehang-io/nps/blob/master/image/donation_zfb.png?raw=true)\n## 微信\n![image](https://github.com/ehang-io/nps/blob/master/image/donation_wx.png?raw=true)\n"
  },
  {
    "path": "docs/example.md",
    "content": "# 使用示例\n## 统一准备工作（必做）\n- 开启服务端，假设公网服务器ip为1.1.1.1，配置文件中`bridge_port`为8024，配置文件中`web_port`为8080\n- 访问1.1.1.1:8080\n- 在客户端管理中创建一个客户端，记录下验证密钥\n- 内网客户端运行（windows使用cmd运行加.exe）\n\n```shell\n./npc -server=1.1.1.1:8024 -vkey=客户端的密钥\n```\n**注意：运行服务端后，请确保能从客户端设备上正常访问配置文件中所配置的`bridge_port`端口，telnet，netcat这类的来检查**\n\n## 域名解析\n\n**适用范围：** 小程序开发、微信公众号开发、产品演示\n\n**注意：域名解析模式为http反向代理，不是dns服务器，在web上能够轻松灵活配置**\n\n**假设场景：**\n- 有一个域名proxy.com，有一台公网机器ip为1.1.1.1\n- 两个内网开发站点127.0.0.1:81，127.0.0.1:82\n- 想通过（http|https://）a.proxy.com访问127.0.0.1:81，通过（http|https://）b.proxy.com访问127.0.0.1:82\n\n**使用步骤**\n- 将*.proxy.com解析到公网服务器1.1.1.1\n- 点击刚才创建的客户端的域名管理，添加两条规则规则：1、域名：`a.proxy.com`，内网目标：`127.0.0.1:81`，2、域名：`b.proxy.com`，内网目标：`127.0.0.1:82`\n\n现在访问（http|https://）`a.proxy.com`，`b.proxy.com`即可成功\n\n**https:** 如需使用https请进行相关配置，详见 [使用https](/nps_extend?id=使用https)\n\n## tcp隧道\n\n\n**适用范围：**  ssh、远程桌面等tcp连接场景\n\n**假设场景：**\n 想通过访问公网服务器1.1.1.1的8001端口，连接内网机器10.1.50.101的22端口，实现ssh连接\n\n**使用步骤**\n- 在刚才创建的客户端隧道管理中添加一条tcp隧道，填写监听的端口（8001）、内网目标ip和目标端口（10.1.50.101:22），保存。\n- 访问公网服务器ip（1.1.1.1）,填写的监听端口(8001)，相当于访问内网ip(10.1.50.101):目标端口(22)，例如：`ssh -p 8001 root@1.1.1.1`\n\n## udp隧道\n\n**适用范围：**  内网dns解析等udp连接场景\n\n**假设场景：**\n内网有一台dns（10.1.50.102:53），在非内网环境下想使用该dns，公网服务器为1.1.1.1\n\n**使用步骤**\n- 在刚才创建的客户端的隧道管理中添加一条udp隧道，填写监听的端口（53）、内网目标ip和目标端口（10.1.50.102:53），保存。\n- 修改需要使用的dns地址为1.1.1.1，则相当于使用10.1.50.102作为dns服务器\n\n## socks5代理\n\n\n**适用范围：**  在外网环境下如同使用vpn一样访问内网设备或者资源\n\n**假设场景：**\n想将公网服务器1.1.1.1的8003端口作为socks5代理，达到访问内网任意设备或者资源的效果\n\n**使用步骤**\n- 在刚才创建的客户端隧道管理中添加一条socks5代理，填写监听的端口（8003），保存。\n- 在外网环境的本机配置socks5代理(例如使用proxifier进行全局代理)，ip为公网服务器ip（1.1.1.1），端口为填写的监听端口(8003)，即可畅享内网了\n\n**注意**\n经过socks5代理，当收到socks5数据包时socket已经是accept状态。表现是扫描端口全open，建立连接后短时间关闭。若想同内网表现一致，建议远程连接一台设备。\n\n## http正向代理\n\n**适用范围：**  在外网环境下使用http正向代理访问内网站点\n\n**假设场景：**\n想将公网服务器1.1.1.1的8004端口作为http代理，访问内网网站\n\n**使用步骤**\n\n- 在刚才创建的客户端隧道管理中添加一条http代理，填写监听的端口（8004），保存。\n- 在外网环境的本机配置http代理，ip为公网服务器ip（1.1.1.1），端口为填写的监听端口(8004)，即可访问了\n\n**注意：对于私密代理与p2p，除了统一配置的客户端和服务端，还需要一个客户端作为访问端提供一个端口来访问**\n\n## 私密代理\n\n**适用范围：**  无需占用多余的端口、安全性要求较高可以防止其他人连接的tcp服务，例如ssh。\n\n**假设场景：**\n无需新增多的端口实现访问内网服务器10.1.50.2的22端口\n\n**使用步骤**\n- 在刚才创建的客户端中添加一条私密代理，并设置唯一密钥secrettest和内网目标10.1.50.2:22\n- 在需要连接ssh的机器上以执行命令\n\n```\n./npc -server=1.1.1.1:8024 -vkey=vkey -type=tcp -password=secrettest -local_type=secret\n```\n如需指定本地端口可加参数`-local_port=xx`，默认为2000\n\n**注意：** password为web管理上添加的唯一密钥，具体命令可查看web管理上的命令提示\n\n假设10.1.50.2用户名为root，现在执行`ssh -p 2000 root@127.0.0.1`即可访问ssh\n\n\n## p2p服务\n\n**适用范围：**  大流量传输场景，流量不经过公网服务器，但是由于p2p穿透和nat类型关系较大，不保证100%成功，支持大部分nat类型。[nat类型检测](/npc_extend?id=nat类型检测)\n\n**假设场景：**\n\n想通过访问使用端机器（访问端，也就是本机）的2000端口---->访问到内网机器 10.2.50.2的22端口\n\n**使用步骤**\n- 在`nps.conf`中设置`p2p_ip`（nps服务器ip）和`p2p_port`（nps服务器udp端口）\n> 注：若 `p2p_port` 设置为6000，请在防火墙开放6000~6002(额外添加2个端口)udp端口\n- 在刚才刚才创建的客户端中添加一条p2p代理，并设置唯一密钥p2pssh\n- 在使用端机器（本机）执行命令\n\n```\n./npc -server=1.1.1.1:8024 -vkey=123 -password=p2pssh -target=10.2.50.2:22\n```\n如需指定本地端口可加参数`-local_port=xx`，默认为2000\n\n**注意：** password为web管理上添加的唯一密钥，具体命令可查看web管理上的命令提示\n\n假设内网机器为10.2.50.2的ssh用户名为root，现在在本机上执行`ssh -p 2000 root@127.0.0.1`即可访问机器2的ssh，如果是网站在浏览器访问127.0.0.1:2000端口即可。\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# FAQ\n\n- 服务端无法启动\n```\n服务端默认配置启用了8024，8080，80，443端口，端口冲突无法启动，请修改配置\n```\n- 客户端无法连接服务端\n```\n请检查配置文件中的所有端口是否在安全组，防火墙放行\n请检查vkey是否对应\n请检查版本是否对应\n```\n- 服务端配置文件修改无效\n```\ninstall 之后，Linux 配置文件在 /etc/nps\n```\n- p2p穿透失败 [p2p服务](https://ehang-io.github.io/nps/#/example?id=p2p%e6%9c%8d%e5%8a%a1)\n```\n双方nat类型都是Symmetric Nat一定不成功，建议先查看nat类型。请按照文档操作(标题上有超链接)\n```\n"
  },
  {
    "path": "docs/feature.md",
    "content": "# 扩展功能\n## 缓存支持\n对于web站点来说，一些静态文件往往消耗更大的流量，且在内网穿透中，静态文件还需到客户端获取一次，这将导致更大的流量消耗。nps在域名解析代理中支持对静态文件进行缓存。\n\n即假设一个站点有a.css，nps将只需从npc客户端读取一次该文件，然后把该文件的内容放在内存中，下一次将不再对npc客户端进行请求而直接返回内存中的对应内容。该功能默认是关闭的，如需开启请在`nps.conf`中设置`http_cache=true`，并设置`http_cache_length`（缓存文件的个数，消耗内存，不宜过大，0表示不限制个数）\n\n## 数据压缩支持\n\n由于是内网穿透，内网客户端与服务端之间的隧道存在大量的数据交换，为节省流量，加快传输速度，由此本程序支持SNNAPY形式的压缩。\n\n\n- 所有模式均支持数据压缩\n- 在web管理或客户端配置文件中设置\n\n\n## 加密传输\n\n如果公司内网防火墙对外网访问进行了流量识别与屏蔽，例如禁止了ssh协议等，通过设置 配置文件，将服务端与客户端之间的通信内容加密传输，将会有效防止流量被拦截。\n- nps现在默认每次启动时随机生成tls证书，用于加密传输\n\n\n\n## 站点保护\n域名代理模式所有客户端共用一个http服务端口，在知道域名后任何人都可访问，一些开发或者测试环境需要保密，所以可以设置用户名和密码，nps将通过 Http Basic Auth 来保护，访问时需要输入正确的用户名和密码。\n\n\n- 在web管理或客户端配置文件中设置\n\n## host修改\n\n由于内网站点需要的host可能与公网域名不一致，域名代理支持host修改功能，即修改request的header中的host字段。\n\n**使用方法：在web管理中设置**\n\n## 自定义header\n\n支持对header进行新增或者修改，以配合服务的需要\n\n## 404页面配置\n支持域名解析模式的自定义404页面，修改/web/static/page/error.html中内容即可，暂不支持静态文件等内容\n\n## 流量限制\n\n支持客户端级流量限制，当该客户端入口流量与出口流量达到设定的总量后会拒绝服务\n，域名代理会返回404页面，其他代理会拒绝连接,使用该功能需要在`nps.conf`中设置`allow_flow_limit`，默认是关闭的。\n\n## 带宽限制\n\n支持客户端级带宽限制，带宽计算方式为入口和出口总和，权重均衡,使用该功能需要在`nps.conf`中设置`allow_rate_limit`，默认是关闭的。\n\n## 负载均衡\n本代理支持域名解析模式和tcp代理的负载均衡，在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡\n\n## 端口白名单\n为了防止服务端上的端口被滥用，可在nps.conf中配置allow_ports限制可开启的端口，忽略或者不填表示端口不受限制，格式：\n\n```ini\nallow_ports=9001-9009,10001,11000-12000\n```\n\n## 端口范围映射\n当客户端以配置文件的方式启动时，可以将本地的端口进行范围映射，仅支持tcp和udp模式，例如：\n\n```ini\n[tcp]\nmode=tcp\nserver_port=9001-9009,10001,11000-12000\ntarget_port=8001-8009,10002,13000-14000\n```\n\n逗号分隔，可单个或者范围，注意上下端口的对应关系，无法一一对应将不能成功\n## 端口范围映射到其他机器\n```ini\n[tcp]\nmode=tcp\nserver_port=9001-9009,10001,11000-12000\ntarget_port=8001-8009,10002,13000-14000\ntarget_ip=10.1.50.2\n```\n填写target_ip后则表示映射的该地址机器的端口，忽略则便是映射本地127.0.0.1,仅范围映射时有效\n\n## KCP协议支持\n\n在网络质量非常好的情况下，例如专线，内网，可以开启略微降低延迟。如需使用可在nps.conf中修改`bridge_type`为kcp\n，设置后本代理将开启udp端口（`bridge_port`）\n\n注意：当服务端为kcp时，客户端连接时也需要使用相同配置，无配置文件模式加上参数type=kcp,配置文件模式在配置文件中设置tp=kcp\n\n## 域名泛解析\n支持域名泛解析，例如将host设置为*.proxy.com，a.proxy.com、b.proxy.com等都将解析到同一目标，在web管理中或客户端配置文件中将host设置为此格式即可。\n\n## URL路由\n本代理支持根据URL将同一域名转发到不同的内网服务器，可在web中或客户端配置文件中设置，此参数也可忽略，例如在客户端配置文件中\n\n```ini\n[web1]\nhost=a.proxy.com\ntarget_addr=127.0.0.1:7001\nlocation=/test\n[web2]\nhost=a.proxy.com\ntarget_addr=127.0.0.1:7002\nlocation=/static\n```\n对于`a.proxy.com/test`将转发到`web1`，对于`a.proxy.com/static`将转发到`web2`\n\n## 限制ip访问\n如果将一些危险性高的端口例如ssh端口暴露在公网上，可能会带来一些风险，本代理支持限制ip访问。\n\n**使用方法:** 在配置文件nps.conf中设置`ip_limit`=true，设置后仅通过注册的ip方可访问。\n\n**ip注册**：\n\n**方式一：**\n在需要访问的机器上，运行客户端\n\n```\n./npc register -server=ip:port -vkey=公钥或客户端密钥 time=2\n```\n\ntime为有效小时数，例如time=2，在当前时间后的两小时内，本机公网ip都可以访问nps代理.\n\n**方式二：**\n此外nps的web登陆也可提供验证的功能，成功登陆nps web admin后将自动为登陆的ip注册两小时的允许访问权限。\n\n\n**注意：** 本机公网ip并不是一成不变的，请自行注意有效期的设置，同时同一网络下，多人也可能是在公用同一个公网ip。\n## 客户端最大连接数\n为防止恶意大量长连接，影响服务端程序的稳定性，可以在web或客户端配置文件中为每个客户端设置最大连接数。该功能针对`socks5`、`http正向代理`、`域名代理`、`tcp代理`、`udp代理`、`私密代理`生效,使用该功能需要在`nps.conf`中设置`allow_connection_num_limit=true`，默认是关闭的。\n\n## 客户端最大隧道数限制\nnps支持对客户端的隧道数量进行限制，该功能默认是关闭的，如需开启，请在`nps.conf`中设置`allow_tunnel_num_limit=true`。\n## 端口复用\n在一些严格的网络环境中，对端口的个数等限制较大，nps支持强大端口复用功能。将`bridge_port`、 `http_proxy_port`、 `https_proxy_port` 、`web_port`都设置为同一端口，也能正常使用。\n\n- 使用时将需要复用的端口设置为与`bridge_port`一致即可，将自动识别。\n- 如需将web管理的端口也复用，需要配置`web_host`也就是一个二级域名以便区分\n\n## 多路复用\n\nnps主要通信默认基于多路复用，无需开启。\n\n多路复用基于TCP滑动窗口原理设计，动态计算延迟以及带宽来算出应该往网络管道中打入的流量。\n由于主要通信大多采用TCP协议，并无法探测其实时丢包情况，对于产生丢包重传的情况，采用较大的宽容度，\n5分钟的等待时间，超时将会关闭当前隧道连接并重新建立，这将会抛弃当前所有的连接。\n在Linux上，可以通过调节内核参数来适应不同应用场景。\n\n对于需求大带宽又有一定的丢包的场景，可以保持默认参数不变，尽可能少抛弃连接\n高并发下可根据[Linux系统限制](## Linux系统限制) 调整\n\n对于延迟敏感而又有一定丢包的场景，可以适当调整TCP重传次数\n`tcp_syn_retries`, `tcp_retries1`, `tcp_retries2`\n高并发同上\nnps会在系统主动关闭连接的时候拿到报错，进而重新建立隧道连接\n\n## 环境变量渲染\nnpc支持环境变量渲染以适应在某些特殊场景下的要求。\n\n**在无配置文件启动模式下：**\n设置环境变量\n```\nexport NPC_SERVER_ADDR=1.1.1.1:8024\nexport NPC_SERVER_VKEY=xxxxx\n```\n直接执行./npc即可运行\n\n**在配置文件启动模式下：**\n```ini\n[common]\nserver_addr={{.NPC_SERVER_ADDR}}\nconn_type=tcp\nvkey={{.NPC_SERVER_VKEY}}\nauto_reconnection=true\n[web]\nhost={{.NPC_WEB_HOST}}\ntarget_addr={{.NPC_WEB_TARGET}}\n```\n在配置文件中填入相应的环境变量名称，npc将自动进行渲染配置文件替换环境变量\n\n## 健康检查\n\n当客户端以配置文件模式启动时，支持多节点的健康检查。配置示例如下\n\n```ini\n[health_check_test1]\nhealth_check_timeout=1\nhealth_check_max_failed=3\nhealth_check_interval=1\nhealth_http_url=/\nhealth_check_type=http\nhealth_check_target=127.0.0.1:8083,127.0.0.1:8082\n\n[health_check_test2]\nhealth_check_timeout=1\nhealth_check_max_failed=3\nhealth_check_interval=1\nhealth_check_type=tcp\nhealth_check_target=127.0.0.1:8083,127.0.0.1:8082\n```\n**health关键词必须在开头存在**\n\n第一种是http模式，也就是以get的方式请求目标+url，返回状态码为200表示成功\n\n第一种是tcp模式，也就是以tcp的方式与目标建立连接，能成功建立连接表示成功\n\n如果失败次数超过`health_check_max_failed`，nps则会移除该npc下的所有该目标，如果失败后目标重新上线，nps将自动将目标重新加入。\n\n项 | 含义\n---|---\nhealth_check_timeout |  健康检查超时时间\nhealth_check_max_failed |  健康检查允许失败次数\nhealth_check_interval |  健康检查间隔\nhealth_check_type |  健康检查类型\nhealth_check_target |  健康检查目标，多个以逗号（,）分隔\nhealth_check_type |  健康检查类型\nhealth_http_url |  健康检查url，仅http模式适用\n\n## 日志输出\n\n日志输出级别\n\n**对于npc：**\n```\n-log_level=0~7 -log_path=npc.log\n```\n```\nLevelEmergency->0  LevelAlert->1\n\nLevelCritical->2 LevelError->3\n\nLevelWarning->4 LevelNotice->5\n\nLevelInformational->6 LevelDebug->7\n```\n默认为全输出,级别为0到7\n\n**对于nps：**\n\n在`nps.conf`中设置相关配置即可\n\n## pprof性能分析与调试\n\n可在服务端与客户端配置中开启pprof端口，用于性能分析与调试，注释或留空相应参数为关闭。\n\n默认为关闭状态\n\n## 自定义客户端超时检测断开时间\n\n客户端与服务端间会间隔5s相互发送延迟测量包，这个时间间隔不可修改。\n可修改延迟测量包丢包的次数，默认为60也就是5分钟都收不到一个延迟测量回包，则会断开客户端连接。\n值得注意的是需要客户端的socket关闭，才会进行重连，也就是当客户端无法收到服务端的fin包时，只有客户端自行关闭socket才行。\n也就是假如服务端设置为较低值，而客户端设置较高值，而此时服务端断开连接而客户端无法收到服务端的fin包，客户端也会继续等着直到触发客户端的超时设置。\n\n在`nps.conf`或`npc.conf`中设置`disconnect_timeout`即可，客户端还可附带`-disconnect_timeout=60`参数启动\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"/>\n    <meta name=\"description\" content=\"Description\">\n    <meta name=\"viewport\"\n          content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n    <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/lib/themes/vue.css\">\n</head>\n<body>\n<div id=\"app\"></div>\n<script src=\"//unpkg.com/docsify-edit-on-github/index.js\"></script>\n\n<script>\n\n    window.$docsify = {\n        name: '',\n        repo: '',\n        loadSidebar: true,\n        coverpage: true,\n        loadNavbar: true,\n        subMaxLevel: 2,\n        maxLevel: 4,\n        search: {\n            noData: \"没有结果\",\n            paths: 'auto',\n            placeholder: \"搜索\",\n            hideOtherSidebarContent: true, // whether or not to hide other sidebar content\n        },\n        plugins: [\n            EditOnGithubPlugin.create(\"https://github.com/ehang-io/nps/tree/master/docs/\", \"\", \"在github上编辑\"),\n        ]\n\n    }\n</script>\n<script src=\"//unpkg.com/docsify/lib/docsify.min.js\"></script>\n<script src=\"//unpkg.com/docsify/lib/plugins/search.min.js\"></script>\n<script src=\"//unpkg.com/docsify-copy-code\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "docs/install.md",
    "content": "# 安装\n## 安装包安装\n [releases](https://github.com/ehang-io/nps/releases)\n\n下载对应的系统版本即可，服务端和客户端是单独的\n\n## 源码安装\n- 安装源码\n```go get -u ehang.io/nps```\n- 编译\n\n服务端```go build cmd/nps/nps.go```\n\n客户端```go build cmd/npc/npc.go```\n\n## docker安装\n> [server](https://hub.docker.com/r/ffdfgdfg/nps)\n> [client](https://hub.docker.com/r/ffdfgdfg/npc)\n"
  },
  {
    "path": "docs/introduction.md",
    "content": "![image](https://github.com/ehang-io/nps/blob/master/image/web2.png?raw=true)\n# 介绍\n\n可在网页上配置和管理各个tcp、udp隧道、内网站点代理，http、https解析等，功能强大，操作方便。\n"
  },
  {
    "path": "docs/npc_extend.md",
    "content": "# 增强功能\n## nat类型检测\n```\n ./npc nat -stun_addr=stun.stunprotocol.org:3478\n```\n如果p2p双方都是Symmetric Nat，肯定不能成功，其他组合都有较大成功率。`stun_addr`可以指定stun服务器地址。\n## 状态检查\n```\n ./npc status -config=npc配置文件路径\n```\n## 重载配置文件\n```\n ./npc restart -config=npc配置文件路径\n```\n\n## 通过代理连接nps\n有时候运行npc的内网机器无法直接访问外网，此时可以可以通过socks5代理连接nps\n\n对于配置文件方式启动,设置\n```ini\n[common]\nproxy_url=socks5://111:222@127.0.0.1:8024\n```\n对于无配置文件模式,加上参数\n\n```\n-proxy=socks5://111:222@127.0.0.1:8024\n```\n支持socks5和http两种模式\n\n即socks5://username:password@ip:port\n\n或http://username:password@ip:port\n\n## 群晖支持\n可在releases中下载spk群晖套件，例如`npc_x64-6.1_0.19.0-1.spk`\n"
  },
  {
    "path": "docs/npc_sdk.md",
    "content": "# npc sdk文档\n\n```\n命令行模式启动客户端\n从v0.26.10开始，此函数会阻塞，直到客户端退出返回，请自行管理是否重连\np0->连接地址\np1->vkey\np2->连接类型（tcp or udp）\np3->连接代理\n\nextern GoInt StartClientByVerifyKey(char* p0, char* p1, char* p2, char* p3);\n\n查看当前启动的客户端状态，在线为1，离线为0\nextern GoInt GetClientStatus();\n\n关闭客户端\nextern void CloseClient();\n\n获取当前客户端版本\nextern char* Version();\n\n获取日志，实时更新\nextern char* Logs();\n```\n"
  },
  {
    "path": "docs/nps_extend.md",
    "content": "# 增强功能\n## 使用https\n\n**方式一：** 类似于nginx实现https的处理\n\n在配置文件中将https_proxy_port设置为443或者其他你想配置的端口，将`https_just_proxy`设置为false，nps 重启后，在web管理界面，域名新增或修改界面中修改域名证书和密钥。\n\n**此外：** 可以在`nps.conf`中设置一个默认的https配置，当遇到未在web中设置https证书的域名解析时，将自动使用默认证书，另还有一种情况就是对于某些请求的clienthello不携带sni扩展信息，nps也将自动使用默认证书\n\n\n**方式二：** 在内网对应服务器上设置https\n\n在`nps.conf`中将`https_just_proxy`设置为true，并且打开`https_proxy_port`端口，然后nps将直接转发https请求到内网服务器上，由内网服务器进行https处理\n\n## 与nginx配合\n\n有时候我们还需要在云服务器上运行nginx来保证静态文件缓存等，本代理可和nginx配合使用，在配置文件中将httpProxyPort设置为非80端口，并在nginx中配置代理，例如httpProxyPort为8010时\n```\nserver {\n    listen 80;\n    server_name *.proxy.com;\n    location / {\n        proxy_set_header Host  $http_host;\n        proxy_pass http://127.0.0.1:8010;\n    }\n}\n```\n如需使用https也可在nginx监听443端口并配置ssl，并将本代理的httpsProxyPort设置为空关闭https即可，例如httpProxyPort为8020时\n\n```\nserver {\n    listen 443;\n    server_name *.proxy.com;\n    ssl on;\n    ssl_certificate  certificate.crt;\n    ssl_certificate_key private.key;\n    ssl_session_timeout 5m;\n    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;\n    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n    ssl_prefer_server_ciphers on;\n    location / {\n        proxy_set_header Host  $http_host;\n        proxy_pass http://127.0.0.1:8020;\n    }\n}\n```\n## web管理使用https\n如果web管理需要使用https，可以在配置文件`nps.conf`中设置`web_open_ssl=true`，并配置`web_cert_file`和`web_key_file`\n## web使用Caddy代理\n\n如果将web配置到Caddy代理,实现子路径访问nps,可以这样配置.\n\n假设我们想通过 `http://caddy_ip:caddy_port/nps` 来访问后台, Caddyfile 这样配置:\n\n```Caddyfile\ncaddy_ip:caddy_port/nps {\n  ##server_ip 为 nps 服务器IP\n  ##web_port 为 nps 后台端口\n  proxy / http://server_ip:web_port/nps {\n\ttransparent\n  }\n}\n```\n\nnps.conf 修改 `web_base_url` 为 `/nps` 即可\n```\nweb_base_url=/nps\n```\n\n\n## 关闭代理\n\n如需关闭http代理可在配置文件中将http_proxy_port设置为空，如需关闭https代理可在配置文件中将https_proxy_port设置为空。\n\n## 流量数据持久化\n服务端支持将流量数据持久化，默认情况下是关闭的，如果有需求可以设置`nps.conf`中的`flow_store_interval`参数，单位为分钟\n\n**注意：** nps不会持久化通过公钥连接的客户端\n## 系统信息显示\nnps服务端支持在web上显示和统计服务器的相关信息，但默认一些统计图表是关闭的，如需开启请在`nps.conf`中设置`system_info_display=true`\n\n## 自定义客户端连接密钥\nweb上可以自定义客户端连接的密钥，但是必须具有唯一性\n## 关闭公钥访问\n可以将`nps.conf`中的`public_vkey`设置为空或者删除\n\n## 关闭web管理\n可以将`nps.conf`中的`web_port`设置为空或者删除\n\n## 服务端多用户登陆\n如果将`nps.conf`中的`allow_user_login`设置为true,服务端web将支持多用户登陆，登陆用户名为user，默认密码为每个客户端的验证密钥，登陆后可以进入客户端编辑修改web登陆的用户名和密码，默认该功能是关闭的。\n\n## 用户注册功能\nnps服务端支持用户注册功能，可将`nps.conf`中的`allow_user_register`设置为true，开启后登陆页将会有有注册功能，\n\n## 监听指定ip\n\nnps支持每个隧道监听不同的服务端端口,在`nps.conf`中设置`allow_multi_ip=true`后，可在web中控制，或者npc配置文件中(可忽略，默认为0.0.0.0)\n```ini\nserver_ip=xxx\n```\n## 代理到服务端本地\n在使用nps监听80或者443端口时，默认是将所有的请求都会转发到内网上，但有时候我们的nps服务器的上一些服务也需要使用这两个端口，nps提供类似于`nginx` `proxy_pass` 的功能，支持将代理到服务器本地，该功能支持域名解析，tcp、udp隧道，默认关闭。\n\n**即：** 假设在nps的vps服务器上有一个服务使用5000端口，这时候nps占用了80端口和443，我们想能使用一个域名通过http(s)访问到5000的服务。\n\n**使用方式：** 在`nps.conf`中设置`allow_local_proxy=true`，然后在web上设置想转发的隧道或者域名然后选择转发到本地选项即可成功。\n"
  },
  {
    "path": "docs/nps_use.md",
    "content": "# 使用\n**提示：使用web模式时，服务端执行文件必须在项目根目录，否则无法正确加载配置文件**\n\n## web管理\n\n进入web界面，公网ip:web界面端口（默认8080），密码默认为123\n\n进入web管理界面，有详细的说明\n\n## 服务端配置文件重载\n对于linux、darwin\n```shell\n sudo nps reload\n```\n对于windows\n```shell\n nps.exe reload\n```\n**说明：** 仅支持部分配置重载，例如`allow_user_login` `auth_crypt_key` `auth_key` `web_username` `web_password` 等，未来将支持更多\n\n\n## 服务端停止或重启\n对于linux、darwin\n```shell\n sudo nps stop|restart\n```\n对于windows\n```shell\n nps.exe stop|restart\n```\n## 服务端更新\n请首先执行 `sudo nps stop` 或者 `nps.exe stop` 停止运行，然后\n\n对于linux\n```shell\n sudo nps-update update\n```\n对于windows\n```shell\n nps-update.exe update\n```\n\n更新完成后，执行执行 `sudo nps start` 或者 `nps.exe start` 重新运行即可完成升级\n\n如果无法更新成功，可以直接自行下载releases压缩包然后覆盖原有的nps二进制文件和web目录\n\n注意：`nps install` 之后的 nps 不在原位置，请使用 `whereis nps` 查找具体目录覆盖 nps 二进制文件\n"
  },
  {
    "path": "docs/run.md",
    "content": "# 启动\n## 服务端\n下载完服务器压缩包后，解压，然后进入解压后的文件夹\n\n- 执行安装命令\n\n对于linux|darwin ```sudo ./nps install```\n\n对于windows，管理员身份运行cmd，进入安装目录 ```nps.exe install```\n\n- 启动\n\n对于linux|darwin ```sudo nps start```\n\n对于windows，管理员身份运行cmd，进入程序目录 ```nps.exe start```\n\n```安装后windows配置文件位于 C:\\Program Files\\nps，linux和darwin位于/etc/nps```\n\n停止和重启可用，stop和restart\n\n**如果发现没有启动成功，可以使用`nps(.exe) stop`，然后运行`nps.(exe)`运行调试，或查看日志**(Windows日志文件位于当前运行目录下，linux和darwin位于/var/log/nps.log)\n- 访问服务端ip:web服务端口（默认为8080）\n- 使用用户名和密码登陆（默认admin/123，正式使用一定要更改）\n- 创建客户端\n\n## 客户端\n- 下载客户端安装包并解压，进入到解压目录\n- 点击web管理中客户端前的+号，复制启动命令\n- 执行启动命令，linux直接执行即可，windows将./npc换成npc.exe用**cmd执行**\n\n如果使用`powershell`运行，**请将ip括起来！**\n\n如果需要注册到系统服务可查看[注册到系统服务](/use?id=注册到系统服务)\n\n## 版本检查\n- 对客户端以及服务的均可以使用参数`-version`打印版本\n- `nps -version`或`./nps -version`\n- `npc -version`或`./npc -version`\n\n## 配置\n- 客户端连接后，在web中配置对应穿透服务即可\n- 可以查看[使用示例](/example)\n"
  },
  {
    "path": "docs/server_config.md",
    "content": "# 服务端配置文件\n- /etc/nps/conf/nps.conf\n\n名称 | 含义\n---|---\nweb_port | web管理端口\nweb_password | web界面管理密码\nweb_username | web界面管理账号\nweb_base_url | web管理主路径,用于将web管理置于代理子路径后面\nbridge_port  | 服务端客户端通信端口\nhttps_proxy_port | 域名代理https代理监听端口\nhttp_proxy_port | 域名代理http代理监听端口\nauth_key|web api密钥\nbridge_type|客户端与服务端连接方式kcp或tcp\npublic_vkey|客户端以配置文件模式启动时的密钥，设置为空表示关闭客户端配置文件连接模式\nip_limit|是否限制ip访问，true或false或忽略\nflow_store_interval|服务端流量数据持久化间隔，单位分钟，忽略表示不持久化\nlog_level|日志输出级别\nauth_crypt_key | 获取服务端authKey时的aes加密密钥，16位\np2p_ip| 服务端Ip，使用p2p模式必填\np2p_port|p2p模式开启的udp端口\npprof_ip|debug pprof 服务端ip\npprof_port|debug pprof 端口\ndisconnect_timeout|客户端连接超时，单位 5s，默认值 60，即 300s = 5mins\n"
  },
  {
    "path": "docs/thanks.md",
    "content": "Thanks [jetbrains](https://www.jetbrains.com/?from=nps) for providing development tools for nps\n\n<html>\n<img src=\"https://ftp.bmp.ovh/imgs/2019/12/6435398b0c7402b1.png\" width=\"300\"  align=center />\n</html>\n"
  },
  {
    "path": "docs/use.md",
    "content": "# 基本使用\n## 无配置文件模式\n此模式的各种配置在服务端web管理中完成,客户端除运行一条命令外无需任何其他设置\n```\n ./npc -server=ip:port -vkey=web界面中显示的密钥\n```\n## 注册到系统服务(开机启动、守护进程)\n对于linux、darwin\n- 注册：`sudo ./npc install 其他参数（例如-server=xx -vkey=xx或者-config=xxx）`\n- 启动：`sudo npc start`\n- 停止：`sudo npc stop`\n- 如果需要更换命令内容需要先卸载`./npc uninstall`，再重新注册\n\n对于windows，使用管理员身份运行cmd\n\n- 注册：`npc.exe install 其他参数（例如-server=xx -vkey=xx或者-config=xxx）`\n- 启动：`npc.exe start`\n- 停止：`npc.exe stop`\n- 如果需要更换命令内容需要先卸载`npc.exe uninstall`，再重新注册\n- 如果需要当客户端退出时自动重启客户端，请按照如图所示配置\n![image](https://github.com/ehang-io/nps/blob/master/docs/windows_client_service_configuration.png?raw=true)\n\n注册到服务后，日志文件windows位于当前目录下，linux和darwin位于/var/log/npc.log\n\n## 客户端更新\n首先进入到对于的客户端二进制文件目录\n\n请首先执行`sudo npc stop`或者`npc.exe stop`停止运行，然后\n\n对于linux\n```shell\n sudo npc-update update\n```\n对于windows\n```shell\nnpc-update.exe update\n```\n\n更新完成后，执行执行`sudo npc start`或者`npc.exe start`重新运行即可完成升级\n\n如果无法更新成功，可以直接自行下载releases压缩包然后覆盖原有的npc二进制文件\n\n## 配置文件模式\n此模式使用nps的公钥或者客户端私钥验证，各种配置在客户端完成，同时服务端web也可以进行管理\n```\n ./npc -config=npc配置文件路径\n```\n## 配置文件说明\n[示例配置文件](https://github.com/ehang-io/nps/tree/master/conf/npc.conf)\n#### 全局配置\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nconn_type=tcp\nvkey=123\nusername=111\npassword=222\ncompress=true\ncrypt=true\nrate_limit=10000\nflow_limit=100\nremark=test\nmax_conn=10\n#pprof_addr=0.0.0.0:9999\n```\n项 | 含义\n---|---\nserver_addr | 服务端ip/域名:port\nconn_type | 与服务端通信模式(tcp或kcp)\nvkey|服务端配置文件中的密钥(非web)\nusername|socks5或http(s)密码保护用户名(可忽略)\npassword|socks5或http(s)密码保护密码(可忽略)\ncompress|是否压缩传输(true或false或忽略)\ncrypt|是否加密传输(true或false或忽略)\nrate_limit|速度限制，可忽略\nflow_limit|流量限制，可忽略\nremark|客户端备注，可忽略\nmax_conn|最大连接数，可忽略\npprof_addr|debug pprof ip:port\n#### 域名代理\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[web1]\nhost=a.proxy.com\ntarget_addr=127.0.0.1:8080,127.0.0.1:8082\nhost_change=www.proxy.com\nheader_set_proxy=nps\n```\n项 | 含义\n---|---\nweb1 | 备注\nhost | 域名(http|https都可解析)\ntarget_addr|内网目标，负载均衡时多个目标，逗号隔开\nhost_change|请求host修改\nheader_xxx|请求header修改或添加，header_proxy表示添加header proxy:nps\n\n#### tcp隧道模式\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[tcp]\nmode=tcp\ntarget_addr=127.0.0.1:8080\nserver_port=9001\n```\n项 | 含义\n---|---\nmode | tcp\nserver_port | 在服务端的代理端口\ntartget_addr|内网目标\n\n#### udp隧道模式\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[udp]\nmode=udp\ntarget_addr=127.0.0.1:8080\nserver_port=9002\n```\n项 | 含义\n---|---\nmode | udp\nserver_port | 在服务端的代理端口\ntarget_addr|内网目标\n#### http代理模式\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[http]\nmode=httpProxy\nserver_port=9003\n```\n项 | 含义\n---|---\nmode | httpProxy\nserver_port | 在服务端的代理端口\n#### socks5代理模式\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[socks5]\nmode=socks5\nserver_port=9004\nmulti_account=multi_account.conf\n```\n项 | 含义\n---|---\nmode | socks5\nserver_port | 在服务端的代理端口\nmulti_account | socks5多账号配置文件（可选),配置后使用basic_username和basic_password无法通过认证\n#### 私密代理模式\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[secret_ssh]\nmode=secret\npassword=ssh2\ntarget_addr=10.1.50.2:22\n```\n项 | 含义\n---|---\nmode | secret\npassword | 唯一密钥\ntarget_addr|内网目标\n\n#### p2p代理模式\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[p2p_ssh]\nmode=p2p\npassword=ssh2\ntarget_addr=10.1.50.2:22\n```\n项 | 含义\n---|---\nmode | p2p\npassword | 唯一密钥\ntarget_addr|内网目标\n\n\n#### 文件访问模式\n利用nps提供一个公网可访问的本地文件服务，此模式仅客户端使用配置文件模式方可启动\n\n```ini\n[common]\nserver_addr=1.1.1.1:8024\nvkey=123\n[file]\nmode=file\nserver_port=9100\nlocal_path=/tmp/\nstrip_pre=/web/\n````\n\n项 | 含义\n---|---\nmode | file\nserver_port | 服务端开启的端口\nlocal_path|本地文件目录\nstrip_pre|前缀\n\n对于`strip_pre`，访问公网`ip:9100/web/`相当于访问`/tmp/`目录\n\n#### 断线重连\n```ini\n[common]\nauto_reconnection=true\n```\n"
  },
  {
    "path": "docs/webapi.md",
    "content": "获取客户端列表\r\n\r\n```\r\nPOST /client/list/\r\n```\r\n\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| search | 搜索 |\r\n| order | 排序asc 正序 desc倒序 |\r\n| offset | 分页(第几页) |\r\n| limit | 条数(分页显示的条数) |\r\n\r\n***\r\n获取单个客户端\r\n\r\n```\r\nPOST /client/getclient/\r\n```\r\n\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 客户端id |\r\n\r\n***\r\n添加客户端\r\n\r\n```\r\nPOST /client/add/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| remark | 备注 |\r\n| u | basic权限认证用户名 |\r\n| p | basic权限认证密码 |\r\n| limit | 条数(分页显示的条数) |\r\n| vkey | 客户端验证密钥 |\r\n| config\\_conn\\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |\r\n| compress | 压缩1允许 0不允许 |\r\n| crypt | 是否加密（1或者0）1允许 0不允许 |\r\n| rate\\_limit | 带宽限制 单位KB/S 空则为不限制 |\r\n| flow\\_limit | 流量限制 单位M 空则为不限制 |\r\n| max\\_conn | 客户端最大连接数量 空则为不限制 |\r\n| max\\_tunnel | 客户端最大隧道数量 空则为不限制 |\r\n\r\n***\r\n修改客户端\r\n\r\n```\r\nPOST /client/edit/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| remark | 备注 |\r\n| u | basic权限认证用户名 |\r\n| p | basic权限认证密码 |\r\n| limit | 条数(分页显示的条数) |\r\n| vkey | 客户端验证密钥 |\r\n| config\\_conn\\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |\r\n| compress | 压缩1允许 0不允许 |\r\n| crypt | 是否加密（1或者0）1允许 0不允许 |\r\n| rate\\_limit | 带宽限制 单位KB/S 空则为不限制 |\r\n| flow\\_limit | 流量限制 单位M 空则为不限制 |\r\n| max\\_conn | 客户端最大连接数量 空则为不限制 |\r\n| max\\_tunnel | 客户端最大隧道数量 空则为不限制 |\r\n| id | 要修改的客户端id |\r\n\r\n***\r\n删除客户端\r\n\r\n```\r\nPOST /client/del/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 要删除的客户端id |\r\n\r\n***\r\n获取域名解析列表\r\n\r\n```\r\nPOST /index/hostlist/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| search | 搜索(可以搜域名/备注什么的) |\r\n| offset | 分页(第几页) |\r\n| limit | 条数(分页显示的条数) |\r\n\r\n***\r\n添加域名解析\r\n\r\n```\r\nPOST /index/addhost/\r\n```\r\n\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| remark | 备注 |\r\n| host | 域名 |\r\n| scheme | 协议类型(三种 all http https) |\r\n| location | url路由 空则为不限制 |\r\n| client\\_id | 客户端id |\r\n| target | 内网目标(ip:端口) |\r\n| header | request header 请求头 |\r\n| hostchange | request host 请求主机 |\r\n\r\n***\r\n修改域名解析\r\n\r\n```\r\nPOST /index/edithost/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| remark | 备注 |\r\n| host | 域名 |\r\n| scheme | 协议类型(三种 all http https) |\r\n| location | url路由 空则为不限制 |\r\n| client\\_id | 客户端id |\r\n| target | 内网目标(ip:端口) |\r\n| header | request header 请求头 |\r\n| hostchange | request host 请求主机 |\r\n| id | 需要修改的域名解析id |\r\n\r\n***\r\n删除域名解析\r\n\r\n```\r\nPOST /index/delhost/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 需要删除的域名解析id |\r\n\r\n***\r\n获取单条隧道信息\r\n\r\n```\r\nPOST /index/getonetunnel/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 隧道的id |\r\n\r\n***\r\n获取隧道列表\r\n\r\n```\r\nPOST /index/gettunnel/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| client\\_id | 穿透隧道的客户端id |\r\n| type | 类型tcp udp httpProx socks5 secret p2p |\r\n| search | 搜索 |\r\n| offset | 分页(第几页) |\r\n| limit | 条数(分页显示的条数) |\r\n\r\n***\r\n添加隧道\r\n\r\n```\r\nPOST /index/add/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| type | 类型tcp udp httpProx socks5 secret p2p |\r\n| remark | 备注 |\r\n| port | 服务端端口 |\r\n| target | 目标(ip:端口) |\r\n| client\\_id | 客户端id |\r\n\r\n***\r\n修改隧道\r\n\r\n```\r\nPOST /index/edit/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| type | 类型tcp udp httpProx socks5 secret p2p |\r\n| remark | 备注 |\r\n| port | 服务端端口 |\r\n| target | 目标(ip:端口) |\r\n| client\\_id | 客户端id |\r\n| id | 隧道id |\r\n\r\n***\r\n删除隧道\r\n\r\n```\r\nPOST /index/del/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 隧道id |\r\n\r\n***\r\n隧道停止工作\r\n\r\n```\r\nPOST /index/stop/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 隧道id |\r\n\r\n***\r\n隧道开始工作\r\n\r\n```\r\nPOST /index/start/\r\n```\r\n\r\n| 参数 | 含义 |\r\n| --- | --- |\r\n| id | 隧道id |\r\n"
  },
  {
    "path": "go.mod",
    "content": "module ehang.io/nps\n\ngo 1.15\n\nrequire (\n\tehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992\n\tfyne.io/fyne/v2 v2.0.2\n\tgithub.com/astaxie/beego v1.12.0\n\tgithub.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect\n\tgithub.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c\n\tgithub.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d\n\tgithub.com/dsnet/compress v0.0.1 // indirect\n\tgithub.com/golang/snappy v0.0.3\n\tgithub.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect\n\tgithub.com/kardianos/service v1.2.0\n\tgithub.com/klauspost/cpuid v1.3.1 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.0.6 // indirect\n\tgithub.com/klauspost/pgzip v1.2.1 // indirect\n\tgithub.com/klauspost/reedsolomon v1.9.12 // indirect\n\tgithub.com/panjf2000/ants/v2 v2.4.2\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect\n\tgithub.com/shirou/gopsutil/v3 v3.21.3\n\tgithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect\n\tgithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect\n\tgithub.com/tjfoc/gmsm v1.4.0 // indirect\n\tgithub.com/xtaci/kcp-go v5.4.20+incompatible\n\tgithub.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect\n\tgolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect\n\tgolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4\n\tgolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect\n)\n\nreplace github.com/astaxie/beego => github.com/exfly/beego v1.12.0-export-init\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\nehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992 h1:LvlcB+8JveSBprHnva0g+OyLwAH8CRxEwtWzTe6ocoE=\nehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992/go.mod h1:v54y/8ICChiM/aVUuKxGIcWwjm4HGNRyyAwbgLBoMbI=\nfyne.io/fyne/v2 v2.0.2 h1:6pDvFuCmL1odyT/fPI+2L54hMJW1Zt9Dno41HmLInRs=\nfyne.io/fyne/v2 v2.0.2/go.mod h1:3+FYmLJVgeb8EvTPJ5YzZeo7LkAq4bbuY3Zrir6xHbg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=\ngithub.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=\ngithub.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=\ngithub.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=\ngithub.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=\ngithub.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=\ngithub.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=\ngithub.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=\ngithub.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=\ngithub.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=\ngithub.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=\ngithub.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c h1:aprLqMn7gSPT+vdDSl+/E6NLEuArwD/J7IWd8bJt5lQ=\ngithub.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c/go.mod h1:Ie6SubJv/NTO9Q0UBH0QCl3Ve50lu9hjbi5YJUw03TE=\ngithub.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=\ngithub.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=\ngithub.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=\ngithub.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=\ngithub.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=\ngithub.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=\ngithub.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=\ngithub.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=\ngithub.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=\ngithub.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=\ngithub.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=\ngithub.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/exfly/beego v1.12.0-export-init h1:VQNYKdXhAwZGUaFmQv8Aj921O3rQJZRIF8xeGrhsjrI=\ngithub.com/exfly/beego v1.12.0-export-init/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=\ngithub.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=\ngithub.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI=\ngithub.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=\ngithub.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=\ngithub.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=\ngithub.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=\ngithub.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=\ngithub.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=\ngithub.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=\ngithub.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 h1:WgfvpuKg42WVLkxNwzfFraXkTXPK36bMqXvMFN67clI=\ngithub.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214/go.mod h1:kj6hFWqfwSjFjLnYW5PK1DoxZ4O0uapwHRmd9jhln4E=\ngithub.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=\ngithub.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=\ngithub.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=\ngithub.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=\ngithub.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=\ngithub.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=\ngithub.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=\ngithub.com/klauspost/cpuid/v2 v2.0.2 h1:pd2FBxFydtPn2ywTLStbFg9CJKrojATnpeJWSP7Ys4k=\ngithub.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=\ngithub.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=\ngithub.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/klauspost/reedsolomon v1.9.12 h1:EyOucRmcrLH+2hqKGdoA5SM8pwPKR6BJsf3r6zpYOA0=\ngithub.com/klauspost/reedsolomon v1.9.12/go.mod h1:nLvuzNvy1ZDNQW30IuMc2ZWCbiqrJgdLoUS2X8HAUVg=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=\ngithub.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/panjf2000/ants/v2 v2.4.2 h1:kesjjo8JipN3vNNg1XaiXaeSs6xJweBTgenkBtsrHf8=\ngithub.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=\ngithub.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=\ngithub.com/shirou/gopsutil/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8=\ngithub.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=\ngithub.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=\ngithub.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=\ngithub.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=\ngithub.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=\ngithub.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=\ngithub.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=\ngithub.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=\ngithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=\ngithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=\ngithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=\ngithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=\ngithub.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s=\ngithub.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=\ngithub.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=\ngithub.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=\ngithub.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=\ngithub.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=\ngithub.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=\ngithub.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=\ngithub.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=\ngithub.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=\ngithub.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=\ngithub.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=\ngithub.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngolang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=\ngolang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=\ngolang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=\ngolang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=\ngolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=\ngopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "lib/cache/lru.go",
    "content": "package cache\n\nimport (\n\t\"container/list\"\n\t\"sync\"\n)\n\n// Cache is an LRU cache. It is safe for concurrent access.\ntype Cache struct {\n\t// MaxEntries is the maximum number of cache entries before\n\t// an item is evicted. Zero means no limit.\n\tMaxEntries int\n\n\t//Execute this callback function when an element is culled\n\tOnEvicted func(key Key, value interface{})\n\n\tll    *list.List //list\n\tcache sync.Map\n}\n\n// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators\ntype Key interface{}\n\ntype entry struct {\n\tkey   Key\n\tvalue interface{}\n}\n\n// New creates a new Cache.\n// If maxEntries is 0, the cache has no length limit.\n// that eviction is done by the caller.\nfunc New(maxEntries int) *Cache {\n\treturn &Cache{\n\t\tMaxEntries: maxEntries,\n\t\tll:         list.New(),\n\t\t//cache:      make(map[interface{}]*list.Element),\n\t}\n}\n\n// If the key value already exists, move the key to the front\nfunc (c *Cache) Add(key Key, value interface{}) {\n\tif ee, ok := c.cache.Load(key); ok {\n\t\tc.ll.MoveToFront(ee.(*list.Element)) // move to the front\n\t\tee.(*list.Element).Value.(*entry).value = value\n\t\treturn\n\t}\n\tele := c.ll.PushFront(&entry{key, value})\n\tc.cache.Store(key, ele)\n\tif c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { // Remove the oldest element if the limit is exceeded\n\t\tc.RemoveOldest()\n\t}\n}\n\n// Get looks up a key's value from the cache.\nfunc (c *Cache) Get(key Key) (value interface{}, ok bool) {\n\tif ele, hit := c.cache.Load(key); hit {\n\t\tc.ll.MoveToFront(ele.(*list.Element))\n\t\treturn ele.(*list.Element).Value.(*entry).value, true\n\t}\n\treturn\n}\n\n// Remove removes the provided key from the cache.\nfunc (c *Cache) Remove(key Key) {\n\tif ele, hit := c.cache.Load(key); hit {\n\t\tc.removeElement(ele.(*list.Element))\n\t}\n}\n\n// RemoveOldest removes the oldest item from the cache.\nfunc (c *Cache) RemoveOldest() {\n\tele := c.ll.Back()\n\tif ele != nil {\n\t\tc.removeElement(ele)\n\t}\n}\n\nfunc (c *Cache) removeElement(e *list.Element) {\n\tc.ll.Remove(e)\n\tkv := e.Value.(*entry)\n\tc.cache.Delete(kv.key)\n\tif c.OnEvicted != nil {\n\t\tc.OnEvicted(kv.key, kv.value)\n\t}\n}\n\n// Len returns the number of items in the cache.\nfunc (c *Cache) Len() int {\n\treturn c.ll.Len()\n}\n\n// Clear purges all stored items from the cache.\nfunc (c *Cache) Clear() {\n\tif c.OnEvicted != nil {\n\t\tc.cache.Range(func(key, value interface{}) bool {\n\t\t\tkv := value.(*list.Element).Value.(*entry)\n\t\t\tc.OnEvicted(kv.key, kv.value)\n\t\t\treturn true\n\t\t})\n\t}\n\tc.ll = nil\n}\n"
  },
  {
    "path": "lib/common/const.go",
    "content": "package common\n\nconst (\n\tCONN_DATA_SEQ     = \"*#*\" //Separator\n\tVERIFY_EER        = \"vkey\"\n\tVERIFY_SUCCESS    = \"sucs\"\n\tWORK_MAIN         = \"main\"\n\tWORK_CHAN         = \"chan\"\n\tWORK_CONFIG       = \"conf\"\n\tWORK_REGISTER     = \"rgst\"\n\tWORK_SECRET       = \"sert\"\n\tWORK_FILE         = \"file\"\n\tWORK_P2P          = \"p2pm\"\n\tWORK_P2P_VISITOR  = \"p2pv\"\n\tWORK_P2P_PROVIDER = \"p2pp\"\n\tWORK_P2P_CONNECT  = \"p2pc\"\n\tWORK_P2P_SUCCESS  = \"p2ps\"\n\tWORK_P2P_END      = \"p2pe\"\n\tWORK_P2P_LAST     = \"p2pl\"\n\tWORK_STATUS       = \"stus\"\n\tRES_MSG           = \"msg0\"\n\tRES_CLOSE         = \"clse\"\n\tNEW_UDP_CONN      = \"udpc\" //p2p udp conn\n\tNEW_TASK          = \"task\"\n\tNEW_CONF          = \"conf\"\n\tNEW_HOST          = \"host\"\n\tCONN_TCP          = \"tcp\"\n\tCONN_UDP          = \"udp\"\n\tCONN_TEST         = \"TST\"\n\tUnauthorizedBytes = `HTTP/1.1 401 Unauthorized\nContent-Type: text/plain; charset=utf-8\nWWW-Authenticate: Basic realm=\"easyProxy\"\n\n401 Unauthorized`\n\tConnectionFailBytes = `HTTP/1.1 404 Not Found\n\n`\n)\n"
  },
  {
    "path": "lib/common/logs.go",
    "content": "package common\n\nimport (\n\t\"github.com/astaxie/beego/logs\"\n\t\"time\"\n)\n\nconst MaxMsgLen = 5000\n\nvar logMsgs string\n\nfunc init() {\n\tlogs.Register(\"store\", func() logs.Logger {\n\t\treturn new(StoreMsg)\n\t})\n}\n\nfunc GetLogMsg() string {\n\treturn logMsgs\n}\n\ntype StoreMsg struct {\n}\n\nfunc (lg *StoreMsg) Init(config string) error {\n\treturn nil\n}\n\nfunc (lg *StoreMsg) WriteMsg(when time.Time, msg string, level int) error {\n\tm := when.Format(\"2006-01-02 15:04:05\") + \" \" + msg + \"\\r\\n\"\n\tif len(logMsgs) > MaxMsgLen {\n\t\tstart := MaxMsgLen - len(m)\n\t\tif start <= 0 {\n\t\t\tstart = MaxMsgLen\n\t\t}\n\t\tlogMsgs = logMsgs[start:]\n\t}\n\tlogMsgs += m\n\treturn nil\n}\n\nfunc (lg *StoreMsg) Destroy() {\n\treturn\n}\n\nfunc (lg *StoreMsg) Flush() {\n\treturn\n}\n"
  },
  {
    "path": "lib/common/netpackager.go",
    "content": "package common\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"strconv\"\n)\n\ntype NetPackager interface {\n\tPack(writer io.Writer) (err error)\n\tUnPack(reader io.Reader) (err error)\n}\n\nconst (\n\tipV4       = 1\n\tdomainName = 3\n\tipV6       = 4\n)\n\ntype UDPHeader struct {\n\tRsv  uint16\n\tFrag uint8\n\tAddr *Addr\n}\n\nfunc NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader {\n\treturn &UDPHeader{\n\t\tRsv:  rsv,\n\t\tFrag: frag,\n\t\tAddr: addr,\n\t}\n}\n\ntype Addr struct {\n\tType uint8\n\tHost string\n\tPort uint16\n}\n\nfunc (addr *Addr) String() string {\n\treturn net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))\n}\n\nfunc (addr *Addr) Decode(b []byte) error {\n\taddr.Type = b[0]\n\tpos := 1\n\tswitch addr.Type {\n\tcase ipV4:\n\t\taddr.Host = net.IP(b[pos : pos+net.IPv4len]).String()\n\t\tpos += net.IPv4len\n\tcase ipV6:\n\t\taddr.Host = net.IP(b[pos : pos+net.IPv6len]).String()\n\t\tpos += net.IPv6len\n\tcase domainName:\n\t\taddrlen := int(b[pos])\n\t\tpos++\n\t\taddr.Host = string(b[pos : pos+addrlen])\n\t\tpos += addrlen\n\tdefault:\n\t\treturn errors.New(\"decode error\")\n\t}\n\n\taddr.Port = binary.BigEndian.Uint16(b[pos:])\n\n\treturn nil\n}\n\nfunc (addr *Addr) Encode(b []byte) (int, error) {\n\tb[0] = addr.Type\n\tpos := 1\n\tswitch addr.Type {\n\tcase ipV4:\n\t\tip4 := net.ParseIP(addr.Host).To4()\n\t\tif ip4 == nil {\n\t\t\tip4 = net.IPv4zero.To4()\n\t\t}\n\t\tpos += copy(b[pos:], ip4)\n\tcase domainName:\n\t\tb[pos] = byte(len(addr.Host))\n\t\tpos++\n\t\tpos += copy(b[pos:], []byte(addr.Host))\n\tcase ipV6:\n\t\tip16 := net.ParseIP(addr.Host).To16()\n\t\tif ip16 == nil {\n\t\t\tip16 = net.IPv6zero.To16()\n\t\t}\n\t\tpos += copy(b[pos:], ip16)\n\tdefault:\n\t\tb[0] = ipV4\n\t\tcopy(b[pos:pos+4], net.IPv4zero.To4())\n\t\tpos += 4\n\t}\n\tbinary.BigEndian.PutUint16(b[pos:], addr.Port)\n\tpos += 2\n\n\treturn pos, nil\n}\n\nfunc (h *UDPHeader) Write(w io.Writer) error {\n\tb := BufPoolUdp.Get().([]byte)\n\tdefer BufPoolUdp.Put(b)\n\n\tbinary.BigEndian.PutUint16(b[:2], h.Rsv)\n\tb[2] = h.Frag\n\n\taddr := h.Addr\n\tif addr == nil {\n\t\taddr = &Addr{}\n\t}\n\tlength, _ := addr.Encode(b[3:])\n\n\t_, err := w.Write(b[:3+length])\n\treturn err\n}\n\ntype UDPDatagram struct {\n\tHeader *UDPHeader\n\tData   []byte\n}\n\nfunc ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) {\n\tb := BufPoolUdp.Get().([]byte)\n\tdefer BufPoolUdp.Put(b)\n\n\t// when r is a streaming (such as TCP connection), we may read more than the required data,\n\t// but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast\n\t// to make sure that no redundant data will be discarded.\n\tn, err := io.ReadFull(r, b[:5])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\theader := &UDPHeader{\n\t\tRsv:  binary.BigEndian.Uint16(b[:2]),\n\t\tFrag: b[2],\n\t}\n\n\tatype := b[3]\n\thlen := 0\n\tswitch atype {\n\tcase ipV4:\n\t\thlen = 10\n\tcase ipV6:\n\t\thlen = 22\n\tcase domainName:\n\t\thlen = 7 + int(b[4])\n\tdefault:\n\t\treturn nil, errors.New(\"addr not support\")\n\t}\n\tdlen := int(header.Rsv)\n\tif dlen == 0 { // standard SOCKS5 UDP datagram\n\t\textra, err := ioutil.ReadAll(r) // we assume no redundant data\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcopy(b[n:], extra)\n\t\tn += len(extra) // total length\n\t\tdlen = n - hlen // data length\n\t} else { // extended feature, for UDP over TCP, using reserved field as data length\n\t\tif _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tn = hlen + dlen\n\t}\n\theader.Addr = new(Addr)\n\tif err := header.Addr.Decode(b[3:hlen]); err != nil {\n\t\treturn nil, err\n\t}\n\tdata := make([]byte, dlen)\n\tcopy(data, b[hlen:n])\n\td := &UDPDatagram{\n\t\tHeader: header,\n\t\tData:   data,\n\t}\n\treturn d, nil\n}\n\nfunc NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram {\n\treturn &UDPDatagram{\n\t\tHeader: header,\n\t\tData:   data,\n\t}\n}\n\nfunc (d *UDPDatagram) Write(w io.Writer) error {\n\th := d.Header\n\tif h == nil {\n\t\th = &UDPHeader{}\n\t}\n\tbuf := bytes.Buffer{}\n\tif err := h.Write(&buf); err != nil {\n\t\treturn err\n\t}\n\tif _, err := buf.Write(d.Data); err != nil {\n\t\treturn err\n\t}\n\n\t_, err := buf.WriteTo(w)\n\treturn err\n}\n\nfunc ToSocksAddr(addr net.Addr) *Addr {\n\thost := \"0.0.0.0\"\n\tport := 0\n\tif addr != nil {\n\t\th, p, _ := net.SplitHostPort(addr.String())\n\t\thost = h\n\t\tport, _ = strconv.Atoi(p)\n\t}\n\treturn &Addr{\n\t\tType: ipV4,\n\t\tHost: host,\n\t\tPort: uint16(port),\n\t}\n}\n"
  },
  {
    "path": "lib/common/pool.go",
    "content": "package common\n\nimport (\n\t\"sync\"\n)\n\nconst PoolSize = 64 * 1024\nconst PoolSizeSmall = 100\nconst PoolSizeUdp = 1472 + 200\nconst PoolSizeCopy = 32 << 10\n\nvar BufPool = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, PoolSize)\n\t},\n}\n\nvar BufPoolUdp = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, PoolSizeUdp)\n\t},\n}\nvar BufPoolMax = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, PoolSize)\n\t},\n}\nvar BufPoolSmall = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, PoolSizeSmall)\n\t},\n}\nvar BufPoolCopy = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, PoolSizeCopy)\n\t},\n}\n\nfunc PutBufPoolUdp(buf []byte) {\n\tif cap(buf) == PoolSizeUdp {\n\t\tBufPoolUdp.Put(buf[:PoolSizeUdp])\n\t}\n}\n\nfunc PutBufPoolCopy(buf []byte) {\n\tif cap(buf) == PoolSizeCopy {\n\t\tBufPoolCopy.Put(buf[:PoolSizeCopy])\n\t}\n}\n\nfunc GetBufPoolCopy() []byte {\n\treturn (BufPoolCopy.Get().([]byte))[:PoolSizeCopy]\n}\n\nfunc PutBufPoolMax(buf []byte) {\n\tif cap(buf) == PoolSize {\n\t\tBufPoolMax.Put(buf[:PoolSize])\n\t}\n}\n\ntype copyBufferPool struct {\n\tpool sync.Pool\n}\n\nfunc (Self *copyBufferPool) New() {\n\tSelf.pool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn make([]byte, PoolSizeCopy, PoolSizeCopy)\n\t\t},\n\t}\n}\n\nfunc (Self *copyBufferPool) Get() []byte {\n\tbuf := Self.pool.Get().([]byte)\n\treturn buf[:PoolSizeCopy] // just like make a new slice, but data may not be 0\n}\n\nfunc (Self *copyBufferPool) Put(x []byte) {\n\tif len(x) == PoolSizeCopy {\n\t\tSelf.pool.Put(x)\n\t} else {\n\t\tx = nil // buf is not full, not allowed, New method returns a full buf\n\t}\n}\n\nvar once = sync.Once{}\nvar CopyBuff = copyBufferPool{}\n\nfunc newPool() {\n\tCopyBuff.New()\n}\n\nfunc init() {\n\tonce.Do(newPool)\n}\n"
  },
  {
    "path": "lib/common/pprof.go",
    "content": "package common\n\nimport (\n\t\"github.com/astaxie/beego\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n)\n\nfunc InitPProfFromFile() {\n\tip := beego.AppConfig.String(\"pprof_ip\")\n\tp := beego.AppConfig.String(\"pprof_port\")\n\tif len(ip) > 0 && len(p) > 0 && IsPort(p) {\n\t\trunPProf(ip + \":\" + p)\n\t}\n}\n\nfunc InitPProfFromArg(arg string) {\n\tif len(arg) > 0 {\n\t\trunPProf(arg)\n\t}\n}\n\nfunc runPProf(ipPort string) {\n\tgo func() {\n\t\t_ = http.ListenAndServe(ipPort, nil)\n\t}()\n\tlogs.Info(\"PProf debug listen on\", ipPort)\n}\n"
  },
  {
    "path": "lib/common/run.go",
    "content": "package common\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n)\n\n//Get the currently selected configuration file directory\n//For non-Windows systems, select the /etc/nps as config directory if exist, or select ./\n//windows system, select the C:\\Program Files\\nps as config directory if exist, or select ./\nfunc GetRunPath() string {\n\tvar path string\n\tif path = GetInstallPath(); !FileExists(path) {\n\t\treturn GetAppPath()\n\t}\n\treturn path\n}\n\n//Different systems get different installation paths\nfunc GetInstallPath() string {\n\tvar path string\n\tif IsWindows() {\n\t\tpath = `C:\\Program Files\\nps`\n\t} else {\n\t\tpath = \"/etc/nps\"\n\t}\n\treturn path\n}\n\n//Get the absolute path to the running directory\nfunc GetAppPath() string {\n\tif path, err := filepath.Abs(filepath.Dir(os.Args[0])); err == nil {\n\t\treturn path\n\t}\n\treturn os.Args[0]\n}\n\n//Determine whether the current system is a Windows system?\nfunc IsWindows() bool {\n\tif runtime.GOOS == \"windows\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\n//interface log file path\nfunc GetLogPath() string {\n\tvar path string\n\tif IsWindows() {\n\t\tpath = filepath.Join(GetAppPath(), \"nps.log\")\n\t} else {\n\t\tpath = \"/var/log/nps.log\"\n\t}\n\treturn path\n}\n\n//interface npc log file path\nfunc GetNpcLogPath() string {\n\tvar path string\n\tif IsWindows() {\n\t\tpath = filepath.Join(GetAppPath(), \"npc.log\")\n\t} else {\n\t\tpath = \"/var/log/npc.log\"\n\t}\n\treturn path\n}\n\n//interface pid file path\nfunc GetTmpPath() string {\n\tvar path string\n\tif IsWindows() {\n\t\tpath = GetAppPath()\n\t} else {\n\t\tpath = \"/tmp\"\n\t}\n\treturn path\n}\n\n//config file path\nfunc GetConfigPath() string {\n\tvar path string\n\tif IsWindows() {\n\t\tpath = filepath.Join(GetAppPath(), \"conf/npc.conf\")\n\t} else {\n\t\tpath = \"conf/npc.conf\"\n\t}\n\treturn path\n}\n"
  },
  {
    "path": "lib/common/util.go",
    "content": "package common\n\nimport (\n\t\"bytes\"\n\t\"ehang.io/nps/lib/version\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"ehang.io/nps/lib/crypt\"\n)\n\n//Get the corresponding IP address through domain name\nfunc GetHostByName(hostname string) string {\n\tif !DomainCheck(hostname) {\n\t\treturn hostname\n\t}\n\tips, _ := net.LookupIP(hostname)\n\tif ips != nil {\n\t\tfor _, v := range ips {\n\t\t\tif v.To4() != nil {\n\t\t\t\treturn v.String()\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n//Check the legality of domain\nfunc DomainCheck(domain string) bool {\n\tvar match bool\n\tIsLine := \"^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\\\-]{0,61}[a-zA-Z0-9])?\\\\.)+[a-zA-Z]{2,6}(/)\"\n\tNotLine := \"^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\\\-]{0,61}[a-zA-Z0-9])?\\\\.)+[a-zA-Z]{2,6}\"\n\tmatch, _ = regexp.MatchString(IsLine, domain)\n\tif !match {\n\t\tmatch, _ = regexp.MatchString(NotLine, domain)\n\t}\n\treturn match\n}\n\n//Check if the Request request is validated\nfunc CheckAuth(r *http.Request, user, passwd string) bool {\n\ts := strings.SplitN(r.Header.Get(\"Authorization\"), \" \", 2)\n\tif len(s) != 2 {\n\t\ts = strings.SplitN(r.Header.Get(\"Proxy-Authorization\"), \" \", 2)\n\t\tif len(s) != 2 {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tb, err := base64.StdEncoding.DecodeString(s[1])\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tpair := strings.SplitN(string(b), \":\", 2)\n\tif len(pair) != 2 {\n\t\treturn false\n\t}\n\treturn pair[0] == user && pair[1] == passwd\n}\n\n//get bool by str\nfunc GetBoolByStr(s string) bool {\n\tswitch s {\n\tcase \"1\", \"true\":\n\t\treturn true\n\t}\n\treturn false\n}\n\n//get str by bool\nfunc GetStrByBool(b bool) string {\n\tif b {\n\t\treturn \"1\"\n\t}\n\treturn \"0\"\n}\n\n//int\nfunc GetIntNoErrByStr(str string) int {\n\ti, _ := strconv.Atoi(strings.TrimSpace(str))\n\treturn i\n}\n\n//Get verify value\nfunc Getverifyval(vkey string) string {\n\treturn crypt.Md5(vkey)\n}\n\n//Change headers and host of request\nfunc ChangeHostAndHeader(r *http.Request, host string, header string, addr string, addOrigin bool) {\n\tif host != \"\" {\n\t\tr.Host = host\n\t}\n\tif header != \"\" {\n\t\th := strings.Split(header, \"\\n\")\n\t\tfor _, v := range h {\n\t\t\thd := strings.Split(v, \":\")\n\t\t\tif len(hd) == 2 {\n\t\t\t\tr.Header.Set(hd[0], hd[1])\n\t\t\t}\n\t\t}\n\t}\n\taddr = strings.Split(addr, \":\")[0]\n\tif prior, ok := r.Header[\"X-Forwarded-For\"]; ok {\n\t\taddr = strings.Join(prior, \", \") + \", \" + addr\n\t}\n\tif addOrigin {\n\t\tr.Header.Set(\"X-Forwarded-For\", addr)\n\t\tr.Header.Set(\"X-Real-IP\", addr)\n\t}\n}\n\n//Read file content by file path\nfunc ReadAllFromFile(filePath string) ([]byte, error) {\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\treturn ioutil.ReadAll(f)\n}\n\n// FileExists reports whether the named file or directory exists.\nfunc FileExists(name string) bool {\n\tif _, err := os.Stat(name); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n//Judge whether the TCP port can open normally\nfunc TestTcpPort(port int) bool {\n\tl, err := net.ListenTCP(\"tcp\", &net.TCPAddr{net.ParseIP(\"0.0.0.0\"), port, \"\"})\n\tdefer func() {\n\t\tif l != nil {\n\t\t\tl.Close()\n\t\t}\n\t}()\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\n//Judge whether the UDP port can open normally\nfunc TestUdpPort(port int) bool {\n\tl, err := net.ListenUDP(\"udp\", &net.UDPAddr{net.ParseIP(\"0.0.0.0\"), port, \"\"})\n\tdefer func() {\n\t\tif l != nil {\n\t\t\tl.Close()\n\t\t}\n\t}()\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\n//Write length and individual byte data\n//Length prevents sticking\n//# Characters are used to separate data\nfunc BinaryWrite(raw *bytes.Buffer, v ...string) {\n\tb := GetWriteStr(v...)\n\tbinary.Write(raw, binary.LittleEndian, int32(len(b)))\n\tbinary.Write(raw, binary.LittleEndian, b)\n}\n\n// get seq str\nfunc GetWriteStr(v ...string) []byte {\n\tbuffer := new(bytes.Buffer)\n\tvar l int32\n\tfor _, v := range v {\n\t\tl += int32(len([]byte(v))) + int32(len([]byte(CONN_DATA_SEQ)))\n\t\tbinary.Write(buffer, binary.LittleEndian, []byte(v))\n\t\tbinary.Write(buffer, binary.LittleEndian, []byte(CONN_DATA_SEQ))\n\t}\n\treturn buffer.Bytes()\n}\n\n//inArray str interface\nfunc InStrArr(arr []string, val string) bool {\n\tfor _, v := range arr {\n\t\tif v == val {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n//inArray int interface\nfunc InIntArr(arr []int, val int) bool {\n\tfor _, v := range arr {\n\t\tif v == val {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n//format ports str to a int array\nfunc GetPorts(p string) []int {\n\tvar ps []int\n\tarr := strings.Split(p, \",\")\n\tfor _, v := range arr {\n\t\tfw := strings.Split(v, \"-\")\n\t\tif len(fw) == 2 {\n\t\t\tif IsPort(fw[0]) && IsPort(fw[1]) {\n\t\t\t\tstart, _ := strconv.Atoi(fw[0])\n\t\t\t\tend, _ := strconv.Atoi(fw[1])\n\t\t\t\tfor i := start; i <= end; i++ {\n\t\t\t\t\tps = append(ps, i)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if IsPort(v) {\n\t\t\tp, _ := strconv.Atoi(v)\n\t\t\tps = append(ps, p)\n\t\t}\n\t}\n\treturn ps\n}\n\n//is the string a port\nfunc IsPort(p string) bool {\n\tpi, err := strconv.Atoi(p)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif pi > 65536 || pi < 1 {\n\t\treturn false\n\t}\n\treturn true\n}\n\n//if the s is just a port,return 127.0.0.1:s\nfunc FormatAddress(s string) string {\n\tif strings.Contains(s, \":\") {\n\t\treturn s\n\t}\n\treturn \"127.0.0.1:\" + s\n}\n\n//get address from the complete address\nfunc GetIpByAddr(addr string) string {\n\tarr := strings.Split(addr, \":\")\n\treturn arr[0]\n}\n\n//get port from the complete address\nfunc GetPortByAddr(addr string) int {\n\tarr := strings.Split(addr, \":\")\n\tif len(arr) < 2 {\n\t\treturn 0\n\t}\n\tp, err := strconv.Atoi(arr[1])\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn p\n}\n\nfunc CopyBuffer(dst io.Writer, src io.Reader, label ...string) (written int64, err error) {\n\tbuf := CopyBuff.Get()\n\tdefer CopyBuff.Put(buf)\n\tfor {\n\t\tnr, er := src.Read(buf)\n\t\t//if len(pr)>0 && pr[0] && nr > 50 {\n\t\t//\tlogs.Warn(string(buf[:50]))\n\t\t//}\n\t\tif nr > 0 {\n\t\t\tnw, ew := dst.Write(buf[0:nr])\n\t\t\tif nw > 0 {\n\t\t\t\twritten += int64(nw)\n\t\t\t}\n\t\t\tif ew != nil {\n\t\t\t\terr = ew\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif nr != nw {\n\t\t\t\terr = io.ErrShortWrite\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif er != nil {\n\t\t\terr = er\n\t\t\tbreak\n\t\t}\n\t}\n\treturn written, err\n}\n\n//send this ip forget to get a local udp port\nfunc GetLocalUdpAddr() (net.Conn, error) {\n\ttmpConn, err := net.Dial(\"udp\", \"114.114.114.114:53\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn tmpConn, tmpConn.Close()\n}\n\n//parse template\nfunc ParseStr(str string) (string, error) {\n\ttmp := template.New(\"npc\")\n\tvar err error\n\tw := new(bytes.Buffer)\n\tif tmp, err = tmp.Parse(str); err != nil {\n\t\treturn \"\", err\n\t}\n\tif err = tmp.Execute(w, GetEnvMap()); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn w.String(), nil\n}\n\n//get env\nfunc GetEnvMap() map[string]string {\n\tm := make(map[string]string)\n\tenviron := os.Environ()\n\tfor i := range environ {\n\t\ttmp := strings.Split(environ[i], \"=\")\n\t\tif len(tmp) == 2 {\n\t\t\tm[tmp[0]] = tmp[1]\n\t\t}\n\t}\n\treturn m\n}\n\n//throw the empty element of the string array\nfunc TrimArr(arr []string) []string {\n\tnewArr := make([]string, 0)\n\tfor _, v := range arr {\n\t\tif v != \"\" {\n\t\t\tnewArr = append(newArr, v)\n\t\t}\n\t}\n\treturn newArr\n}\n\n//\nfunc IsArrContains(arr []string, val string) bool {\n\tif arr == nil {\n\t\treturn false\n\t}\n\tfor _, v := range arr {\n\t\tif v == val {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n//remove value from string array\nfunc RemoveArrVal(arr []string, val string) []string {\n\tfor k, v := range arr {\n\t\tif v == val {\n\t\t\tarr = append(arr[:k], arr[k+1:]...)\n\t\t\treturn arr\n\t\t}\n\t}\n\treturn arr\n}\n\n//convert bytes to num\nfunc BytesToNum(b []byte) int {\n\tvar str string\n\tfor i := 0; i < len(b); i++ {\n\t\tstr += strconv.Itoa(int(b[i]))\n\t}\n\tx, _ := strconv.Atoi(str)\n\treturn int(x)\n}\n\n//get the length of the sync map\nfunc GeSynctMapLen(m sync.Map) int {\n\tvar c int\n\tm.Range(func(key, value interface{}) bool {\n\t\tc++\n\t\treturn true\n\t})\n\treturn c\n}\n\nfunc GetExtFromPath(path string) string {\n\ts := strings.Split(path, \".\")\n\tre, err := regexp.Compile(`(\\w+)`)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn string(re.Find([]byte(s[0])))\n}\n\nvar externalIp string\n\nfunc GetExternalIp() string {\n\tif externalIp != \"\" {\n\t\treturn externalIp\n\t}\n\tresp, err := http.Get(\"http://myexternalip.com/raw\")\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tdefer resp.Body.Close()\n\tcontent, _ := ioutil.ReadAll(resp.Body)\n\texternalIp = string(content)\n\treturn externalIp\n}\n\nfunc GetIntranetIp() (error, string) {\n\taddrs, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn nil, \"\"\n\t}\n\tfor _, address := range addrs {\n\t\t// 检查ip地址判断是否回环地址\n\t\tif ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {\n\t\t\tif ipnet.IP.To4() != nil {\n\t\t\t\treturn nil, ipnet.IP.To4().String()\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.New(\"get intranet ip error\"), \"\"\n}\n\nfunc IsPublicIP(IP net.IP) bool {\n\tif IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {\n\t\treturn false\n\t}\n\tif ip4 := IP.To4(); ip4 != nil {\n\t\tswitch true {\n\t\tcase ip4[0] == 10:\n\t\t\treturn false\n\t\tcase ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:\n\t\t\treturn false\n\t\tcase ip4[0] == 192 && ip4[1] == 168:\n\t\t\treturn false\n\t\tdefault:\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc GetServerIpByClientIp(clientIp net.IP) string {\n\tif IsPublicIP(clientIp) {\n\t\treturn GetExternalIp()\n\t}\n\t_, ip := GetIntranetIp()\n\treturn ip\n}\n\nfunc PrintVersion() {\n\tfmt.Printf(\"Version: %s\\nCore version: %s\\nSame core version of client and server can connect each other\\n\", version.VERSION, version.GetVersion())\n}\n"
  },
  {
    "path": "lib/config/config.go",
    "content": "package config\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/file\"\n)\n\ntype CommonConfig struct {\n\tServer           string\n\tVKey             string\n\tTp               string //bridgeType kcp or tcp\n\tAutoReconnection bool\n\tProxyUrl         string\n\tClient           *file.Client\n\tDisconnectTime   int\n}\n\ntype LocalServer struct {\n\tType     string\n\tPort     int\n\tIp       string\n\tPassword string\n\tTarget   string\n}\n\ntype Config struct {\n\tcontent      string\n\ttitle        []string\n\tCommonConfig *CommonConfig\n\tHosts        []*file.Host\n\tTasks        []*file.Tunnel\n\tHealths      []*file.Health\n\tLocalServer  []*LocalServer\n}\n\nfunc NewConfig(path string) (c *Config, err error) {\n\tc = new(Config)\n\tvar b []byte\n\tif b, err = common.ReadAllFromFile(path); err != nil {\n\t\treturn\n\t} else {\n\t\tif c.content, err = common.ParseStr(string(b)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif c.title, err = getAllTitle(c.content); err != nil {\n\t\t\treturn\n\t\t}\n\t\tvar nowIndex int\n\t\tvar nextIndex int\n\t\tvar nowContent string\n\t\tfor i := 0; i < len(c.title); i++ {\n\t\t\tnowIndex = strings.Index(c.content, c.title[i]) + len(c.title[i])\n\t\t\tif i < len(c.title)-1 {\n\t\t\t\tnextIndex = strings.Index(c.content, c.title[i+1])\n\t\t\t} else {\n\t\t\t\tnextIndex = len(c.content)\n\t\t\t}\n\t\t\tnowContent = c.content[nowIndex:nextIndex]\n\n\t\t\tif strings.Index(getTitleContent(c.title[i]), \"secret\") == 0 && !strings.Contains(nowContent, \"mode\") {\n\t\t\t\tlocal := delLocalService(nowContent)\n\t\t\t\tlocal.Type = \"secret\"\n\t\t\t\tc.LocalServer = append(c.LocalServer, local)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t//except mode\n\t\t\tif strings.Index(getTitleContent(c.title[i]), \"p2p\") == 0 && !strings.Contains(nowContent, \"mode\") {\n\t\t\t\tlocal := delLocalService(nowContent)\n\t\t\t\tlocal.Type = \"p2p\"\n\t\t\t\tc.LocalServer = append(c.LocalServer, local)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t//health set\n\t\t\tif strings.Index(getTitleContent(c.title[i]), \"health\") == 0 {\n\t\t\t\tc.Healths = append(c.Healths, dealHealth(nowContent))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch c.title[i] {\n\t\t\tcase \"[common]\":\n\t\t\t\tc.CommonConfig = dealCommon(nowContent)\n\t\t\tdefault:\n\t\t\t\tif strings.Index(nowContent, \"host\") > -1 {\n\t\t\t\t\th := dealHost(nowContent)\n\t\t\t\t\th.Remark = getTitleContent(c.title[i])\n\t\t\t\t\tc.Hosts = append(c.Hosts, h)\n\t\t\t\t} else {\n\t\t\t\t\tt := dealTunnel(nowContent)\n\t\t\t\t\tt.Remark = getTitleContent(c.title[i])\n\t\t\t\t\tc.Tasks = append(c.Tasks, t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc getTitleContent(s string) string {\n\tre, _ := regexp.Compile(`[\\[\\]]`)\n\treturn re.ReplaceAllString(s, \"\")\n}\n\nfunc dealCommon(s string) *CommonConfig {\n\tc := &CommonConfig{}\n\tc.Client = file.NewClient(\"\", true, true)\n\tc.Client.Cnf = new(file.Config)\n\tfor _, v := range splitStr(s) {\n\t\titem := strings.Split(v, \"=\")\n\t\tif len(item) == 0 {\n\t\t\tcontinue\n\t\t} else if len(item) == 1 {\n\t\t\titem = append(item, \"\")\n\t\t}\n\t\tswitch item[0] {\n\t\tcase \"server_addr\":\n\t\t\tc.Server = item[1]\n\t\tcase \"vkey\":\n\t\t\tc.VKey = item[1]\n\t\tcase \"conn_type\":\n\t\t\tc.Tp = item[1]\n\t\tcase \"auto_reconnection\":\n\t\t\tc.AutoReconnection = common.GetBoolByStr(item[1])\n\t\tcase \"basic_username\":\n\t\t\tc.Client.Cnf.U = item[1]\n\t\tcase \"basic_password\":\n\t\t\tc.Client.Cnf.P = item[1]\n\t\tcase \"web_password\":\n\t\t\tc.Client.WebPassword = item[1]\n\t\tcase \"web_username\":\n\t\t\tc.Client.WebUserName = item[1]\n\t\tcase \"compress\":\n\t\t\tc.Client.Cnf.Compress = common.GetBoolByStr(item[1])\n\t\tcase \"crypt\":\n\t\t\tc.Client.Cnf.Crypt = common.GetBoolByStr(item[1])\n\t\tcase \"proxy_url\":\n\t\t\tc.ProxyUrl = item[1]\n\t\tcase \"rate_limit\":\n\t\t\tc.Client.RateLimit = common.GetIntNoErrByStr(item[1])\n\t\tcase \"flow_limit\":\n\t\t\tc.Client.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[1]))\n\t\tcase \"max_conn\":\n\t\t\tc.Client.MaxConn = common.GetIntNoErrByStr(item[1])\n\t\tcase \"remark\":\n\t\t\tc.Client.Remark = item[1]\n\t\tcase \"pprof_addr\":\n\t\t\tcommon.InitPProfFromArg(item[1])\n\t\tcase \"disconnect_timeout\":\n\t\t\tc.DisconnectTime = common.GetIntNoErrByStr(item[1])\n\t\t}\n\t}\n\treturn c\n}\n\nfunc dealHost(s string) *file.Host {\n\th := &file.Host{}\n\th.Target = new(file.Target)\n\th.Scheme = \"all\"\n\tvar headerChange string\n\tfor _, v := range splitStr(s) {\n\t\titem := strings.Split(v, \"=\")\n\t\tif len(item) == 0 {\n\t\t\tcontinue\n\t\t} else if len(item) == 1 {\n\t\t\titem = append(item, \"\")\n\t\t}\n\t\tswitch strings.TrimSpace(item[0]) {\n\t\tcase \"host\":\n\t\t\th.Host = item[1]\n\t\tcase \"target_addr\":\n\t\t\th.Target.TargetStr = strings.Replace(item[1], \",\", \"\\n\", -1)\n\t\tcase \"host_change\":\n\t\t\th.HostChange = item[1]\n\t\tcase \"scheme\":\n\t\t\th.Scheme = item[1]\n\t\tcase \"location\":\n\t\t\th.Location = item[1]\n\t\tdefault:\n\t\t\tif strings.Contains(item[0], \"header\") {\n\t\t\t\theaderChange += strings.Replace(item[0], \"header_\", \"\", -1) + \":\" + item[1] + \"\\n\"\n\t\t\t}\n\t\t\th.HeaderChange = headerChange\n\t\t}\n\t}\n\treturn h\n}\n\nfunc dealHealth(s string) *file.Health {\n\th := &file.Health{}\n\tfor _, v := range splitStr(s) {\n\t\titem := strings.Split(v, \"=\")\n\t\tif len(item) == 0 {\n\t\t\tcontinue\n\t\t} else if len(item) == 1 {\n\t\t\titem = append(item, \"\")\n\t\t}\n\t\tswitch strings.TrimSpace(item[0]) {\n\t\tcase \"health_check_timeout\":\n\t\t\th.HealthCheckTimeout = common.GetIntNoErrByStr(item[1])\n\t\tcase \"health_check_max_failed\":\n\t\t\th.HealthMaxFail = common.GetIntNoErrByStr(item[1])\n\t\tcase \"health_check_interval\":\n\t\t\th.HealthCheckInterval = common.GetIntNoErrByStr(item[1])\n\t\tcase \"health_http_url\":\n\t\t\th.HttpHealthUrl = item[1]\n\t\tcase \"health_check_type\":\n\t\t\th.HealthCheckType = item[1]\n\t\tcase \"health_check_target\":\n\t\t\th.HealthCheckTarget = item[1]\n\t\t}\n\t}\n\treturn h\n}\n\nfunc dealTunnel(s string) *file.Tunnel {\n\tt := &file.Tunnel{}\n\tt.Target = new(file.Target)\n\tfor _, v := range splitStr(s) {\n\t\titem := strings.Split(v, \"=\")\n\t\tif len(item) == 0 {\n\t\t\tcontinue\n\t\t} else if len(item) == 1 {\n\t\t\titem = append(item, \"\")\n\t\t}\n\t\tswitch strings.TrimSpace(item[0]) {\n\t\tcase \"server_port\":\n\t\t\tt.Ports = item[1]\n\t\tcase \"server_ip\":\n\t\t\tt.ServerIp = item[1]\n\t\tcase \"mode\":\n\t\t\tt.Mode = item[1]\n\t\tcase \"target_addr\":\n\t\t\tt.Target.TargetStr = strings.Replace(item[1], \",\", \"\\n\", -1)\n\t\tcase \"target_port\":\n\t\t\tt.Target.TargetStr = item[1]\n\t\tcase \"target_ip\":\n\t\t\tt.TargetAddr = item[1]\n\t\tcase \"password\":\n\t\t\tt.Password = item[1]\n\t\tcase \"local_path\":\n\t\t\tt.LocalPath = item[1]\n\t\tcase \"strip_pre\":\n\t\t\tt.StripPre = item[1]\n\t\tcase \"multi_account\":\n\t\t\tt.MultiAccount = &file.MultiAccount{}\n\t\t\tif common.FileExists(item[1]) {\n\t\t\t\tif b, err := common.ReadAllFromFile(item[1]); err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t} else {\n\t\t\t\t\tif content, err := common.ParseStr(string(b)); err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.MultiAccount.AccountMap = dealMultiUser(content)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn t\n\n}\n\nfunc dealMultiUser(s string) map[string]string {\n\tmultiUserMap := make(map[string]string)\n\tfor _, v := range splitStr(s) {\n\t\titem := strings.Split(v, \"=\")\n\t\tif len(item) == 0 {\n\t\t\tcontinue\n\t\t} else if len(item) == 1 {\n\t\t\titem = append(item, \"\")\n\t\t}\n\t\tmultiUserMap[strings.TrimSpace(item[0])] = item[1]\n\t}\n\treturn multiUserMap\n}\n\nfunc delLocalService(s string) *LocalServer {\n\tl := new(LocalServer)\n\tfor _, v := range splitStr(s) {\n\t\titem := strings.Split(v, \"=\")\n\t\tif len(item) == 0 {\n\t\t\tcontinue\n\t\t} else if len(item) == 1 {\n\t\t\titem = append(item, \"\")\n\t\t}\n\t\tswitch item[0] {\n\t\tcase \"local_port\":\n\t\t\tl.Port = common.GetIntNoErrByStr(item[1])\n\t\tcase \"local_ip\":\n\t\t\tl.Ip = item[1]\n\t\tcase \"password\":\n\t\t\tl.Password = item[1]\n\t\tcase \"target_addr\":\n\t\t\tl.Target = item[1]\n\t\t}\n\t}\n\treturn l\n}\n\nfunc getAllTitle(content string) (arr []string, err error) {\n\tvar re *regexp.Regexp\n\tre, err = regexp.Compile(`(?m)^\\[[^\\[\\]\\r\\n]+\\]`)\n\tif err != nil {\n\t\treturn\n\t}\n\tarr = re.FindAllString(content, -1)\n\tm := make(map[string]bool)\n\tfor _, v := range arr {\n\t\tif _, ok := m[v]; ok {\n\t\t\terr = errors.New(fmt.Sprintf(\"Item names %s are not allowed to be duplicated\", v))\n\t\t\treturn\n\t\t}\n\t\tm[v] = true\n\t}\n\treturn\n}\n\nfunc splitStr(s string) (configDataArr []string) {\n\tif common.IsWindows() {\n\t\tconfigDataArr = strings.Split(s, \"\\r\\n\")\n\t}\n\tif len(configDataArr) < 3 {\n\t\tconfigDataArr = strings.Split(s, \"\\n\")\n\t}\n\treturn\n}\n"
  },
  {
    "path": "lib/config/config_test.go",
    "content": "package config\n\nimport (\n\t\"log\"\n\t\"regexp\"\n\t\"testing\"\n)\n\nfunc TestReg(t *testing.T) {\n\tcontent := `\n[common]\nserver=127.0.0.1:8284\ntp=tcp\nvkey=123\n[web2]\nhost=www.baidu.com\nhost_change=www.sina.com\ntarget=127.0.0.1:8080,127.0.0.1:8082\nheader_cookkile=122123\nheader_user-Agent=122123\n[web2]\nhost=www.baidu.com\nhost_change=www.sina.com\ntarget=127.0.0.1:8080,127.0.0.1:8082\nheader_cookkile=\"122123\"\nheader_user-Agent=122123\n[tunnel1]\ntype=udp\ntarget=127.0.0.1:8080\nport=9001\ncompress=snappy\ncrypt=true\nu=1\np=2\n[tunnel2]\ntype=tcp\ntarget=127.0.0.1:8080\nport=9001\ncompress=snappy\ncrypt=true\nu=1\np=2\n`\n\tre, err := regexp.Compile(`\\[.+?\\]`)\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\tlog.Println(re.FindAllString(content, -1))\n}\n\nfunc TestDealCommon(t *testing.T) {\n\ts := `server=127.0.0.1:8284\ntp=tcp\nvkey=123`\n\tf := new(CommonConfig)\n\tf.Server = \"127.0.0.1:8284\"\n\tf.Tp = \"tcp\"\n\tf.VKey = \"123\"\n\tif c := dealCommon(s); *c != *f {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestGetTitleContent(t *testing.T) {\n\ts := \"[common]\"\n\tif getTitleContent(s) != \"common\" {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "lib/conn/conn.go",
    "content": "package conn\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"ehang.io/nps/lib/goroutine\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/lib/pmux\"\n\t\"ehang.io/nps/lib/rate\"\n\t\"github.com/xtaci/kcp-go\"\n)\n\ntype Conn struct {\n\tConn net.Conn\n\tRb   []byte\n}\n\n//new conn\nfunc NewConn(conn net.Conn) *Conn {\n\treturn &Conn{Conn: conn}\n}\n\nfunc (s *Conn) readRequest(buf []byte) (n int, err error) {\n\tvar rd int\n\tfor {\n\t\trd, err = s.Read(buf[n:])\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tn += rd\n\t\tif n < 4 {\n\t\t\tcontinue\n\t\t}\n\t\tif string(buf[n-4:n]) == \"\\r\\n\\r\\n\" {\n\t\t\treturn\n\t\t}\n\t\t// buf is full, can't contain the request\n\t\tif n == cap(buf) {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t\treturn\n\t\t}\n\t}\n}\n\n//get host 、connection type、method...from connection\nfunc (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) {\n\tvar b [32 * 1024]byte\n\tvar n int\n\tif n, err = s.readRequest(b[:]); err != nil {\n\t\treturn\n\t}\n\trb = b[:n]\n\tr, err = http.ReadRequest(bufio.NewReader(bytes.NewReader(rb)))\n\tif err != nil {\n\t\treturn\n\t}\n\thostPortURL, err := url.Parse(r.Host)\n\tif err != nil {\n\t\taddress = r.Host\n\t\terr = nil\n\t\treturn\n\t}\n\tif hostPortURL.Opaque == \"443\" {\n\t\tif strings.Index(r.Host, \":\") == -1 {\n\t\t\taddress = r.Host + \":443\"\n\t\t} else {\n\t\t\taddress = r.Host\n\t\t}\n\t} else {\n\t\tif strings.Index(r.Host, \":\") == -1 {\n\t\t\taddress = r.Host + \":80\"\n\t\t} else {\n\t\t\taddress = r.Host\n\t\t}\n\t}\n\treturn\n}\n\nfunc (s *Conn) GetShortLenContent() (b []byte, err error) {\n\tvar l int\n\tif l, err = s.GetLen(); err != nil {\n\t\treturn\n\t}\n\tif l < 0 || l > 32<<10 {\n\t\terr = errors.New(\"read length error\")\n\t\treturn\n\t}\n\treturn s.GetShortContent(l)\n}\n\nfunc (s *Conn) GetShortContent(l int) (b []byte, err error) {\n\tbuf := make([]byte, l)\n\treturn buf, binary.Read(s, binary.LittleEndian, &buf)\n}\n\n//读取指定长度内容\nfunc (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {\n\tif cLen > len(buf) || cLen <= 0 {\n\t\treturn 0, errors.New(\"长度错误\" + strconv.Itoa(cLen))\n\t}\n\tif n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen {\n\t\treturn n, errors.New(\"Error reading specified length \" + err.Error())\n\t}\n\treturn cLen, nil\n}\n\nfunc (s *Conn) GetLen() (int, error) {\n\tvar l int32\n\terr := binary.Read(s, binary.LittleEndian, &l)\n\treturn int(l), err\n}\n\nfunc (s *Conn) WriteLenContent(buf []byte) (err error) {\n\tvar b []byte\n\tif b, err = GetLenBytes(buf); err != nil {\n\t\treturn\n\t}\n\treturn binary.Write(s.Conn, binary.LittleEndian, b)\n}\n\n//read flag\nfunc (s *Conn) ReadFlag() (string, error) {\n\tbuf := make([]byte, 4)\n\treturn string(buf), binary.Read(s, binary.LittleEndian, &buf)\n}\n\n//set alive\nfunc (s *Conn) SetAlive(tp string) {\n\tswitch s.Conn.(type) {\n\tcase *kcp.UDPSession:\n\t\ts.Conn.(*kcp.UDPSession).SetReadDeadline(time.Time{})\n\tcase *net.TCPConn:\n\t\tconn := s.Conn.(*net.TCPConn)\n\t\tconn.SetReadDeadline(time.Time{})\n\t\t//conn.SetKeepAlive(false)\n\t\t//conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))\n\tcase *pmux.PortConn:\n\t\ts.Conn.(*pmux.PortConn).SetReadDeadline(time.Time{})\n\t}\n}\n\n//set read deadline\nfunc (s *Conn) SetReadDeadlineBySecond(t time.Duration) {\n\tswitch s.Conn.(type) {\n\tcase *kcp.UDPSession:\n\t\ts.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))\n\tcase *net.TCPConn:\n\t\ts.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))\n\tcase *pmux.PortConn:\n\t\ts.Conn.(*pmux.PortConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))\n\t}\n}\n\n//get link info from conn\nfunc (s *Conn) GetLinkInfo() (lk *Link, err error) {\n\terr = s.getInfo(&lk)\n\treturn\n}\n\n//send info for link\nfunc (s *Conn) SendHealthInfo(info, status string) (int, error) {\n\traw := bytes.NewBuffer([]byte{})\n\tcommon.BinaryWrite(raw, info, status)\n\treturn s.Write(raw.Bytes())\n}\n\n//get health info from conn\nfunc (s *Conn) GetHealthInfo() (info string, status bool, err error) {\n\tvar l int\n\tbuf := common.BufPoolMax.Get().([]byte)\n\tdefer common.PutBufPoolMax(buf)\n\tif l, err = s.GetLen(); err != nil {\n\t\treturn\n\t} else if _, err = s.ReadLen(l, buf); err != nil {\n\t\treturn\n\t} else {\n\t\tarr := strings.Split(string(buf[:l]), common.CONN_DATA_SEQ)\n\t\tif len(arr) >= 2 {\n\t\t\treturn arr[0], common.GetBoolByStr(arr[1]), nil\n\t\t}\n\t}\n\treturn \"\", false, errors.New(\"receive health info error\")\n}\n\n//get task info\nfunc (s *Conn) GetHostInfo() (h *file.Host, err error) {\n\terr = s.getInfo(&h)\n\th.Id = int(file.GetDb().JsonDb.GetHostId())\n\th.Flow = new(file.Flow)\n\th.NoStore = true\n\treturn\n}\n\n//get task info\nfunc (s *Conn) GetConfigInfo() (c *file.Client, err error) {\n\terr = s.getInfo(&c)\n\tc.NoStore = true\n\tc.Status = true\n\tif c.Flow == nil {\n\t\tc.Flow = new(file.Flow)\n\t}\n\tc.NoDisplay = false\n\treturn\n}\n\n//get task info\nfunc (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) {\n\terr = s.getInfo(&t)\n\tt.Id = int(file.GetDb().JsonDb.GetTaskId())\n\tt.NoStore = true\n\tt.Flow = new(file.Flow)\n\treturn\n}\n\n//send  info\nfunc (s *Conn) SendInfo(t interface{}, flag string) (int, error) {\n\t/*\n\t\tThe task info is formed as follows:\n\t\t+----+-----+---------+\n\t\t|type| len | content |\n\t\t+----+---------------+\n\t\t| 4  |  4  |   ...   |\n\t\t+----+---------------+\n\t*/\n\traw := bytes.NewBuffer([]byte{})\n\tif flag != \"\" {\n\t\tbinary.Write(raw, binary.LittleEndian, []byte(flag))\n\t}\n\tb, err := json.Marshal(t)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tlenBytes, err := GetLenBytes(b)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tbinary.Write(raw, binary.LittleEndian, lenBytes)\n\treturn s.Write(raw.Bytes())\n}\n\n//get task info\nfunc (s *Conn) getInfo(t interface{}) (err error) {\n\tvar l int\n\tbuf := common.BufPoolMax.Get().([]byte)\n\tdefer common.PutBufPoolMax(buf)\n\tif l, err = s.GetLen(); err != nil {\n\t\treturn\n\t} else if _, err = s.ReadLen(l, buf); err != nil {\n\t\treturn\n\t} else {\n\t\tjson.Unmarshal(buf[:l], &t)\n\t}\n\treturn\n}\n\n//close\nfunc (s *Conn) Close() error {\n\treturn s.Conn.Close()\n}\n\n//write\nfunc (s *Conn) Write(b []byte) (int, error) {\n\treturn s.Conn.Write(b)\n}\n\n//read\nfunc (s *Conn) Read(b []byte) (n int, err error) {\n\tif s.Rb != nil {\n\t\t//if the rb is not nil ,read rb first\n\t\tif len(s.Rb) > 0 {\n\t\t\tn = copy(b, s.Rb)\n\t\t\ts.Rb = s.Rb[n:]\n\t\t\treturn\n\t\t}\n\t\ts.Rb = nil\n\t}\n\treturn s.Conn.Read(b)\n}\n\n//write sign flag\nfunc (s *Conn) WriteClose() (int, error) {\n\treturn s.Write([]byte(common.RES_CLOSE))\n}\n\n//write main\nfunc (s *Conn) WriteMain() (int, error) {\n\treturn s.Write([]byte(common.WORK_MAIN))\n}\n\n//write main\nfunc (s *Conn) WriteConfig() (int, error) {\n\treturn s.Write([]byte(common.WORK_CONFIG))\n}\n\n//write chan\nfunc (s *Conn) WriteChan() (int, error) {\n\treturn s.Write([]byte(common.WORK_CHAN))\n}\n\n//get task or host result of add\nfunc (s *Conn) GetAddStatus() (b bool) {\n\tbinary.Read(s.Conn, binary.LittleEndian, &b)\n\treturn\n}\n\nfunc (s *Conn) WriteAddOk() error {\n\treturn binary.Write(s.Conn, binary.LittleEndian, true)\n}\n\nfunc (s *Conn) WriteAddFail() error {\n\tdefer s.Close()\n\treturn binary.Write(s.Conn, binary.LittleEndian, false)\n}\n\nfunc (s *Conn) LocalAddr() net.Addr {\n\treturn s.Conn.LocalAddr()\n}\n\nfunc (s *Conn) RemoteAddr() net.Addr {\n\treturn s.Conn.RemoteAddr()\n}\n\nfunc (s *Conn) SetDeadline(t time.Time) error {\n\treturn s.Conn.SetDeadline(t)\n}\n\nfunc (s *Conn) SetWriteDeadline(t time.Time) error {\n\treturn s.Conn.SetWriteDeadline(t)\n}\n\nfunc (s *Conn) SetReadDeadline(t time.Time) error {\n\treturn s.Conn.SetReadDeadline(t)\n}\n\n//get the assembled amount data(len 4 and content)\nfunc GetLenBytes(buf []byte) (b []byte, err error) {\n\traw := bytes.NewBuffer([]byte{})\n\tif err = binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Write(raw, binary.LittleEndian, buf); err != nil {\n\t\treturn\n\t}\n\tb = raw.Bytes()\n\treturn\n}\n\n//udp connection setting\nfunc SetUdpSession(sess *kcp.UDPSession) {\n\tsess.SetStreamMode(true)\n\tsess.SetWindowSize(1024, 1024)\n\tsess.SetReadBuffer(64 * 1024)\n\tsess.SetWriteBuffer(64 * 1024)\n\tsess.SetNoDelay(1, 10, 2, 1)\n\tsess.SetMtu(1600)\n\tsess.SetACKNoDelay(true)\n\tsess.SetWriteDelay(false)\n}\n\n//conn1 mux conn\nfunc CopyWaitGroup(conn1, conn2 net.Conn, crypt bool, snappy bool, rate *rate.Rate, flow *file.Flow, isServer bool, rb []byte) {\n\t//var in, out int64\n\t//var wg sync.WaitGroup\n\tconnHandle := GetConn(conn1, crypt, snappy, rate, isServer)\n\tif rb != nil {\n\t\tconnHandle.Write(rb)\n\t}\n\t//go func(in *int64) {\n\t//\twg.Add(1)\n\t//\t*in, _ = common.CopyBuffer(connHandle, conn2)\n\t//\tconnHandle.Close()\n\t//\tconn2.Close()\n\t//\twg.Done()\n\t//}(&in)\n\t//out, _ = common.CopyBuffer(conn2, connHandle)\n\t//connHandle.Close()\n\t//conn2.Close()\n\t//wg.Wait()\n\t//if flow != nil {\n\t//\tflow.Add(in, out)\n\t//}\n\twg := new(sync.WaitGroup)\n\twg.Add(1)\n\terr := goroutine.CopyConnsPool.Invoke(goroutine.NewConns(connHandle, conn2, flow, wg))\n\twg.Wait()\n\tif err != nil {\n\t\tlogs.Error(err)\n\t}\n}\n\n//get crypt or snappy conn\nfunc GetConn(conn net.Conn, cpt, snappy bool, rt *rate.Rate, isServer bool) io.ReadWriteCloser {\n\tif cpt {\n\t\tif isServer {\n\t\t\treturn rate.NewRateConn(crypt.NewTlsServerConn(conn), rt)\n\t\t}\n\t\treturn rate.NewRateConn(crypt.NewTlsClientConn(conn), rt)\n\t} else if snappy {\n\t\treturn rate.NewRateConn(NewSnappyConn(conn), rt)\n\t}\n\treturn rate.NewRateConn(conn, rt)\n}\n\ntype LenConn struct {\n\tconn io.Writer\n\tLen  int\n}\n\nfunc NewLenConn(conn io.Writer) *LenConn {\n\treturn &LenConn{conn: conn}\n}\nfunc (c *LenConn) Write(p []byte) (n int, err error) {\n\tn, err = c.conn.Write(p)\n\tc.Len += n\n\treturn\n}\n"
  },
  {
    "path": "lib/conn/link.go",
    "content": "package conn\n\nimport \"time\"\n\ntype Secret struct {\n\tPassword string\n\tConn     *Conn\n}\n\nfunc NewSecret(p string, conn *Conn) *Secret {\n\treturn &Secret{\n\t\tPassword: p,\n\t\tConn:     conn,\n\t}\n}\n\ntype Link struct {\n\tConnType   string //连接类型\n\tHost       string //目标\n\tCrypt      bool   //加密\n\tCompress   bool\n\tLocalProxy bool\n\tRemoteAddr string\n\tOption     Options\n}\n\ntype Option func(*Options)\n\ntype Options struct {\n\tTimeout time.Duration\n}\n\nvar defaultTimeOut = time.Second * 5\n\nfunc NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool, opts ...Option) *Link {\n\toptions := newOptions(opts...)\n\n\treturn &Link{\n\t\tRemoteAddr: remoteAddr,\n\t\tConnType:   connType,\n\t\tHost:       host,\n\t\tCrypt:      crypt,\n\t\tCompress:   compress,\n\t\tLocalProxy: localProxy,\n\t\tOption:     options,\n\t}\n}\n\nfunc newOptions(opts ...Option) Options {\n\topt := Options{\n\t\tTimeout: defaultTimeOut,\n\t}\n\tfor _, o := range opts {\n\t\to(&opt)\n\t}\n\treturn opt\n}\n\nfunc LinkTimeout(t time.Duration) Option {\n\treturn func(opt *Options) {\n\t\topt.Timeout = t\n\t}\n}\n"
  },
  {
    "path": "lib/conn/listener.go",
    "content": "package conn\n\nimport (\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/xtaci/kcp-go\"\n)\n\nfunc NewTcpListenerAndProcess(addr string, f func(c net.Conn), listener *net.Listener) error {\n\tvar err error\n\t*listener, err = net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tAccept(*listener, f)\n\treturn nil\n}\n\nfunc NewKcpListenerAndProcess(addr string, f func(c net.Conn)) error {\n\tkcpListener, err := kcp.ListenWithOptions(addr, nil, 150, 3)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\treturn err\n\t}\n\tfor {\n\t\tc, err := kcpListener.AcceptKCP()\n\t\tSetUdpSession(c)\n\t\tif err != nil {\n\t\t\tlogs.Warn(err)\n\t\t\tcontinue\n\t\t}\n\t\tgo f(c)\n\t}\n\treturn nil\n}\n\nfunc Accept(l net.Listener, f func(c net.Conn)) {\n\tfor {\n\t\tc, err := l.Accept()\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif strings.Contains(err.Error(), \"the mux has closed\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlogs.Warn(err)\n\t\t\tcontinue\n\t\t}\n\t\tif c == nil {\n\t\t\tlogs.Warn(\"nil connection\")\n\t\t\tbreak\n\t\t}\n\t\tgo f(c)\n\t}\n}\n"
  },
  {
    "path": "lib/conn/snappy.go",
    "content": "package conn\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/golang/snappy\"\n)\n\ntype SnappyConn struct {\n\tw *snappy.Writer\n\tr *snappy.Reader\n\tc io.Closer\n}\n\nfunc NewSnappyConn(conn io.ReadWriteCloser) *SnappyConn {\n\tc := new(SnappyConn)\n\tc.w = snappy.NewBufferedWriter(conn)\n\tc.r = snappy.NewReader(conn)\n\tc.c = conn.(io.Closer)\n\treturn c\n}\n\n//snappy压缩写\nfunc (s *SnappyConn) Write(b []byte) (n int, err error) {\n\tif n, err = s.w.Write(b); err != nil {\n\t\treturn\n\t}\n\tif err = s.w.Flush(); err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\n//snappy压缩读\nfunc (s *SnappyConn) Read(b []byte) (n int, err error) {\n\treturn s.r.Read(b)\n}\n\nfunc (s *SnappyConn) Close() error {\n\terr := s.w.Close()\n\terr2 := s.c.Close()\n\tif err != nil && err2 == nil {\n\t\treturn err\n\t}\n\tif err == nil && err2 != nil {\n\t\treturn err2\n\t}\n\tif err != nil && err2 != nil {\n\t\treturn errors.New(err.Error() + err2.Error())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "lib/crypt/clientHello.go",
    "content": "package crypt\n\nimport (\n\t\"strings\"\n)\n\ntype CurveID uint16\ntype SignatureScheme uint16\n\nconst (\n\tstatusTypeOCSP               uint8  = 1\n\textensionServerName          uint16 = 0\n\textensionStatusRequest       uint16 = 5\n\textensionSupportedCurves     uint16 = 10\n\textensionSupportedPoints     uint16 = 11\n\textensionSignatureAlgorithms uint16 = 13\n\textensionALPN                uint16 = 16\n\textensionSCT                 uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6\n\textensionSessionTicket       uint16 = 35\n\textensionNextProtoNeg        uint16 = 13172 // not IANA assigned\n\textensionRenegotiationInfo   uint16 = 0xff01\n\tscsvRenegotiation            uint16 = 0x00ff\n)\n\ntype ClientHelloMsg struct {\n\traw                          []byte\n\tvers                         uint16\n\trandom                       []byte\n\tsessionId                    []byte\n\tcipherSuites                 []uint16\n\tcompressionMethods           []uint8\n\tnextProtoNeg                 bool\n\tserverName                   string\n\tocspStapling                 bool\n\tscts                         bool\n\tsupportedCurves              []CurveID\n\tsupportedPoints              []uint8\n\tticketSupported              bool\n\tsessionTicket                []uint8\n\tsupportedSignatureAlgorithms []SignatureScheme\n\tsecureRenegotiation          []byte\n\tsecureRenegotiationSupported bool\n\talpnProtocols                []string\n}\n\nfunc (m *ClientHelloMsg) GetServerName() string {\n\treturn m.serverName\n}\n\nfunc (m *ClientHelloMsg) Unmarshal(data []byte) bool {\n\tif len(data) < 42 {\n\t\treturn false\n\t}\n\tm.raw = data\n\tm.vers = uint16(data[4])<<8 | uint16(data[5])\n\tm.random = data[6:38]\n\tsessionIdLen := int(data[38])\n\tif sessionIdLen > 32 || len(data) < 39+sessionIdLen {\n\t\treturn false\n\t}\n\tm.sessionId = data[39 : 39+sessionIdLen]\n\tdata = data[39+sessionIdLen:]\n\tif len(data) < 2 {\n\t\treturn false\n\t}\n\t// cipherSuiteLen is the number of bytes of cipher suite numbers. Since\n\t// they are uint16s, the number must be even.\n\tcipherSuiteLen := int(data[0])<<8 | int(data[1])\n\tif cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {\n\t\treturn false\n\t}\n\tnumCipherSuites := cipherSuiteLen / 2\n\tm.cipherSuites = make([]uint16, numCipherSuites)\n\tfor i := 0; i < numCipherSuites; i++ {\n\t\tm.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])\n\t\tif m.cipherSuites[i] == scsvRenegotiation {\n\t\t\tm.secureRenegotiationSupported = true\n\t\t}\n\t}\n\tdata = data[2+cipherSuiteLen:]\n\tif len(data) < 1 {\n\t\treturn false\n\t}\n\tcompressionMethodsLen := int(data[0])\n\tif len(data) < 1+compressionMethodsLen {\n\t\treturn false\n\t}\n\tm.compressionMethods = data[1 : 1+compressionMethodsLen]\n\tdata = data[1+compressionMethodsLen:]\n\n\tm.nextProtoNeg = false\n\tm.serverName = \"\"\n\tm.ocspStapling = false\n\tm.ticketSupported = false\n\tm.sessionTicket = nil\n\tm.supportedSignatureAlgorithms = nil\n\tm.alpnProtocols = nil\n\tm.scts = false\n\n\tif len(data) == 0 {\n\t\t// ClientHello is optionally followed by extension data\n\t\treturn true\n\t}\n\tif len(data) < 2 {\n\t\treturn false\n\t}\n\n\textensionsLength := int(data[0])<<8 | int(data[1])\n\tdata = data[2:]\n\tif extensionsLength != len(data) {\n\t\treturn false\n\t}\n\n\tfor len(data) != 0 {\n\t\tif len(data) < 4 {\n\t\t\treturn false\n\t\t}\n\t\textension := uint16(data[0])<<8 | uint16(data[1])\n\t\tlength := int(data[2])<<8 | int(data[3])\n\t\tdata = data[4:]\n\t\tif len(data) < length {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch extension {\n\t\tcase extensionServerName:\n\t\t\td := data[:length]\n\t\t\tif len(d) < 2 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tnamesLen := int(d[0])<<8 | int(d[1])\n\t\t\td = d[2:]\n\t\t\tif len(d) != namesLen {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tfor len(d) > 0 {\n\t\t\t\tif len(d) < 3 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tnameType := d[0]\n\t\t\t\tnameLen := int(d[1])<<8 | int(d[2])\n\t\t\t\td = d[3:]\n\t\t\t\tif len(d) < nameLen {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif nameType == 0 {\n\t\t\t\t\tm.serverName = string(d[:nameLen])\n\t\t\t\t\t// An SNI value may not include a\n\t\t\t\t\t// trailing dot. See\n\t\t\t\t\t// https://tools.ietf.org/html/rfc6066#section-3.\n\t\t\t\t\tif strings.HasSuffix(m.serverName, \".\") {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\td = d[nameLen:]\n\t\t\t}\n\t\tcase extensionNextProtoNeg:\n\t\t\tif length > 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tm.nextProtoNeg = true\n\t\tcase extensionStatusRequest:\n\t\t\tm.ocspStapling = length > 0 && data[0] == statusTypeOCSP\n\t\tcase extensionSupportedCurves:\n\t\t\t// https://tools.ietf.org/html/rfc4492#section-5.5.1\n\t\t\tif length < 2 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tl := int(data[0])<<8 | int(data[1])\n\t\t\tif l%2 == 1 || length != l+2 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tnumCurves := l / 2\n\t\t\tm.supportedCurves = make([]CurveID, numCurves)\n\t\t\td := data[2:]\n\t\t\tfor i := 0; i < numCurves; i++ {\n\t\t\t\tm.supportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1])\n\t\t\t\td = d[2:]\n\t\t\t}\n\t\tcase extensionSupportedPoints:\n\t\t\t// https://tools.ietf.org/html/rfc4492#section-5.5.2\n\t\t\tif length < 1 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tl := int(data[0])\n\t\t\tif length != l+1 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tm.supportedPoints = make([]uint8, l)\n\t\t\tcopy(m.supportedPoints, data[1:])\n\t\tcase extensionSessionTicket:\n\t\t\t// https://tools.ietf.org/html/rfc5077#section-3.2\n\t\t\tm.ticketSupported = true\n\t\t\tm.sessionTicket = data[:length]\n\t\tcase extensionSignatureAlgorithms:\n\t\t\t// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1\n\t\t\tif length < 2 || length&1 != 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tl := int(data[0])<<8 | int(data[1])\n\t\t\tif l != length-2 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tn := l / 2\n\t\t\td := data[2:]\n\t\t\tm.supportedSignatureAlgorithms = make([]SignatureScheme, n)\n\t\t\tfor i := range m.supportedSignatureAlgorithms {\n\t\t\t\tm.supportedSignatureAlgorithms[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1])\n\t\t\t\td = d[2:]\n\t\t\t}\n\t\tcase extensionRenegotiationInfo:\n\t\t\tif length == 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\td := data[:length]\n\t\t\tl := int(d[0])\n\t\t\td = d[1:]\n\t\t\tif l != len(d) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tm.secureRenegotiation = d\n\t\t\tm.secureRenegotiationSupported = true\n\t\tcase extensionALPN:\n\t\t\tif length < 2 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tl := int(data[0])<<8 | int(data[1])\n\t\t\tif l != length-2 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\td := data[2:length]\n\t\t\tfor len(d) != 0 {\n\t\t\t\tstringLen := int(d[0])\n\t\t\t\td = d[1:]\n\t\t\t\tif stringLen == 0 || stringLen > len(d) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tm.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen]))\n\t\t\t\td = d[stringLen:]\n\t\t\t}\n\t\tcase extensionSCT:\n\t\t\tm.scts = true\n\t\t\tif length != 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tdata = data[length:]\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "lib/crypt/crypt.go",
    "content": "package crypt\n\nimport (\n\t\"bytes\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"math/rand\"\n\t\"time\"\n)\n\n//en\nfunc AesEncrypt(origData, key []byte) ([]byte, error) {\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tblockSize := block.BlockSize()\n\torigData = PKCS5Padding(origData, blockSize)\n\tblockMode := cipher.NewCBCEncrypter(block, key[:blockSize])\n\tcrypted := make([]byte, len(origData))\n\tblockMode.CryptBlocks(crypted, origData)\n\treturn crypted, nil\n}\n\n//de\nfunc AesDecrypt(crypted, key []byte) ([]byte, error) {\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tblockSize := block.BlockSize()\n\tblockMode := cipher.NewCBCDecrypter(block, key[:blockSize])\n\torigData := make([]byte, len(crypted))\n\tblockMode.CryptBlocks(origData, crypted)\n\terr, origData = PKCS5UnPadding(origData)\n\treturn origData, err\n}\n\n//Completion when the length is insufficient\nfunc PKCS5Padding(ciphertext []byte, blockSize int) []byte {\n\tpadding := blockSize - len(ciphertext)%blockSize\n\tpadtext := bytes.Repeat([]byte{byte(padding)}, padding)\n\treturn append(ciphertext, padtext...)\n}\n\n//Remove excess\nfunc PKCS5UnPadding(origData []byte) (error, []byte) {\n\tlength := len(origData)\n\tunpadding := int(origData[length-1])\n\tif (length - unpadding) < 0 {\n\t\treturn errors.New(\"len error\"), nil\n\t}\n\treturn nil, origData[:(length - unpadding)]\n}\n\n//Generate 32-bit MD5 strings\nfunc Md5(s string) string {\n\th := md5.New()\n\th.Write([]byte(s))\n\treturn hex.EncodeToString(h.Sum(nil))\n}\n\n//Generating Random Verification Key\nfunc GetRandomString(l int) string {\n\tstr := \"0123456789abcdefghijklmnopqrstuvwxyz\"\n\tbytes := []byte(str)\n\tresult := []byte{}\n\tr := rand.New(rand.NewSource(time.Now().UnixNano()))\n\tfor i := 0; i < l; i++ {\n\t\tresult = append(result, bytes[r.Intn(len(bytes))])\n\t}\n\treturn string(result)\n}\n"
  },
  {
    "path": "lib/crypt/tls.go",
    "content": "package crypt\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"log\"\n\t\"math/big\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/astaxie/beego/logs\"\n)\n\nvar (\n\tcert tls.Certificate\n)\n\nfunc InitTls() {\n\tc, k, err := generateKeyPair(\"NPS Org\")\n\tif err == nil {\n\t\tcert, err = tls.X509KeyPair(c, k)\n\t}\n\tif err != nil {\n\t\tlog.Fatalln(\"Error initializing crypto certs\", err)\n\t}\n}\n\nfunc NewTlsServerConn(conn net.Conn) net.Conn {\n\tvar err error\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\tos.Exit(0)\n\t\treturn nil\n\t}\n\tconfig := &tls.Config{Certificates: []tls.Certificate{cert}}\n\treturn tls.Server(conn, config)\n}\n\nfunc NewTlsClientConn(conn net.Conn) net.Conn {\n\tconf := &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t}\n\treturn tls.Client(conn, conf)\n}\n\nfunc generateKeyPair(CommonName string) (rawCert, rawKey []byte, err error) {\n\t// Create private key and self-signed certificate\n\t// Adapted from https://golang.org/src/crypto/tls/generate_cert.go\n\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn\n\t}\n\tvalidFor := time.Hour * 24 * 365 * 10 // ten years\n\tnotBefore := time.Now()\n\tnotAfter := notBefore.Add(validFor)\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\ttemplate := x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"My Company Name LTD.\"},\n\t\t\tCommonName:   CommonName,\n\t\t\tCountry:      []string{\"US\"},\n\t\t},\n\t\tNotBefore: notBefore,\n\t\tNotAfter:  notAfter,\n\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)\n\tif err != nil {\n\t\treturn\n\t}\n\n\trawCert = pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: derBytes})\n\trawKey = pem.EncodeToMemory(&pem.Block{Type: \"RSA PRIVATE KEY\", Bytes: x509.MarshalPKCS1PrivateKey(priv)})\n\n\treturn\n}\n"
  },
  {
    "path": "lib/daemon/daemon.go",
    "content": "package daemon\n\nimport (\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"ehang.io/nps/lib/common\"\n)\n\nfunc InitDaemon(f string, runPath string, pidPath string) {\n\tif len(os.Args) < 2 {\n\t\treturn\n\t}\n\tvar args []string\n\targs = append(args, os.Args[0])\n\tif len(os.Args) >= 2 {\n\t\targs = append(args, os.Args[2:]...)\n\t}\n\targs = append(args, \"-log=file\")\n\tswitch os.Args[1] {\n\tcase \"start\":\n\t\tstart(args, f, pidPath, runPath)\n\t\tos.Exit(0)\n\tcase \"stop\":\n\t\tstop(f, args[0], pidPath)\n\t\tos.Exit(0)\n\tcase \"restart\":\n\t\tstop(f, args[0], pidPath)\n\t\tstart(args, f, pidPath, runPath)\n\t\tos.Exit(0)\n\tcase \"status\":\n\t\tif status(f, pidPath) {\n\t\t\tlog.Printf(\"%s is running\", f)\n\t\t} else {\n\t\t\tlog.Printf(\"%s is not running\", f)\n\t\t}\n\t\tos.Exit(0)\n\tcase \"reload\":\n\t\treload(f, pidPath)\n\t\tos.Exit(0)\n\t}\n}\n\nfunc reload(f string, pidPath string) {\n\tif f == \"nps\" && !common.IsWindows() && !status(f, pidPath) {\n\t\tlog.Println(\"reload fail\")\n\t\treturn\n\t}\n\tvar c *exec.Cmd\n\tvar err error\n\tb, err := ioutil.ReadFile(filepath.Join(pidPath, f+\".pid\"))\n\tif err == nil {\n\t\tc = exec.Command(\"/bin/bash\", \"-c\", `kill -30 `+string(b))\n\t} else {\n\t\tlog.Fatalln(\"reload error,pid file does not exist\")\n\t}\n\tif c.Run() == nil {\n\t\tlog.Println(\"reload success\")\n\t} else {\n\t\tlog.Println(\"reload fail\")\n\t}\n}\n\nfunc status(f string, pidPath string) bool {\n\tvar cmd *exec.Cmd\n\tb, err := ioutil.ReadFile(filepath.Join(pidPath, f+\".pid\"))\n\tif err == nil {\n\t\tif !common.IsWindows() {\n\t\t\tcmd = exec.Command(\"/bin/sh\", \"-c\", \"ps -ax | awk '{ print $1 }' | grep \"+string(b))\n\t\t} else {\n\t\t\tcmd = exec.Command(\"tasklist\")\n\t\t}\n\t\tout, _ := cmd.Output()\n\t\tif strings.Index(string(out), string(b)) > -1 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc start(osArgs []string, f string, pidPath, runPath string) {\n\tif status(f, pidPath) {\n\t\tlog.Printf(\" %s is running\", f)\n\t\treturn\n\t}\n\tcmd := exec.Command(osArgs[0], osArgs[1:]...)\n\tcmd.Start()\n\tif cmd.Process.Pid > 0 {\n\t\tlog.Println(\"start ok , pid:\", cmd.Process.Pid, \"config path:\", runPath)\n\t\td1 := []byte(strconv.Itoa(cmd.Process.Pid))\n\t\tioutil.WriteFile(filepath.Join(pidPath, f+\".pid\"), d1, 0600)\n\t} else {\n\t\tlog.Println(\"start error\")\n\t}\n}\n\nfunc stop(f string, p string, pidPath string) {\n\tif !status(f, pidPath) {\n\t\tlog.Printf(\" %s is not running\", f)\n\t\treturn\n\t}\n\tvar c *exec.Cmd\n\tvar err error\n\tif common.IsWindows() {\n\t\tp := strings.Split(p, `\\`)\n\t\tc = exec.Command(\"taskkill\", \"/F\", \"/IM\", p[len(p)-1])\n\t} else {\n\t\tb, err := ioutil.ReadFile(filepath.Join(pidPath, f+\".pid\"))\n\t\tif err == nil {\n\t\t\tc = exec.Command(\"/bin/bash\", \"-c\", `kill -9 `+string(b))\n\t\t} else {\n\t\t\tlog.Fatalln(\"stop error,pid file does not exist\")\n\t\t}\n\t}\n\terr = c.Run()\n\tif err != nil {\n\t\tlog.Println(\"stop error,\", err)\n\t} else {\n\t\tlog.Println(\"stop ok\")\n\t}\n}\n"
  },
  {
    "path": "lib/daemon/reload.go",
    "content": "// +build !windows\n\npackage daemon\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"syscall\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"github.com/astaxie/beego\"\n)\n\nfunc init() {\n\ts := make(chan os.Signal, 1)\n\tsignal.Notify(s, syscall.SIGUSR1)\n\tgo func() {\n\t\tfor {\n\t\t\t<-s\n\t\t\tbeego.LoadAppConfig(\"ini\", filepath.Join(common.GetRunPath(), \"conf\", \"nps.conf\"))\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "lib/file/db.go",
    "content": "package file\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/rate\"\n)\n\ntype DbUtils struct {\n\tJsonDb *JsonDb\n}\n\nvar (\n\tDb   *DbUtils\n\tonce sync.Once\n)\n\n//init csv from file\nfunc GetDb() *DbUtils {\n\tonce.Do(func() {\n\t\tjsonDb := NewJsonDb(common.GetRunPath())\n\t\tjsonDb.LoadClientFromJsonFile()\n\t\tjsonDb.LoadTaskFromJsonFile()\n\t\tjsonDb.LoadHostFromJsonFile()\n\t\tDb = &DbUtils{JsonDb: jsonDb}\n\t})\n\treturn Db\n}\n\nfunc GetMapKeys(m sync.Map, isSort bool, sortKey, order string) (keys []int) {\n\tif sortKey != \"\" && isSort {\n\t\treturn sortClientByKey(m, sortKey, order)\n\t}\n\tm.Range(func(key, value interface{}) bool {\n\t\tkeys = append(keys, key.(int))\n\t\treturn true\n\t})\n\tsort.Ints(keys)\n\treturn\n}\n\nfunc (s *DbUtils) GetClientList(start, length int, search, sort, order string, clientId int) ([]*Client, int) {\n\tlist := make([]*Client, 0)\n\tvar cnt int\n\tkeys := GetMapKeys(s.JsonDb.Clients, true, sort, order)\n\tfor _, key := range keys {\n\t\tif value, ok := s.JsonDb.Clients.Load(key); ok {\n\t\t\tv := value.(*Client)\n\t\t\tif v.NoDisplay {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif clientId != 0 && clientId != v.Id {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif search != \"\" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.VerifyKey, search) || strings.Contains(v.Remark, search)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcnt++\n\t\t\tif start--; start < 0 {\n\t\t\t\tif length--; length >= 0 {\n\t\t\t\t\tlist = append(list, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn list, cnt\n}\n\nfunc (s *DbUtils) GetIdByVerifyKey(vKey string, addr string) (id int, err error) {\n\tvar exist bool\n\ts.JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Client)\n\t\tif common.Getverifyval(v.VerifyKey) == vKey && v.Status {\n\t\t\tv.Addr = common.GetIpByAddr(addr)\n\t\t\tid = v.Id\n\t\t\texist = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif exist {\n\t\treturn\n\t}\n\treturn 0, errors.New(\"not found\")\n}\n\nfunc (s *DbUtils) NewTask(t *Tunnel) (err error) {\n\ts.JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Tunnel)\n\t\tif (v.Mode == \"secret\" || v.Mode == \"p2p\") && v.Password == t.Password {\n\t\t\terr = errors.New(fmt.Sprintf(\"secret mode keys %s must be unique\", t.Password))\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\tt.Flow = new(Flow)\n\ts.JsonDb.Tasks.Store(t.Id, t)\n\ts.JsonDb.StoreTasksToJsonFile()\n\treturn\n}\n\nfunc (s *DbUtils) UpdateTask(t *Tunnel) error {\n\ts.JsonDb.Tasks.Store(t.Id, t)\n\ts.JsonDb.StoreTasksToJsonFile()\n\treturn nil\n}\n\nfunc (s *DbUtils) DelTask(id int) error {\n\ts.JsonDb.Tasks.Delete(id)\n\ts.JsonDb.StoreTasksToJsonFile()\n\treturn nil\n}\n\n//md5 password\nfunc (s *DbUtils) GetTaskByMd5Password(p string) (t *Tunnel) {\n\ts.JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tif crypt.Md5(value.(*Tunnel).Password) == p {\n\t\t\tt = value.(*Tunnel)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\nfunc (s *DbUtils) GetTask(id int) (t *Tunnel, err error) {\n\tif v, ok := s.JsonDb.Tasks.Load(id); ok {\n\t\tt = v.(*Tunnel)\n\t\treturn\n\t}\n\terr = errors.New(\"not found\")\n\treturn\n}\n\nfunc (s *DbUtils) DelHost(id int) error {\n\ts.JsonDb.Hosts.Delete(id)\n\ts.JsonDb.StoreHostToJsonFile()\n\treturn nil\n}\n\nfunc (s *DbUtils) IsHostExist(h *Host) bool {\n\tvar exist bool\n\ts.JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Host)\n\t\tif v.Id != h.Id && v.Host == h.Host && h.Location == v.Location && (v.Scheme == \"all\" || v.Scheme == h.Scheme) {\n\t\t\texist = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn exist\n}\n\nfunc (s *DbUtils) NewHost(t *Host) error {\n\tif t.Location == \"\" {\n\t\tt.Location = \"/\"\n\t}\n\tif s.IsHostExist(t) {\n\t\treturn errors.New(\"host has exist\")\n\t}\n\tt.Flow = new(Flow)\n\ts.JsonDb.Hosts.Store(t.Id, t)\n\ts.JsonDb.StoreHostToJsonFile()\n\treturn nil\n}\n\nfunc (s *DbUtils) GetHost(start, length int, id int, search string) ([]*Host, int) {\n\tlist := make([]*Host, 0)\n\tvar cnt int\n\tkeys := GetMapKeys(s.JsonDb.Hosts, false, \"\", \"\")\n\tfor _, key := range keys {\n\t\tif value, ok := s.JsonDb.Hosts.Load(key); ok {\n\t\t\tv := value.(*Host)\n\t\t\tif search != \"\" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.Host, search) || strings.Contains(v.Remark, search)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif id == 0 || v.Client.Id == id {\n\t\t\t\tcnt++\n\t\t\t\tif start--; start < 0 {\n\t\t\t\t\tif length--; length >= 0 {\n\t\t\t\t\t\tlist = append(list, v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn list, cnt\n}\n\nfunc (s *DbUtils) DelClient(id int) error {\n\ts.JsonDb.Clients.Delete(id)\n\ts.JsonDb.StoreClientsToJsonFile()\n\treturn nil\n}\n\nfunc (s *DbUtils) NewClient(c *Client) error {\n\tvar isNotSet bool\n\tif c.WebUserName != \"\" && !s.VerifyUserName(c.WebUserName, c.Id) {\n\t\treturn errors.New(\"web login username duplicate, please reset\")\n\t}\nreset:\n\tif c.VerifyKey == \"\" || isNotSet {\n\t\tisNotSet = true\n\t\tc.VerifyKey = crypt.GetRandomString(16)\n\t}\n\tif c.RateLimit == 0 {\n\t\tc.Rate = rate.NewRate(int64(2 << 23))\n\t} else if c.Rate == nil {\n\t\tc.Rate = rate.NewRate(int64(c.RateLimit * 1024))\n\t}\n\tc.Rate.Start()\n\tif !s.VerifyVkey(c.VerifyKey, c.Id) {\n\t\tif isNotSet {\n\t\t\tgoto reset\n\t\t}\n\t\treturn errors.New(\"Vkey duplicate, please reset\")\n\t}\n\tif c.Id == 0 {\n\t\tc.Id = int(s.JsonDb.GetClientId())\n\t}\n\tif c.Flow == nil {\n\t\tc.Flow = new(Flow)\n\t}\n\ts.JsonDb.Clients.Store(c.Id, c)\n\ts.JsonDb.StoreClientsToJsonFile()\n\treturn nil\n}\n\nfunc (s *DbUtils) VerifyVkey(vkey string, id int) (res bool) {\n\tres = true\n\ts.JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Client)\n\t\tif v.VerifyKey == vkey && v.Id != id {\n\t\t\tres = false\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn res\n}\n\nfunc (s *DbUtils) VerifyUserName(username string, id int) (res bool) {\n\tres = true\n\ts.JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Client)\n\t\tif v.WebUserName == username && v.Id != id {\n\t\t\tres = false\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn res\n}\n\nfunc (s *DbUtils) UpdateClient(t *Client) error {\n\ts.JsonDb.Clients.Store(t.Id, t)\n\tif t.RateLimit == 0 {\n\t\tt.Rate = rate.NewRate(int64(2 << 23))\n\t\tt.Rate.Start()\n\t}\n\treturn nil\n}\n\nfunc (s *DbUtils) IsPubClient(id int) bool {\n\tclient, err := s.GetClient(id)\n\tif err == nil {\n\t\treturn client.NoDisplay\n\t}\n\treturn false\n}\n\nfunc (s *DbUtils) GetClient(id int) (c *Client, err error) {\n\tif v, ok := s.JsonDb.Clients.Load(id); ok {\n\t\tc = v.(*Client)\n\t\treturn\n\t}\n\terr = errors.New(\"未找到客户端\")\n\treturn\n}\n\nfunc (s *DbUtils) GetClientIdByVkey(vkey string) (id int, err error) {\n\tvar exist bool\n\ts.JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Client)\n\t\tif crypt.Md5(v.VerifyKey) == vkey {\n\t\t\texist = true\n\t\t\tid = v.Id\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif exist {\n\t\treturn\n\t}\n\terr = errors.New(\"未找到客户端\")\n\treturn\n}\n\nfunc (s *DbUtils) GetHostById(id int) (h *Host, err error) {\n\tif v, ok := s.JsonDb.Hosts.Load(id); ok {\n\t\th = v.(*Host)\n\t\treturn\n\t}\n\terr = errors.New(\"The host could not be parsed\")\n\treturn\n}\n\n//get key by host from x\nfunc (s *DbUtils) GetInfoByHost(host string, r *http.Request) (h *Host, err error) {\n\tvar hosts []*Host\n\t//Handling Ported Access\n\thost = common.GetIpByAddr(host)\n\ts.JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Host)\n\t\tif v.IsClose {\n\t\t\treturn true\n\t\t}\n\t\t//Remove http(s) http(s)://a.proxy.com\n\t\t//*.proxy.com *.a.proxy.com  Do some pan-parsing\n\t\tif v.Scheme != \"all\" && v.Scheme != r.URL.Scheme {\n\t\t\treturn true\n\t\t}\n\t\ttmpHost := v.Host\n\t\tif strings.Contains(tmpHost, \"*\") {\n\t\t\ttmpHost = strings.Replace(tmpHost, \"*\", \"\", -1)\n\t\t\tif strings.Contains(host, tmpHost) {\n\t\t\t\thosts = append(hosts, v)\n\t\t\t}\n\t\t} else if v.Host == host {\n\t\t\thosts = append(hosts, v)\n\t\t}\n\t\treturn true\n\t})\n\n\tfor _, v := range hosts {\n\t\t//If not set, default matches all\n\t\tif v.Location == \"\" {\n\t\t\tv.Location = \"/\"\n\t\t}\n\t\tif strings.Index(r.RequestURI, v.Location) == 0 {\n\t\t\tif h == nil || (len(v.Location) > len(h.Location)) {\n\t\t\t\th = v\n\t\t\t}\n\t\t}\n\t}\n\tif h != nil {\n\t\treturn\n\t}\n\terr = errors.New(\"The host could not be parsed\")\n\treturn\n}\n"
  },
  {
    "path": "lib/file/file.go",
    "content": "package file\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/rate\"\n)\n\nfunc NewJsonDb(runPath string) *JsonDb {\n\treturn &JsonDb{\n\t\tRunPath:        runPath,\n\t\tTaskFilePath:   filepath.Join(runPath, \"conf\", \"tasks.json\"),\n\t\tHostFilePath:   filepath.Join(runPath, \"conf\", \"hosts.json\"),\n\t\tClientFilePath: filepath.Join(runPath, \"conf\", \"clients.json\"),\n\t}\n}\n\ntype JsonDb struct {\n\tTasks            sync.Map\n\tHosts            sync.Map\n\tHostsTmp         sync.Map\n\tClients          sync.Map\n\tRunPath          string\n\tClientIncreaseId int32  //client increased id\n\tTaskIncreaseId   int32  //task increased id\n\tHostIncreaseId   int32  //host increased id\n\tTaskFilePath     string //task file path\n\tHostFilePath     string //host file path\n\tClientFilePath   string //client file path\n}\n\nfunc (s *JsonDb) LoadTaskFromJsonFile() {\n\tloadSyncMapFromFile(s.TaskFilePath, func(v string) {\n\t\tvar err error\n\t\tpost := new(Tunnel)\n\t\tif json.Unmarshal([]byte(v), &post) != nil {\n\t\t\treturn\n\t\t}\n\t\tif post.Client, err = s.GetClient(post.Client.Id); err != nil {\n\t\t\treturn\n\t\t}\n\t\ts.Tasks.Store(post.Id, post)\n\t\tif post.Id > int(s.TaskIncreaseId) {\n\t\t\ts.TaskIncreaseId = int32(post.Id)\n\t\t}\n\t})\n}\n\nfunc (s *JsonDb) LoadClientFromJsonFile() {\n\tloadSyncMapFromFile(s.ClientFilePath, func(v string) {\n\t\tpost := new(Client)\n\t\tif json.Unmarshal([]byte(v), &post) != nil {\n\t\t\treturn\n\t\t}\n\t\tif post.RateLimit > 0 {\n\t\t\tpost.Rate = rate.NewRate(int64(post.RateLimit * 1024))\n\t\t} else {\n\t\t\tpost.Rate = rate.NewRate(int64(2 << 23))\n\t\t}\n\t\tpost.Rate.Start()\n\t\tpost.NowConn = 0\n\t\ts.Clients.Store(post.Id, post)\n\t\tif post.Id > int(s.ClientIncreaseId) {\n\t\t\ts.ClientIncreaseId = int32(post.Id)\n\t\t}\n\t})\n}\n\nfunc (s *JsonDb) LoadHostFromJsonFile() {\n\tloadSyncMapFromFile(s.HostFilePath, func(v string) {\n\t\tvar err error\n\t\tpost := new(Host)\n\t\tif json.Unmarshal([]byte(v), &post) != nil {\n\t\t\treturn\n\t\t}\n\t\tif post.Client, err = s.GetClient(post.Client.Id); err != nil {\n\t\t\treturn\n\t\t}\n\t\ts.Hosts.Store(post.Id, post)\n\t\tif post.Id > int(s.HostIncreaseId) {\n\t\t\ts.HostIncreaseId = int32(post.Id)\n\t\t}\n\t})\n}\n\nfunc (s *JsonDb) GetClient(id int) (c *Client, err error) {\n\tif v, ok := s.Clients.Load(id); ok {\n\t\tc = v.(*Client)\n\t\treturn\n\t}\n\terr = errors.New(\"未找到客户端\")\n\treturn\n}\n\nvar hostLock sync.Mutex\n\nfunc (s *JsonDb) StoreHostToJsonFile() {\n\thostLock.Lock()\n\tstoreSyncMapToFile(s.Hosts, s.HostFilePath)\n\thostLock.Unlock()\n}\n\nvar taskLock sync.Mutex\n\nfunc (s *JsonDb) StoreTasksToJsonFile() {\n\ttaskLock.Lock()\n\tstoreSyncMapToFile(s.Tasks, s.TaskFilePath)\n\ttaskLock.Unlock()\n}\n\nvar clientLock sync.Mutex\n\nfunc (s *JsonDb) StoreClientsToJsonFile() {\n\tclientLock.Lock()\n\tstoreSyncMapToFile(s.Clients, s.ClientFilePath)\n\tclientLock.Unlock()\n}\n\nfunc (s *JsonDb) GetClientId() int32 {\n\treturn atomic.AddInt32(&s.ClientIncreaseId, 1)\n}\n\nfunc (s *JsonDb) GetTaskId() int32 {\n\treturn atomic.AddInt32(&s.TaskIncreaseId, 1)\n}\n\nfunc (s *JsonDb) GetHostId() int32 {\n\treturn atomic.AddInt32(&s.HostIncreaseId, 1)\n}\n\nfunc loadSyncMapFromFile(filePath string, f func(value string)) {\n\tb, err := common.ReadAllFromFile(filePath)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfor _, v := range strings.Split(string(b), \"\\n\"+common.CONN_DATA_SEQ) {\n\t\tf(v)\n\t}\n}\n\nfunc storeSyncMapToFile(m sync.Map, filePath string) {\n\tfile, err := os.Create(filePath + \".tmp\")\n\t// first create a temporary file to store\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tm.Range(func(key, value interface{}) bool {\n\t\tvar b []byte\n\t\tvar err error\n\t\tswitch value.(type) {\n\t\tcase *Tunnel:\n\t\t\tobj := value.(*Tunnel)\n\t\t\tif obj.NoStore {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tb, err = json.Marshal(obj)\n\t\tcase *Host:\n\t\t\tobj := value.(*Host)\n\t\t\tif obj.NoStore {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tb, err = json.Marshal(obj)\n\t\tcase *Client:\n\t\t\tobj := value.(*Client)\n\t\t\tif obj.NoStore {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tb, err = json.Marshal(obj)\n\t\tdefault:\n\t\t\treturn true\n\t\t}\n\t\tif err != nil {\n\t\t\treturn true\n\t\t}\n\t\t_, err = file.Write(b)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\t_, err = file.Write([]byte(\"\\n\" + common.CONN_DATA_SEQ))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn true\n\t})\n\t_ = file.Sync()\n\t_ = file.Close()\n\t// must close file first, then rename it\n\terr = os.Rename(filePath+\".tmp\", filePath)\n\tif err != nil {\n\t\tlogs.Error(err, \"store to file err, data will lost\")\n\t}\n\t// replace the file, maybe provides atomic operation\n}\n"
  },
  {
    "path": "lib/file/obj.go",
    "content": "package file\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/rate\"\n\t\"github.com/pkg/errors\"\n)\n\ntype Flow struct {\n\tExportFlow int64\n\tInletFlow  int64\n\tFlowLimit  int64\n\tsync.RWMutex\n}\n\nfunc (s *Flow) Add(in, out int64) {\n\ts.Lock()\n\tdefer s.Unlock()\n\ts.InletFlow += int64(in)\n\ts.ExportFlow += int64(out)\n}\n\ntype Config struct {\n\tU        string\n\tP        string\n\tCompress bool\n\tCrypt    bool\n}\n\ntype Client struct {\n\tCnf             *Config\n\tId              int        //id\n\tVerifyKey       string     //verify key\n\tAddr            string     //the ip of client\n\tRemark          string     //remark\n\tStatus          bool       //is allow connect\n\tIsConnect       bool       //is the client connect\n\tRateLimit       int        //rate /kb\n\tFlow            *Flow      //flow setting\n\tRate            *rate.Rate //rate limit\n\tNoStore         bool       //no store to file\n\tNoDisplay       bool       //no display on web\n\tMaxConn         int        //the max connection num of client allow\n\tNowConn         int32      //the connection num of now\n\tWebUserName     string     //the username of web login\n\tWebPassword     string     //the password of web login\n\tConfigConnAllow bool       //is allow connected by config file\n\tMaxTunnelNum    int\n\tVersion         string\n\tsync.RWMutex\n}\n\nfunc NewClient(vKey string, noStore bool, noDisplay bool) *Client {\n\treturn &Client{\n\t\tCnf:       new(Config),\n\t\tId:        0,\n\t\tVerifyKey: vKey,\n\t\tAddr:      \"\",\n\t\tRemark:    \"\",\n\t\tStatus:    true,\n\t\tIsConnect: false,\n\t\tRateLimit: 0,\n\t\tFlow:      new(Flow),\n\t\tRate:      nil,\n\t\tNoStore:   noStore,\n\t\tRWMutex:   sync.RWMutex{},\n\t\tNoDisplay: noDisplay,\n\t}\n}\n\nfunc (s *Client) CutConn() {\n\tatomic.AddInt32(&s.NowConn, 1)\n}\n\nfunc (s *Client) AddConn() {\n\tatomic.AddInt32(&s.NowConn, -1)\n}\n\nfunc (s *Client) GetConn() bool {\n\tif s.MaxConn == 0 || int(s.NowConn) < s.MaxConn {\n\t\ts.CutConn()\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (s *Client) HasTunnel(t *Tunnel) (exist bool) {\n\tGetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Tunnel)\n\t\tif v.Client.Id == s.Id && v.Port == t.Port && t.Port != 0 {\n\t\t\texist = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\nfunc (s *Client) GetTunnelNum() (num int) {\n\tGetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Tunnel)\n\t\tif v.Client.Id == s.Id {\n\t\t\tnum++\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\nfunc (s *Client) HasHost(h *Host) bool {\n\tvar has bool\n\tGetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\tv := value.(*Host)\n\t\tif v.Client.Id == s.Id && v.Host == h.Host && h.Location == v.Location {\n\t\t\thas = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn has\n}\n\ntype Tunnel struct {\n\tId           int\n\tPort         int\n\tServerIp     string\n\tMode         string\n\tStatus       bool\n\tRunStatus    bool\n\tClient       *Client\n\tPorts        string\n\tFlow         *Flow\n\tPassword     string\n\tRemark       string\n\tTargetAddr   string\n\tNoStore      bool\n\tLocalPath    string\n\tStripPre     string\n\tTarget       *Target\n\tMultiAccount *MultiAccount\n\tHealth\n\tsync.RWMutex\n}\n\ntype Health struct {\n\tHealthCheckTimeout  int\n\tHealthMaxFail       int\n\tHealthCheckInterval int\n\tHealthNextTime      time.Time\n\tHealthMap           map[string]int\n\tHttpHealthUrl       string\n\tHealthRemoveArr     []string\n\tHealthCheckType     string\n\tHealthCheckTarget   string\n\tsync.RWMutex\n}\n\ntype Host struct {\n\tId           int\n\tHost         string //host\n\tHeaderChange string //header change\n\tHostChange   string //host change\n\tLocation     string //url router\n\tRemark       string //remark\n\tScheme       string //http https all\n\tCertFilePath string\n\tKeyFilePath  string\n\tNoStore      bool\n\tIsClose      bool\n\tFlow         *Flow\n\tClient       *Client\n\tTarget       *Target //目标\n\tHealth       `json:\"-\"`\n\tsync.RWMutex\n}\n\ntype Target struct {\n\tnowIndex   int\n\tTargetStr  string\n\tTargetArr  []string\n\tLocalProxy bool\n\tsync.RWMutex\n}\n\ntype MultiAccount struct {\n\tAccountMap map[string]string // multi account and pwd\n}\n\nfunc (s *Target) GetRandomTarget() (string, error) {\n\tif s.TargetArr == nil {\n\t\ts.TargetArr = strings.Split(s.TargetStr, \"\\n\")\n\t}\n\tif len(s.TargetArr) == 1 {\n\t\treturn s.TargetArr[0], nil\n\t}\n\tif len(s.TargetArr) == 0 {\n\t\treturn \"\", errors.New(\"all inward-bending targets are offline\")\n\t}\n\ts.Lock()\n\tdefer s.Unlock()\n\tif s.nowIndex >= len(s.TargetArr)-1 {\n\t\ts.nowIndex = -1\n\t}\n\ts.nowIndex++\n\treturn s.TargetArr[s.nowIndex], nil\n}\n"
  },
  {
    "path": "lib/file/sort.go",
    "content": "package file\n\nimport (\n\t\"reflect\"\n\t\"sort\"\n\t\"sync\"\n)\n\n// A data structure to hold a key/value pair.\ntype Pair struct {\n\tkey        string //sort key\n\tcId        int\n\torder      string\n\tclientFlow *Flow\n}\n\n// A slice of Pairs that implements sort.Interface to sort by Value.\ntype PairList []*Pair\n\nfunc (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }\nfunc (p PairList) Len() int      { return len(p) }\nfunc (p PairList) Less(i, j int) bool {\n\tif p[i].order == \"desc\" {\n\t\treturn reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() < reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()\n\t}\n\treturn reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() > reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()\n}\n\n// A function to turn a map into a PairList, then sort and return it.\nfunc sortClientByKey(m sync.Map, sortKey, order string) (res []int) {\n\tp := make(PairList, 0)\n\tm.Range(func(key, value interface{}) bool {\n\t\tp = append(p, &Pair{sortKey, value.(*Client).Id, order, value.(*Client).Flow})\n\t\treturn true\n\t})\n\tsort.Sort(p)\n\tfor _, v := range p {\n\t\tres = append(res, v.cId)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "lib/goroutine/pool.go",
    "content": "package goroutine\n\nimport (\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/file\"\n\t\"github.com/panjf2000/ants/v2\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n)\n\ntype connGroup struct {\n\tsrc io.ReadWriteCloser\n\tdst io.ReadWriteCloser\n\twg  *sync.WaitGroup\n\tn   *int64\n}\n\nfunc newConnGroup(dst, src io.ReadWriteCloser, wg *sync.WaitGroup, n *int64) connGroup {\n\treturn connGroup{\n\t\tsrc: src,\n\t\tdst: dst,\n\t\twg:  wg,\n\t\tn:   n,\n\t}\n}\n\nfunc copyConnGroup(group interface{}) {\n\tcg, ok := group.(connGroup)\n\tif !ok {\n\t\treturn\n\t}\n\tvar err error\n\t*cg.n, err = common.CopyBuffer(cg.dst, cg.src)\n\tif err != nil {\n\t\tcg.src.Close()\n\t\tcg.dst.Close()\n\t\t//logs.Warn(\"close npc by copy from nps\", err, c.connId)\n\t}\n\tcg.wg.Done()\n}\n\ntype Conns struct {\n\tconn1 io.ReadWriteCloser // mux connection\n\tconn2 net.Conn           // outside connection\n\tflow  *file.Flow\n\twg    *sync.WaitGroup\n}\n\nfunc NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow, wg *sync.WaitGroup) Conns {\n\treturn Conns{\n\t\tconn1: c1,\n\t\tconn2: c2,\n\t\tflow:  flow,\n\t\twg:    wg,\n\t}\n}\n\nfunc copyConns(group interface{}) {\n\tconns := group.(Conns)\n\twg := new(sync.WaitGroup)\n\twg.Add(2)\n\tvar in, out int64\n\t_ = connCopyPool.Invoke(newConnGroup(conns.conn1, conns.conn2, wg, &in))\n\t// outside to mux : incoming\n\t_ = connCopyPool.Invoke(newConnGroup(conns.conn2, conns.conn1, wg, &out))\n\t// mux to outside : outgoing\n\twg.Wait()\n\tif conns.flow != nil {\n\t\tconns.flow.Add(in, out)\n\t}\n\tconns.wg.Done()\n}\n\nvar connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false))\nvar CopyConnsPool, _ = ants.NewPoolWithFunc(100000, copyConns, ants.WithNonblocking(false))\n"
  },
  {
    "path": "lib/install/install.go",
    "content": "package install\n\nimport (\n\t\"ehang.io/nps/lib/common\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/c4milo/unpackit\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n)\n\n// Keep it in sync with the template from service_sysv_linux.go file\n// Use \"ps | grep -v grep | grep $(get_pid)\" because \"ps PID\" may not work on OpenWrt\nconst SysvScript = `#!/bin/sh\n# For RedHat and cousins:\n# chkconfig: - 99 01\n# description: {{.Description}}\n# processname: {{.Path}}\n### BEGIN INIT INFO\n# Provides:          {{.Path}}\n# Required-Start:\n# Required-Stop:\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n# Short-Description: {{.DisplayName}}\n# Description:       {{.Description}}\n### END INIT INFO\ncmd=\"{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}\"\nname=$(basename $(readlink -f $0))\npid_file=\"/var/run/$name.pid\"\nstdout_log=\"/var/log/$name.log\"\nstderr_log=\"/var/log/$name.err\"\n[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name\nget_pid() {\n    cat \"$pid_file\"\n}\nis_running() {\n    [ -f \"$pid_file\" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1\n}\ncase \"$1\" in\n    start)\n        if is_running; then\n            echo \"Already started\"\n        else\n            echo \"Starting $name\"\n            {{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}\n            $cmd >> \"$stdout_log\" 2>> \"$stderr_log\" &\n            echo $! > \"$pid_file\"\n            if ! is_running; then\n                echo \"Unable to start, see $stdout_log and $stderr_log\"\n                exit 1\n            fi\n        fi\n    ;;\n    stop)\n        if is_running; then\n            echo -n \"Stopping $name..\"\n            kill $(get_pid)\n            for i in $(seq 1 10)\n            do\n                if ! is_running; then\n                    break\n                fi\n                echo -n \".\"\n                sleep 1\n            done\n            echo\n            if is_running; then\n                echo \"Not stopped; may still be shutting down or shutdown may have failed\"\n                exit 1\n            else\n                echo \"Stopped\"\n                if [ -f \"$pid_file\" ]; then\n                    rm \"$pid_file\"\n                fi\n            fi\n        else\n            echo \"Not running\"\n        fi\n    ;;\n    restart)\n        $0 stop\n        if is_running; then\n            echo \"Unable to stop, will not attempt to start\"\n            exit 1\n        fi\n        $0 start\n    ;;\n    status)\n        if is_running; then\n            echo \"Running\"\n        else\n            echo \"Stopped\"\n            exit 1\n        fi\n    ;;\n    *)\n    echo \"Usage: $0 {start|stop|restart|status}\"\n    exit 1\n    ;;\nesac\nexit 0\n`\n\nconst SystemdScript = `[Unit]\nDescription={{.Description}}\nConditionFileIsExecutable={{.Path|cmdEscape}}\n{{range $i, $dep := .Dependencies}} \n{{$dep}} {{end}}\n[Service]\nLimitNOFILE=65536\nStartLimitInterval=5\nStartLimitBurst=10\nExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}\n{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}\n{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}\n{{if .UserName}}User={{.UserName}}{{end}}\n{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} \"$MAINPID\"{{end}}\n{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}\n{{if and .LogOutput .HasOutputFileSupport -}}\nStandardOutput=file:/var/log/{{.Name}}.out\nStandardError=file:/var/log/{{.Name}}.err\n{{- end}}\nRestart=always\nRestartSec=120\n[Install]\nWantedBy=multi-user.target\n`\n\nfunc UpdateNps() {\n\tdestPath := downloadLatest(\"server\")\n\t//复制文件到对应目录\n\tcopyStaticFile(destPath, \"nps\")\n\tfmt.Println(\"Update completed, please restart\")\n}\n\nfunc UpdateNpc() {\n\tdestPath := downloadLatest(\"client\")\n\t//复制文件到对应目录\n\tcopyStaticFile(destPath, \"npc\")\n\tfmt.Println(\"Update completed, please restart\")\n}\n\ntype release struct {\n\tTagName string `json:\"tag_name\"`\n}\n\nfunc downloadLatest(bin string) string {\n\t// get version\n\tdata, err := http.Get(\"https://api.github.com/repos/ehang-io/nps/releases/latest\")\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\tb, err := ioutil.ReadAll(data.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\trl := new(release)\n\tjson.Unmarshal(b, &rl)\n\tversion := rl.TagName\n\tfmt.Println(\"the latest version is\", version)\n\tfilename := runtime.GOOS + \"_\" + runtime.GOARCH + \"_\" + bin + \".tar.gz\"\n\t// download latest package\n\tdownloadUrl := fmt.Sprintf(\"https://ehang.io/nps/releases/download/%s/%s\", version, filename)\n\tfmt.Println(\"download package from \", downloadUrl)\n\tresp, err := http.Get(downloadUrl)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\tdestPath, err := unpackit.Unpack(resp.Body, \"\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif bin == \"server\" {\n\t\tdestPath = strings.Replace(destPath, \"/web\", \"\", -1)\n\t\tdestPath = strings.Replace(destPath, `\\web`, \"\", -1)\n\t\tdestPath = strings.Replace(destPath, \"/views\", \"\", -1)\n\t\tdestPath = strings.Replace(destPath, `\\views`, \"\", -1)\n\t} else {\n\t\tdestPath = strings.Replace(destPath, `\\conf`, \"\", -1)\n\t\tdestPath = strings.Replace(destPath, \"/conf\", \"\", -1)\n\t}\n\treturn destPath\n}\n\nfunc copyStaticFile(srcPath, bin string) string {\n\tpath := common.GetInstallPath()\n\tif bin == \"nps\" {\n\t\t//复制文件到对应目录\n\t\tif err := CopyDir(filepath.Join(srcPath, \"web\", \"views\"), filepath.Join(path, \"web\", \"views\")); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t\tchMod(filepath.Join(path, \"web\", \"views\"), 0766)\n\t\tif err := CopyDir(filepath.Join(srcPath, \"web\", \"static\"), filepath.Join(path, \"web\", \"static\")); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t\tchMod(filepath.Join(path, \"web\", \"static\"), 0766)\n\t}\n\tbinPath, _ := filepath.Abs(os.Args[0])\n\tif !common.IsWindows() {\n\t\tif _, err := copyFile(filepath.Join(srcPath, bin), \"/usr/bin/\"+bin); err != nil {\n\t\t\tif _, err := copyFile(filepath.Join(srcPath, bin), \"/usr/local/bin/\"+bin); err != nil {\n\t\t\t\tlog.Fatalln(err)\n\t\t\t} else {\n\t\t\t\tcopyFile(filepath.Join(srcPath, bin), \"/usr/local/bin/\"+bin+\"-update\")\n\t\t\t\tchMod(\"/usr/local/bin/\"+bin+\"-update\", 0755)\n\t\t\t\tbinPath = \"/usr/local/bin/\" + bin\n\t\t\t}\n\t\t} else {\n\t\t\tcopyFile(filepath.Join(srcPath, bin), \"/usr/bin/\"+bin+\"-update\")\n\t\t\tchMod(\"/usr/bin/\"+bin+\"-update\", 0755)\n\t\t\tbinPath = \"/usr/bin/\" + bin\n\t\t}\n\t} else {\n\t\tcopyFile(filepath.Join(srcPath, bin+\".exe\"), filepath.Join(common.GetAppPath(), bin+\"-update.exe\"))\n\t\tcopyFile(filepath.Join(srcPath, bin+\".exe\"), filepath.Join(common.GetAppPath(), bin+\".exe\"))\n\t}\n\tchMod(binPath, 0755)\n\treturn binPath\n}\n\nfunc InstallNpc() {\n\tpath := common.GetInstallPath()\n\tif !common.FileExists(path) {\n\t\terr := os.Mkdir(path, 0755)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\tcopyStaticFile(common.GetAppPath(), \"npc\")\n}\n\nfunc InstallNps() string {\n\tpath := common.GetInstallPath()\n\tif common.FileExists(path) {\n\t\tMkidrDirAll(path, \"web/static\", \"web/views\")\n\t} else {\n\t\tMkidrDirAll(path, \"conf\", \"web/static\", \"web/views\")\n\t\t// not copy config if the config file is exist\n\t\tif err := CopyDir(filepath.Join(common.GetAppPath(), \"conf\"), filepath.Join(path, \"conf\")); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t\tchMod(filepath.Join(path, \"conf\"), 0766)\n\t}\n\tbinPath := copyStaticFile(common.GetAppPath(), \"nps\")\n\tlog.Println(\"install ok!\")\n\tlog.Println(\"Static files and configuration files in the current directory will be useless\")\n\tlog.Println(\"The new configuration file is located in\", path, \"you can edit them\")\n\tif !common.IsWindows() {\n\t\tlog.Println(`You can start with:\nnps start|stop|restart|uninstall|update or nps-update update\nanywhere!`)\n\t} else {\n\t\tlog.Println(`You can copy executable files to any directory and start working with:\nnps.exe start|stop|restart|uninstall|update or nps-update.exe update\nnow!`)\n\t}\n\tchMod(common.GetLogPath(), 0777)\n\treturn binPath\n}\nfunc MkidrDirAll(path string, v ...string) {\n\tfor _, item := range v {\n\t\tif err := os.MkdirAll(filepath.Join(path, item), 0755); err != nil {\n\t\t\tlog.Fatalf(\"Failed to create directory %s error:%s\", path, err.Error())\n\t\t}\n\t}\n}\n\nfunc CopyDir(srcPath string, destPath string) error {\n\t//检测目录正确性\n\tif srcInfo, err := os.Stat(srcPath); err != nil {\n\t\tfmt.Println(err.Error())\n\t\treturn err\n\t} else {\n\t\tif !srcInfo.IsDir() {\n\t\t\te := errors.New(\"SrcPath is not the right directory!\")\n\t\t\treturn e\n\t\t}\n\t}\n\tif destInfo, err := os.Stat(destPath); err != nil {\n\t\treturn err\n\t} else {\n\t\tif !destInfo.IsDir() {\n\t\t\te := errors.New(\"DestInfo is not the right directory!\")\n\t\t\treturn e\n\t\t}\n\t}\n\terr := filepath.Walk(srcPath, func(path string, f os.FileInfo, err error) error {\n\t\tif f == nil {\n\t\t\treturn err\n\t\t}\n\t\tif !f.IsDir() {\n\t\t\tdestNewPath := strings.Replace(path, srcPath, destPath, -1)\n\t\t\tlog.Println(\"copy file ::\" + path + \" to \" + destNewPath)\n\t\t\tcopyFile(path, destNewPath)\n\t\t\tif !common.IsWindows() {\n\t\t\t\tchMod(destNewPath, 0766)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\treturn err\n}\n\n//生成目录并拷贝文件\nfunc copyFile(src, dest string) (w int64, err error) {\n\tsrcFile, err := os.Open(src)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer srcFile.Close()\n\t//分割path目录\n\tdestSplitPathDirs := strings.Split(dest, string(filepath.Separator))\n\n\t//检测时候存在目录\n\tdestSplitPath := \"\"\n\tfor index, dir := range destSplitPathDirs {\n\t\tif index < len(destSplitPathDirs)-1 {\n\t\t\tdestSplitPath = destSplitPath + dir + string(filepath.Separator)\n\t\t\tb, _ := pathExists(destSplitPath)\n\t\t\tif b == false {\n\t\t\t\tlog.Println(\"mkdir:\" + destSplitPath)\n\t\t\t\t//创建目录\n\t\t\t\terr := os.Mkdir(destSplitPath, os.ModePerm)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Fatalln(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tdstFile, err := os.Create(dest)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer dstFile.Close()\n\n\treturn io.Copy(dstFile, srcFile)\n}\n\n//检测文件夹路径时候存在\nfunc pathExists(path string) (bool, error) {\n\t_, err := os.Stat(path)\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n\nfunc chMod(name string, mode os.FileMode) {\n\tif !common.IsWindows() {\n\t\tos.Chmod(name, mode)\n\t}\n}\n"
  },
  {
    "path": "lib/pmux/pconn.go",
    "content": "package pmux\n\nimport (\n\t\"net\"\n\t\"time\"\n)\n\ntype PortConn struct {\n\tConn     net.Conn\n\trs       []byte\n\treadMore bool\n\tstart    int\n}\n\nfunc newPortConn(conn net.Conn, rs []byte, readMore bool) *PortConn {\n\treturn &PortConn{\n\t\tConn:     conn,\n\t\trs:       rs,\n\t\treadMore: readMore,\n\t}\n}\n\nfunc (pConn *PortConn) Read(b []byte) (n int, err error) {\n\tif len(b) < len(pConn.rs)-pConn.start {\n\t\tdefer func() {\n\t\t\tpConn.start = pConn.start + len(b)\n\t\t}()\n\t\treturn copy(b, pConn.rs), nil\n\t}\n\tif pConn.start < len(pConn.rs) {\n\t\tdefer func() {\n\t\t\tpConn.start = len(pConn.rs)\n\t\t}()\n\t\tn = copy(b, pConn.rs[pConn.start:])\n\t\tif !pConn.readMore {\n\t\t\treturn\n\t\t}\n\t}\n\tvar n2 = 0\n\tn2, err = pConn.Conn.Read(b[n:])\n\tn = n + n2\n\treturn\n}\n\nfunc (pConn *PortConn) Write(b []byte) (n int, err error) {\n\treturn pConn.Conn.Write(b)\n}\n\nfunc (pConn *PortConn) Close() error {\n\treturn pConn.Conn.Close()\n}\n\nfunc (pConn *PortConn) LocalAddr() net.Addr {\n\treturn pConn.Conn.LocalAddr()\n}\n\nfunc (pConn *PortConn) RemoteAddr() net.Addr {\n\treturn pConn.Conn.RemoteAddr()\n}\n\nfunc (pConn *PortConn) SetDeadline(t time.Time) error {\n\treturn pConn.Conn.SetDeadline(t)\n}\n\nfunc (pConn *PortConn) SetReadDeadline(t time.Time) error {\n\treturn pConn.Conn.SetReadDeadline(t)\n}\n\nfunc (pConn *PortConn) SetWriteDeadline(t time.Time) error {\n\treturn pConn.Conn.SetWriteDeadline(t)\n}\n"
  },
  {
    "path": "lib/pmux/plistener.go",
    "content": "package pmux\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\ntype PortListener struct {\n\tnet.Listener\n\tconnCh  chan *PortConn\n\taddr    net.Addr\n\tisClose bool\n}\n\nfunc NewPortListener(connCh chan *PortConn, addr net.Addr) *PortListener {\n\treturn &PortListener{\n\t\tconnCh: connCh,\n\t\taddr:   addr,\n\t}\n}\n\nfunc (pListener *PortListener) Accept() (net.Conn, error) {\n\tif pListener.isClose {\n\t\treturn nil, errors.New(\"the listener has closed\")\n\t}\n\tconn := <-pListener.connCh\n\tif conn != nil {\n\t\treturn conn, nil\n\t}\n\treturn nil, errors.New(\"the listener has closed\")\n}\n\nfunc (pListener *PortListener) Close() error {\n\t//close\n\tif pListener.isClose {\n\t\treturn errors.New(\"the listener has closed\")\n\t}\n\tpListener.isClose = true\n\treturn nil\n}\n\nfunc (pListener *PortListener) Addr() net.Addr {\n\treturn pListener.addr\n}\n"
  },
  {
    "path": "lib/pmux/pmux.go",
    "content": "// This module is used for port reuse\n// Distinguish client, web manager , HTTP and HTTPS according to the difference of protocol\npackage pmux\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tHTTP_GET        = 716984\n\tHTTP_POST       = 807983\n\tHTTP_HEAD       = 726965\n\tHTTP_PUT        = 808585\n\tHTTP_DELETE     = 686976\n\tHTTP_CONNECT    = 677978\n\tHTTP_OPTIONS    = 798084\n\tHTTP_TRACE      = 848265\n\tCLIENT          = 848384\n\tACCEPT_TIME_OUT = 10\n)\n\ntype PortMux struct {\n\tnet.Listener\n\tport        int\n\tisClose     bool\n\tmanagerHost string\n\tclientConn  chan *PortConn\n\thttpConn    chan *PortConn\n\thttpsConn   chan *PortConn\n\tmanagerConn chan *PortConn\n}\n\nfunc NewPortMux(port int, managerHost string) *PortMux {\n\tpMux := &PortMux{\n\t\tmanagerHost: managerHost,\n\t\tport:        port,\n\t\tclientConn:  make(chan *PortConn),\n\t\thttpConn:    make(chan *PortConn),\n\t\thttpsConn:   make(chan *PortConn),\n\t\tmanagerConn: make(chan *PortConn),\n\t}\n\tpMux.Start()\n\treturn pMux\n}\n\nfunc (pMux *PortMux) Start() error {\n\t// Port multiplexing is based on TCP only\n\ttcpAddr, err := net.ResolveTCPAddr(\"tcp\", \"0.0.0.0:\"+strconv.Itoa(pMux.port))\n\tif err != nil {\n\t\treturn err\n\t}\n\tpMux.Listener, err = net.ListenTCP(\"tcp\", tcpAddr)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\tos.Exit(0)\n\t}\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := pMux.Listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tlogs.Warn(err)\n\t\t\t\t//close\n\t\t\t\tpMux.Close()\n\t\t\t}\n\t\t\tgo pMux.process(conn)\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc (pMux *PortMux) process(conn net.Conn) {\n\t// Recognition according to different signs\n\t// read 3 byte\n\tbuf := make([]byte, 3)\n\tif n, err := io.ReadFull(conn, buf); err != nil || n != 3 {\n\t\treturn\n\t}\n\tvar ch chan *PortConn\n\tvar rs []byte\n\tvar buffer bytes.Buffer\n\tvar readMore = false\n\tswitch common.BytesToNum(buf) {\n\tcase HTTP_CONNECT, HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_OPTIONS, HTTP_POST, HTTP_PUT, HTTP_TRACE: //http and manager\n\t\tbuffer.Reset()\n\t\tr := bufio.NewReader(conn)\n\t\tbuffer.Write(buf)\n\t\tfor {\n\t\t\tb, _, err := r.ReadLine()\n\t\t\tif err != nil {\n\t\t\t\tlogs.Warn(\"read line error\", err.Error())\n\t\t\t\tconn.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbuffer.Write(b)\n\t\t\tbuffer.Write([]byte(\"\\r\\n\"))\n\t\t\tif strings.Index(string(b), \"Host:\") == 0 || strings.Index(string(b), \"host:\") == 0 {\n\t\t\t\t// Remove host and space effects\n\t\t\t\tstr := strings.Replace(string(b), \"Host:\", \"\", -1)\n\t\t\t\tstr = strings.Replace(str, \"host:\", \"\", -1)\n\t\t\t\tstr = strings.TrimSpace(str)\n\t\t\t\t// Determine whether it is the same as the manager domain name\n\t\t\t\tif common.GetIpByAddr(str) == pMux.managerHost {\n\t\t\t\t\tch = pMux.managerConn\n\t\t\t\t} else {\n\t\t\t\t\tch = pMux.httpConn\n\t\t\t\t}\n\t\t\t\tb, _ := r.Peek(r.Buffered())\n\t\t\t\tbuffer.Write(b)\n\t\t\t\trs = buffer.Bytes()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\tcase CLIENT: // client connection\n\t\tch = pMux.clientConn\n\tdefault: // https\n\t\treadMore = true\n\t\tch = pMux.httpsConn\n\t}\n\tif len(rs) == 0 {\n\t\trs = buf\n\t}\n\ttimer := time.NewTimer(ACCEPT_TIME_OUT)\n\tselect {\n\tcase <-timer.C:\n\tcase ch <- newPortConn(conn, rs, readMore):\n\t}\n}\n\nfunc (pMux *PortMux) Close() error {\n\tif pMux.isClose {\n\t\treturn errors.New(\"the port pmux has closed\")\n\t}\n\tpMux.isClose = true\n\tclose(pMux.clientConn)\n\tclose(pMux.httpsConn)\n\tclose(pMux.httpConn)\n\tclose(pMux.managerConn)\n\treturn pMux.Listener.Close()\n}\n\nfunc (pMux *PortMux) GetClientListener() net.Listener {\n\treturn NewPortListener(pMux.clientConn, pMux.Listener.Addr())\n}\n\nfunc (pMux *PortMux) GetHttpListener() net.Listener {\n\treturn NewPortListener(pMux.httpConn, pMux.Listener.Addr())\n}\n\nfunc (pMux *PortMux) GetHttpsListener() net.Listener {\n\treturn NewPortListener(pMux.httpsConn, pMux.Listener.Addr())\n}\n\nfunc (pMux *PortMux) GetManagerListener() net.Listener {\n\treturn NewPortListener(pMux.managerConn, pMux.Listener.Addr())\n}\n"
  },
  {
    "path": "lib/pmux/pmux_test.go",
    "content": "package pmux\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/astaxie/beego/logs\"\n)\n\nfunc TestPortMux_Close(t *testing.T) {\n\tlogs.Reset()\n\tlogs.EnableFuncCallDepth(true)\n\tlogs.SetLogFuncCallDepth(3)\n\n\tpMux := NewPortMux(8888, \"Ds\")\n\tgo func() {\n\t\tif pMux.Start() != nil {\n\t\t\tlogs.Warn(\"Error\")\n\t\t}\n\t}()\n\ttime.Sleep(time.Second * 3)\n\tgo func() {\n\t\tl := pMux.GetHttpListener()\n\t\tconn, err := l.Accept()\n\t\tlogs.Warn(conn, err)\n\t}()\n\tgo func() {\n\t\tl := pMux.GetHttpListener()\n\t\tconn, err := l.Accept()\n\t\tlogs.Warn(conn, err)\n\t}()\n\tgo func() {\n\t\tl := pMux.GetHttpListener()\n\t\tconn, err := l.Accept()\n\t\tlogs.Warn(conn, err)\n\t}()\n\tl := pMux.GetHttpListener()\n\tconn, err := l.Accept()\n\tlogs.Warn(conn, err)\n}\n"
  },
  {
    "path": "lib/rate/conn.go",
    "content": "package rate\n\nimport (\n\t\"io\"\n)\n\ntype rateConn struct {\n\tconn io.ReadWriteCloser\n\trate *Rate\n}\n\nfunc NewRateConn(conn io.ReadWriteCloser, rate *Rate) io.ReadWriteCloser {\n\treturn &rateConn{\n\t\tconn: conn,\n\t\trate: rate,\n\t}\n}\n\nfunc (s *rateConn) Read(b []byte) (n int, err error) {\n\tn, err = s.conn.Read(b)\n\tif s.rate != nil {\n\t\ts.rate.Get(int64(n))\n\t}\n\treturn\n}\n\nfunc (s *rateConn) Write(b []byte) (n int, err error) {\n\tn, err = s.conn.Write(b)\n\tif s.rate != nil {\n\t\ts.rate.Get(int64(n))\n\t}\n\treturn\n}\n\nfunc (s *rateConn) Close() error {\n\treturn s.conn.Close()\n}\n"
  },
  {
    "path": "lib/rate/rate.go",
    "content": "package rate\n\nimport (\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype Rate struct {\n\tbucketSize        int64\n\tbucketSurplusSize int64\n\tbucketAddSize     int64\n\tstopChan          chan bool\n\tNowRate           int64\n}\n\nfunc NewRate(addSize int64) *Rate {\n\treturn &Rate{\n\t\tbucketSize:        addSize * 2,\n\t\tbucketSurplusSize: 0,\n\t\tbucketAddSize:     addSize,\n\t\tstopChan:          make(chan bool),\n\t}\n}\n\nfunc (s *Rate) Start() {\n\tgo s.session()\n}\n\nfunc (s *Rate) add(size int64) {\n\tif res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize {\n\t\tatomic.AddInt64(&s.bucketSurplusSize, res)\n\t\treturn\n\t}\n\tatomic.AddInt64(&s.bucketSurplusSize, size)\n}\n\n//回桶\nfunc (s *Rate) ReturnBucket(size int64) {\n\ts.add(size)\n}\n\n//停止\nfunc (s *Rate) Stop() {\n\ts.stopChan <- true\n}\n\nfunc (s *Rate) Get(size int64) {\n\tif s.bucketSurplusSize >= size {\n\t\tatomic.AddInt64(&s.bucketSurplusSize, -size)\n\t\treturn\n\t}\n\tticker := time.NewTicker(time.Millisecond * 100)\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tif s.bucketSurplusSize >= size {\n\t\t\t\tatomic.AddInt64(&s.bucketSurplusSize, -size)\n\t\t\t\tticker.Stop()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Rate) session() {\n\tticker := time.NewTicker(time.Second * 1)\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tif rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 {\n\t\t\t\ts.NowRate = rs\n\t\t\t} else {\n\t\t\t\ts.NowRate = s.bucketSize - s.bucketSurplusSize\n\t\t\t}\n\t\t\ts.add(s.bucketAddSize)\n\t\tcase <-s.stopChan:\n\t\t\tticker.Stop()\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "lib/sheap/heap.go",
    "content": "package sheap\n\ntype IntHeap []int64\n\nfunc (h IntHeap) Len() int           { return len(h) }\nfunc (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }\nfunc (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }\n\nfunc (h *IntHeap) Push(x interface{}) {\n\t// Push and Pop use pointer receivers because they modify the slice's length,\n\t// not just its contents.\n\t*h = append(*h, x.(int64))\n}\n\nfunc (h *IntHeap) Pop() interface{} {\n\told := *h\n\tn := len(old)\n\tx := old[n-1]\n\t*h = old[0 : n-1]\n\treturn x\n}\n"
  },
  {
    "path": "lib/version/version.go",
    "content": "package version\n\nconst VERSION = \"0.26.10\"\n\n// Compulsory minimum version, Minimum downward compatibility to this version\nfunc GetVersion() string {\n\treturn \"0.26.0\"\n}\n"
  },
  {
    "path": "server/connection/connection.go",
    "content": "package connection\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"ehang.io/nps/lib/pmux\"\n\t\"github.com/astaxie/beego\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\nvar pMux *pmux.PortMux\nvar bridgePort string\nvar httpsPort string\nvar httpPort string\nvar webPort string\n\nfunc InitConnectionService() {\n\tbridgePort = beego.AppConfig.String(\"bridge_port\")\n\thttpsPort = beego.AppConfig.String(\"https_proxy_port\")\n\thttpPort = beego.AppConfig.String(\"http_proxy_port\")\n\twebPort = beego.AppConfig.String(\"web_port\")\n\n\tif httpPort == bridgePort || httpsPort == bridgePort || webPort == bridgePort {\n\t\tport, err := strconv.Atoi(bridgePort)\n\t\tif err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tos.Exit(0)\n\t\t}\n\t\tpMux = pmux.NewPortMux(port, beego.AppConfig.String(\"web_host\"))\n\t}\n}\n\nfunc GetBridgeListener(tp string) (net.Listener, error) {\n\tlogs.Info(\"server start, the bridge type is %s, the bridge port is %s\", tp, bridgePort)\n\tvar p int\n\tvar err error\n\tif p, err = strconv.Atoi(bridgePort); err != nil {\n\t\treturn nil, err\n\t}\n\tif pMux != nil {\n\t\treturn pMux.GetClientListener(), nil\n\t}\n\treturn net.ListenTCP(\"tcp\", &net.TCPAddr{net.ParseIP(beego.AppConfig.String(\"bridge_ip\")), p, \"\"})\n}\n\nfunc GetHttpListener() (net.Listener, error) {\n\tif pMux != nil && httpPort == bridgePort {\n\t\tlogs.Info(\"start http listener, port is\", bridgePort)\n\t\treturn pMux.GetHttpListener(), nil\n\t}\n\tlogs.Info(\"start http listener, port is\", httpPort)\n\treturn getTcpListener(beego.AppConfig.String(\"http_proxy_ip\"), httpPort)\n}\n\nfunc GetHttpsListener() (net.Listener, error) {\n\tif pMux != nil && httpsPort == bridgePort {\n\t\tlogs.Info(\"start https listener, port is\", bridgePort)\n\t\treturn pMux.GetHttpsListener(), nil\n\t}\n\tlogs.Info(\"start https listener, port is\", httpsPort)\n\treturn getTcpListener(beego.AppConfig.String(\"http_proxy_ip\"), httpsPort)\n}\n\nfunc GetWebManagerListener() (net.Listener, error) {\n\tif pMux != nil && webPort == bridgePort {\n\t\tlogs.Info(\"Web management start, access port is\", bridgePort)\n\t\treturn pMux.GetManagerListener(), nil\n\t}\n\tlogs.Info(\"web management start, access port is\", webPort)\n\treturn getTcpListener(beego.AppConfig.String(\"web_ip\"), webPort)\n}\n\nfunc getTcpListener(ip, p string) (net.Listener, error) {\n\tport, err := strconv.Atoi(p)\n\tif err != nil {\n\t\tlogs.Error(err)\n\t\tos.Exit(0)\n\t}\n\tif ip == \"\" {\n\t\tip = \"0.0.0.0\"\n\t}\n\treturn net.ListenTCP(\"tcp\", &net.TCPAddr{net.ParseIP(ip), port, \"\"})\n}\n"
  },
  {
    "path": "server/proxy/base.go",
    "content": "package proxy\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"ehang.io/nps/bridge\"\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/file\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\ntype Service interface {\n\tStart() error\n\tClose() error\n}\n\ntype NetBridge interface {\n\tSendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error)\n}\n\n//BaseServer struct\ntype BaseServer struct {\n\tid           int\n\tbridge       NetBridge\n\ttask         *file.Tunnel\n\terrorContent []byte\n\tsync.Mutex\n}\n\nfunc NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer {\n\treturn &BaseServer{\n\t\tbridge:       bridge,\n\t\ttask:         task,\n\t\terrorContent: nil,\n\t\tMutex:        sync.Mutex{},\n\t}\n}\n\n//add the flow\nfunc (s *BaseServer) FlowAdd(in, out int64) {\n\ts.Lock()\n\tdefer s.Unlock()\n\ts.task.Flow.ExportFlow += out\n\ts.task.Flow.InletFlow += in\n}\n\n//change the flow\nfunc (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) {\n\ts.Lock()\n\tdefer s.Unlock()\n\thost.Flow.ExportFlow += out\n\thost.Flow.InletFlow += in\n}\n\n//write fail bytes to the connection\nfunc (s *BaseServer) writeConnFail(c net.Conn) {\n\tc.Write([]byte(common.ConnectionFailBytes))\n\tc.Write(s.errorContent)\n}\n\n//auth check\nfunc (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error {\n\tif u != \"\" && p != \"\" && !common.CheckAuth(r, u, p) {\n\t\tc.Write([]byte(common.UnauthorizedBytes))\n\t\tc.Close()\n\t\treturn errors.New(\"401 Unauthorized\")\n\t}\n\treturn nil\n}\n\n//check flow limit of the client ,and decrease the allow num of client\nfunc (s *BaseServer) CheckFlowAndConnNum(client *file.Client) error {\n\tif client.Flow.FlowLimit > 0 && (client.Flow.FlowLimit<<20) < (client.Flow.ExportFlow+client.Flow.InletFlow) {\n\t\treturn errors.New(\"Traffic exceeded\")\n\t}\n\tif !client.GetConn() {\n\t\treturn errors.New(\"Connections exceed the current client limit\")\n\t}\n\treturn nil\n}\n\n//create a new connection and start bytes copying\nfunc (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, rb []byte, tp string, f func(), flow *file.Flow, localProxy bool) error {\n\tlink := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String(), localProxy)\n\tif target, err := s.bridge.SendLinkInfo(client.Id, link, s.task); err != nil {\n\t\tlogs.Warn(\"get connection from client id %d  error %s\", client.Id, err.Error())\n\t\tc.Close()\n\t\treturn err\n\t} else {\n\t\tif f != nil {\n\t\t\tf()\n\t\t}\n\t\tconn.CopyWaitGroup(target, c.Conn, link.Crypt, link.Compress, client.Rate, flow, true, rb)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/proxy/http.go",
    "content": "package proxy\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"ehang.io/nps/bridge\"\n\t\"ehang.io/nps/lib/cache\"\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server/connection\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\ntype httpServer struct {\n\tBaseServer\n\thttpPort      int\n\thttpsPort     int\n\thttpServer    *http.Server\n\thttpsServer   *http.Server\n\thttpsListener net.Listener\n\tuseCache      bool\n\taddOrigin     bool\n\tcache         *cache.Cache\n\tcacheLen      int\n}\n\nfunc NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int, addOrigin bool) *httpServer {\n\thttpServer := &httpServer{\n\t\tBaseServer: BaseServer{\n\t\t\ttask:   c,\n\t\t\tbridge: bridge,\n\t\t\tMutex:  sync.Mutex{},\n\t\t},\n\t\thttpPort:  httpPort,\n\t\thttpsPort: httpsPort,\n\t\tuseCache:  useCache,\n\t\tcacheLen:  cacheLen,\n\t\taddOrigin: addOrigin,\n\t}\n\tif useCache {\n\t\thttpServer.cache = cache.New(cacheLen)\n\t}\n\treturn httpServer\n}\n\nfunc (s *httpServer) Start() error {\n\tvar err error\n\tif s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), \"web\", \"static\", \"page\", \"error.html\")); err != nil {\n\t\ts.errorContent = []byte(\"nps 404\")\n\t}\n\tif s.httpPort > 0 {\n\t\ts.httpServer = s.NewServer(s.httpPort, \"http\")\n\t\tgo func() {\n\t\t\tl, err := connection.GetHttpListener()\n\t\t\tif err != nil {\n\t\t\t\tlogs.Error(err)\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t\terr = s.httpServer.Serve(l)\n\t\t\tif err != nil {\n\t\t\t\tlogs.Error(err)\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t}()\n\t}\n\tif s.httpsPort > 0 {\n\t\ts.httpsServer = s.NewServer(s.httpsPort, \"https\")\n\t\tgo func() {\n\t\t\ts.httpsListener, err = connection.GetHttpsListener()\n\t\t\tif err != nil {\n\t\t\t\tlogs.Error(err)\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t\tlogs.Error(NewHttpsServer(s.httpsListener, s.bridge, s.useCache, s.cacheLen).Start())\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc (s *httpServer) Close() error {\n\tif s.httpsListener != nil {\n\t\ts.httpsListener.Close()\n\t}\n\tif s.httpsServer != nil {\n\t\ts.httpsServer.Close()\n\t}\n\tif s.httpServer != nil {\n\t\ts.httpServer.Close()\n\t}\n\treturn nil\n}\n\nfunc (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) {\n\thijacker, ok := w.(http.Hijacker)\n\tif !ok {\n\t\thttp.Error(w, \"Hijacking not supported\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tc, _, err := hijacker.Hijack()\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusServiceUnavailable)\n\t}\n\ts.handleHttp(conn.NewConn(c), r)\n}\n\nfunc (s *httpServer) handleHttp(c *conn.Conn, r *http.Request) {\n\tvar (\n\t\thost       *file.Host\n\t\ttarget     net.Conn\n\t\terr        error\n\t\tconnClient io.ReadWriteCloser\n\t\tscheme     = r.URL.Scheme\n\t\tlk         *conn.Link\n\t\ttargetAddr string\n\t\tlenConn    *conn.LenConn\n\t\tisReset    bool\n\t\twg         sync.WaitGroup\n\t)\n\tdefer func() {\n\t\tif connClient != nil {\n\t\t\tconnClient.Close()\n\t\t} else {\n\t\t\ts.writeConnFail(c.Conn)\n\t\t}\n\t\tc.Close()\n\t}()\nreset:\n\tif isReset {\n\t\thost.Client.AddConn()\n\t}\n\tif host, err = file.GetDb().GetInfoByHost(r.Host, r); err != nil {\n\t\tlogs.Notice(\"the url %s %s %s can't be parsed!\", r.URL.Scheme, r.Host, r.RequestURI)\n\t\treturn\n\t}\n\tif err := s.CheckFlowAndConnNum(host.Client); err != nil {\n\t\tlogs.Warn(\"client id %d, host id %d, error %s, when https connection\", host.Client.Id, host.Id, err.Error())\n\t\treturn\n\t}\n\tif !isReset {\n\t\tdefer host.Client.AddConn()\n\t}\n\tif err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil {\n\t\tlogs.Warn(\"auth error\", err, r.RemoteAddr)\n\t\treturn\n\t}\n\tif targetAddr, err = host.Target.GetRandomTarget(); err != nil {\n\t\tlogs.Warn(err.Error())\n\t\treturn\n\t}\n\tlk = conn.NewLink(\"http\", targetAddr, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr, host.Target.LocalProxy)\n\tif target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, nil); err != nil {\n\t\tlogs.Notice(\"connect to target %s error %s\", lk.Host, err)\n\t\treturn\n\t}\n\tconnClient = conn.GetConn(target, lk.Crypt, lk.Compress, host.Client.Rate, true)\n\n\t//read from inc-client\n\tgo func() {\n\t\twg.Add(1)\n\t\tisReset = false\n\t\tdefer connClient.Close()\n\t\tdefer func() {\n\t\t\twg.Done()\n\t\t\tif !isReset {\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t}()\n\t\tfor {\n\t\t\tif resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil || r == nil {\n\t\t\t\t// if there got broken pipe, http.ReadResponse will get a nil\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\t//if the cache is start and the response is in the extension,store the response to the cache list\n\t\t\t\tif s.useCache && r.URL != nil && strings.Contains(r.URL.Path, \".\") {\n\t\t\t\t\tb, err := httputil.DumpResponse(resp, true)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tc.Write(b)\n\t\t\t\t\thost.Flow.Add(0, int64(len(b)))\n\t\t\t\t\ts.cache.Add(filepath.Join(host.Host, r.URL.Path), b)\n\t\t\t\t} else {\n\t\t\t\t\tlenConn := conn.NewLenConn(c)\n\t\t\t\t\tif err := resp.Write(lenConn); err != nil {\n\t\t\t\t\t\tlogs.Error(err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\thost.Flow.Add(0, int64(lenConn.Len))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\t//if the cache start and the request is in the cache list, return the cache\n\t\tif s.useCache {\n\t\t\tif v, ok := s.cache.Get(filepath.Join(host.Host, r.URL.Path)); ok {\n\t\t\t\tn, err := c.Write(v.([]byte))\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tlogs.Trace(\"%s request, method %s, host %s, url %s, remote address %s, return cache\", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String())\n\t\t\t\thost.Flow.Add(0, int64(n))\n\t\t\t\t//if return cache and does not create a new conn with client and Connection is not set or close, close the connection.\n\t\t\t\tif strings.ToLower(r.Header.Get(\"Connection\")) == \"close\" || strings.ToLower(r.Header.Get(\"Connection\")) == \"\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tgoto readReq\n\t\t\t}\n\t\t}\n\n\t\t//change the host and header and set proxy setting\n\t\tcommon.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String(), s.addOrigin)\n\t\tlogs.Trace(\"%s request, method %s, host %s, url %s, remote address %s, target %s\", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String(), lk.Host)\n\t\t//write\n\t\tlenConn = conn.NewLenConn(connClient)\n\t\tif err := r.Write(lenConn); err != nil {\n\t\t\tlogs.Error(err)\n\t\t\tbreak\n\t\t}\n\t\thost.Flow.Add(int64(lenConn.Len), 0)\n\n\treadReq:\n\t\t//read req from connection\n\t\tif r, err = http.ReadRequest(bufio.NewReader(c)); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tr.URL.Scheme = scheme\n\t\t//What happened ，Why one character less???\n\t\tr.Method = resetReqMethod(r.Method)\n\t\tif hostTmp, err := file.GetDb().GetInfoByHost(r.Host, r); err != nil {\n\t\t\tlogs.Notice(\"the url %s %s %s can't be parsed!\", r.URL.Scheme, r.Host, r.RequestURI)\n\t\t\tbreak\n\t\t} else if host != hostTmp {\n\t\t\thost = hostTmp\n\t\t\tisReset = true\n\t\t\tconnClient.Close()\n\t\t\tgoto reset\n\t\t}\n\t}\n\twg.Wait()\n}\n\nfunc resetReqMethod(method string) string {\n\tif method == \"ET\" {\n\t\treturn \"GET\"\n\t}\n\tif method == \"OST\" {\n\t\treturn \"POST\"\n\t}\n\treturn method\n}\n\nfunc (s *httpServer) NewServer(port int, scheme string) *http.Server {\n\treturn &http.Server{\n\t\tAddr: \":\" + strconv.Itoa(port),\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tr.URL.Scheme = scheme\n\t\t\ts.handleTunneling(w, r)\n\t\t}),\n\t\t// Disable HTTP/2.\n\t\tTLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),\n\t}\n}\n"
  },
  {
    "path": "server/proxy/https.go",
    "content": "package proxy\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\n\t\"ehang.io/nps/lib/cache\"\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/file\"\n\t\"github.com/astaxie/beego\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/pkg/errors\"\n)\n\ntype HttpsServer struct {\n\thttpServer\n\tlistener         net.Listener\n\thttpsListenerMap sync.Map\n}\n\nfunc NewHttpsServer(l net.Listener, bridge NetBridge, useCache bool, cacheLen int) *HttpsServer {\n\thttps := &HttpsServer{listener: l}\n\thttps.bridge = bridge\n\thttps.useCache = useCache\n\tif useCache {\n\t\thttps.cache = cache.New(cacheLen)\n\t}\n\treturn https\n}\n\n//start https server\nfunc (https *HttpsServer) Start() error {\n\tif b, err := beego.AppConfig.Bool(\"https_just_proxy\"); err == nil && b {\n\t\tconn.Accept(https.listener, func(c net.Conn) {\n\t\t\thttps.handleHttps(c)\n\t\t})\n\t} else {\n\t\t//start the default listener\n\t\tcertFile := beego.AppConfig.String(\"https_default_cert_file\")\n\t\tkeyFile := beego.AppConfig.String(\"https_default_key_file\")\n\t\tif common.FileExists(certFile) && common.FileExists(keyFile) {\n\t\t\tl := NewHttpsListener(https.listener)\n\t\t\thttps.NewHttps(l, certFile, keyFile)\n\t\t\thttps.httpsListenerMap.Store(\"default\", l)\n\t\t}\n\t\tconn.Accept(https.listener, func(c net.Conn) {\n\t\t\tserverName, rb := GetServerNameFromClientHello(c)\n\t\t\t//if the clientHello does not contains sni ,use the default ssl certificate\n\t\t\tif serverName == \"\" {\n\t\t\t\tserverName = \"default\"\n\t\t\t}\n\t\t\tvar l *HttpsListener\n\t\t\tif v, ok := https.httpsListenerMap.Load(serverName); ok {\n\t\t\t\tl = v.(*HttpsListener)\n\t\t\t} else {\n\t\t\t\tr := buildHttpsRequest(serverName)\n\t\t\t\tif host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {\n\t\t\t\t\tc.Close()\n\t\t\t\t\tlogs.Notice(\"the url %s can't be parsed!,remote addr %s\", serverName, c.RemoteAddr().String())\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\tif !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {\n\t\t\t\t\t\t//if the host cert file or key file is not set ,use the default file\n\t\t\t\t\t\tif v, ok := https.httpsListenerMap.Load(\"default\"); ok {\n\t\t\t\t\t\t\tl = v.(*HttpsListener)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tc.Close()\n\t\t\t\t\t\t\tlogs.Error(\"the key %s cert %s file is not exist\", host.KeyFilePath, host.CertFilePath)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tl = NewHttpsListener(https.listener)\n\t\t\t\t\t\thttps.NewHttps(l, host.CertFilePath, host.KeyFilePath)\n\t\t\t\t\t\thttps.httpsListenerMap.Store(serverName, l)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tacceptConn := conn.NewConn(c)\n\t\t\tacceptConn.Rb = rb\n\t\t\tl.acceptConn <- acceptConn\n\t\t})\n\t}\n\treturn nil\n}\n\n// close\nfunc (https *HttpsServer) Close() error {\n\treturn https.listener.Close()\n}\n\n// new https server by cert and key file\nfunc (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {\n\tgo func() {\n\t\tlogs.Error(https.NewServer(0, \"https\").ServeTLS(l, certFile, keyFile))\n\t}()\n}\n\n//handle the https which is just proxy to other client\nfunc (https *HttpsServer) handleHttps(c net.Conn) {\n\thostName, rb := GetServerNameFromClientHello(c)\n\tvar targetAddr string\n\tr := buildHttpsRequest(hostName)\n\tvar host *file.Host\n\tvar err error\n\tif host, err = file.GetDb().GetInfoByHost(hostName, r); err != nil {\n\t\tc.Close()\n\t\tlogs.Notice(\"the url %s can't be parsed!\", hostName)\n\t\treturn\n\t}\n\tif err := https.CheckFlowAndConnNum(host.Client); err != nil {\n\t\tlogs.Warn(\"client id %d, host id %d, error %s, when https connection\", host.Client.Id, host.Id, err.Error())\n\t\tc.Close()\n\t\treturn\n\t}\n\tdefer host.Client.AddConn()\n\tif err = https.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {\n\t\tlogs.Warn(\"auth error\", err, r.RemoteAddr)\n\t\treturn\n\t}\n\tif targetAddr, err = host.Target.GetRandomTarget(); err != nil {\n\t\tlogs.Warn(err.Error())\n\t}\n\tlogs.Trace(\"new https connection,clientId %d,host %s,remote address %s\", host.Client.Id, r.Host, c.RemoteAddr().String())\n\thttps.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy)\n}\n\ntype HttpsListener struct {\n\tacceptConn     chan *conn.Conn\n\tparentListener net.Listener\n}\n\n// https listener\nfunc NewHttpsListener(l net.Listener) *HttpsListener {\n\treturn &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}\n}\n\n// accept\nfunc (httpsListener *HttpsListener) Accept() (net.Conn, error) {\n\thttpsConn := <-httpsListener.acceptConn\n\tif httpsConn == nil {\n\t\treturn nil, errors.New(\"get connection error\")\n\t}\n\treturn httpsConn, nil\n}\n\n// close\nfunc (httpsListener *HttpsListener) Close() error {\n\treturn nil\n}\n\n// addr\nfunc (httpsListener *HttpsListener) Addr() net.Addr {\n\treturn httpsListener.parentListener.Addr()\n}\n\n// get server name from connection by read client hello bytes\nfunc GetServerNameFromClientHello(c net.Conn) (string, []byte) {\n\tbuf := make([]byte, 4096)\n\tdata := make([]byte, 4096)\n\tn, err := c.Read(buf)\n\tif err != nil {\n\t\treturn \"\", nil\n\t}\n\tif n < 42 {\n\t\treturn \"\", nil\n\t}\n\tcopy(data, buf[:n])\n\tclientHello := new(crypt.ClientHelloMsg)\n\tclientHello.Unmarshal(data[5:n])\n\treturn clientHello.GetServerName(), buf[:n]\n}\n\n// build https request\nfunc buildHttpsRequest(hostName string) *http.Request {\n\tr := new(http.Request)\n\tr.RequestURI = \"/\"\n\tr.URL = new(url.URL)\n\tr.URL.Scheme = \"https\"\n\tr.Host = hostName\n\treturn r\n}\n"
  },
  {
    "path": "server/proxy/p2p.go",
    "content": "package proxy\n\nimport (\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\ntype P2PServer struct {\n\tBaseServer\n\tp2pPort  int\n\tp2p      map[string]*p2p\n\tlistener *net.UDPConn\n}\n\ntype p2p struct {\n\tvisitorAddr  *net.UDPAddr\n\tproviderAddr *net.UDPAddr\n}\n\nfunc NewP2PServer(p2pPort int) *P2PServer {\n\treturn &P2PServer{\n\t\tp2pPort: p2pPort,\n\t\tp2p:     make(map[string]*p2p),\n\t}\n}\n\nfunc (s *P2PServer) Start() error {\n\tlogs.Info(\"start p2p server port\", s.p2pPort)\n\tvar err error\n\ts.listener, err = net.ListenUDP(\"udp\", &net.UDPAddr{net.ParseIP(\"0.0.0.0\"), s.p2pPort, \"\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tbuf := common.BufPoolUdp.Get().([]byte)\n\t\tn, addr, err := s.listener.ReadFromUDP(buf)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tgo s.handleP2P(addr, string(buf[:n]))\n\t}\n\treturn nil\n}\n\nfunc (s *P2PServer) handleP2P(addr *net.UDPAddr, str string) {\n\tvar (\n\t\tv  *p2p\n\t\tok bool\n\t)\n\tarr := strings.Split(str, common.CONN_DATA_SEQ)\n\tif len(arr) < 2 {\n\t\treturn\n\t}\n\tif v, ok = s.p2p[arr[0]]; !ok {\n\t\tv = new(p2p)\n\t\ts.p2p[arr[0]] = v\n\t}\n\tlogs.Trace(\"new p2p connection ,role %s , password %s ,local address %s\", arr[1], arr[0], addr.String())\n\tif arr[1] == common.WORK_P2P_VISITOR {\n\t\tv.visitorAddr = addr\n\t\tfor i := 20; i > 0; i-- {\n\t\t\tif v.providerAddr != nil {\n\t\t\t\ts.listener.WriteTo([]byte(v.providerAddr.String()), v.visitorAddr)\n\t\t\t\ts.listener.WriteTo([]byte(v.visitorAddr.String()), v.providerAddr)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t\tdelete(s.p2p, arr[0])\n\t} else {\n\t\tv.providerAddr = addr\n\t}\n}\n"
  },
  {
    "path": "server/proxy/socks5.go",
    "content": "package proxy\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/file\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\nconst (\n\tipV4            = 1\n\tdomainName      = 3\n\tipV6            = 4\n\tconnectMethod   = 1\n\tbindMethod      = 2\n\tassociateMethod = 3\n\t// The maximum packet size of any udp Associate packet, based on ethernet's max size,\n\t// minus the IP and UDP headers. IPv4 has a 20 byte header, UDP adds an\n\t// additional 4 bytes.  This is a total overhead of 24 bytes.  Ethernet's\n\t// max packet size is 1500 bytes,  1500 - 24 = 1476.\n\tmaxUDPPacketSize = 1476\n)\n\nconst (\n\tsucceeded uint8 = iota\n\tserverFailure\n\tnotAllowed\n\tnetworkUnreachable\n\thostUnreachable\n\tconnectionRefused\n\tttlExpired\n\tcommandNotSupported\n\taddrTypeNotSupported\n)\n\nconst (\n\tUserPassAuth    = uint8(2)\n\tuserAuthVersion = uint8(1)\n\tauthSuccess     = uint8(0)\n\tauthFailure     = uint8(1)\n)\n\ntype Sock5ModeServer struct {\n\tBaseServer\n\tlistener net.Listener\n}\n\n//req\nfunc (s *Sock5ModeServer) handleRequest(c net.Conn) {\n\t/*\n\t\tThe SOCKS request is formed as follows:\n\t\t+----+-----+-------+------+----------+----------+\n\t\t|VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |\n\t\t+----+-----+-------+------+----------+----------+\n\t\t| 1  |  1  | X'00' |  1   | Variable |    2     |\n\t\t+----+-----+-------+------+----------+----------+\n\t*/\n\theader := make([]byte, 3)\n\n\t_, err := io.ReadFull(c, header)\n\n\tif err != nil {\n\t\tlogs.Warn(\"illegal request\", err)\n\t\tc.Close()\n\t\treturn\n\t}\n\n\tswitch header[1] {\n\tcase connectMethod:\n\t\ts.handleConnect(c)\n\tcase bindMethod:\n\t\ts.handleBind(c)\n\tcase associateMethod:\n\t\ts.handleUDP(c)\n\tdefault:\n\t\ts.sendReply(c, commandNotSupported)\n\t\tc.Close()\n\t}\n}\n\n//reply\nfunc (s *Sock5ModeServer) sendReply(c net.Conn, rep uint8) {\n\treply := []byte{\n\t\t5,\n\t\trep,\n\t\t0,\n\t\t1,\n\t}\n\n\tlocalAddr := c.LocalAddr().String()\n\tlocalHost, localPort, _ := net.SplitHostPort(localAddr)\n\tipBytes := net.ParseIP(localHost).To4()\n\tnPort, _ := strconv.Atoi(localPort)\n\treply = append(reply, ipBytes...)\n\tportBytes := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(portBytes, uint16(nPort))\n\treply = append(reply, portBytes...)\n\n\tc.Write(reply)\n}\n\n//do conn\nfunc (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) {\n\taddrType := make([]byte, 1)\n\tc.Read(addrType)\n\tvar host string\n\tswitch addrType[0] {\n\tcase ipV4:\n\t\tipv4 := make(net.IP, net.IPv4len)\n\t\tc.Read(ipv4)\n\t\thost = ipv4.String()\n\tcase ipV6:\n\t\tipv6 := make(net.IP, net.IPv6len)\n\t\tc.Read(ipv6)\n\t\thost = ipv6.String()\n\tcase domainName:\n\t\tvar domainLen uint8\n\t\tbinary.Read(c, binary.BigEndian, &domainLen)\n\t\tdomain := make([]byte, domainLen)\n\t\tc.Read(domain)\n\t\thost = string(domain)\n\tdefault:\n\t\ts.sendReply(c, addrTypeNotSupported)\n\t\treturn\n\t}\n\n\tvar port uint16\n\tbinary.Read(c, binary.BigEndian, &port)\n\t// connect to host\n\taddr := net.JoinHostPort(host, strconv.Itoa(int(port)))\n\tvar ltype string\n\tif command == associateMethod {\n\t\tltype = common.CONN_UDP\n\t} else {\n\t\tltype = common.CONN_TCP\n\t}\n\ts.DealClient(conn.NewConn(c), s.task.Client, addr, nil, ltype, func() {\n\t\ts.sendReply(c, succeeded)\n\t}, s.task.Flow, s.task.Target.LocalProxy)\n\treturn\n}\n\n//conn\nfunc (s *Sock5ModeServer) handleConnect(c net.Conn) {\n\ts.doConnect(c, connectMethod)\n}\n\n// passive mode\nfunc (s *Sock5ModeServer) handleBind(c net.Conn) {\n}\nfunc (s *Sock5ModeServer) sendUdpReply(writeConn net.Conn, c net.Conn, rep uint8, serverIp string) {\n\treply := []byte{\n\t\t5,\n\t\trep,\n\t\t0,\n\t\t1,\n\t}\n\tlocalHost, localPort, _ := net.SplitHostPort(c.LocalAddr().String())\n\tlocalHost = serverIp\n\tipBytes := net.ParseIP(localHost).To4()\n\tnPort, _ := strconv.Atoi(localPort)\n\treply = append(reply, ipBytes...)\n\tportBytes := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(portBytes, uint16(nPort))\n\treply = append(reply, portBytes...)\n\twriteConn.Write(reply)\n\n}\n\nfunc (s *Sock5ModeServer) handleUDP(c net.Conn) {\n\tdefer c.Close()\n\taddrType := make([]byte, 1)\n\tc.Read(addrType)\n\tvar host string\n\tswitch addrType[0] {\n\tcase ipV4:\n\t\tipv4 := make(net.IP, net.IPv4len)\n\t\tc.Read(ipv4)\n\t\thost = ipv4.String()\n\tcase ipV6:\n\t\tipv6 := make(net.IP, net.IPv6len)\n\t\tc.Read(ipv6)\n\t\thost = ipv6.String()\n\tcase domainName:\n\t\tvar domainLen uint8\n\t\tbinary.Read(c, binary.BigEndian, &domainLen)\n\t\tdomain := make([]byte, domainLen)\n\t\tc.Read(domain)\n\t\thost = string(domain)\n\tdefault:\n\t\ts.sendReply(c, addrTypeNotSupported)\n\t\treturn\n\t}\n\t//读取端口\n\tvar port uint16\n\tbinary.Read(c, binary.BigEndian, &port)\n\tlogs.Warn(host, string(port))\n\treplyAddr, err := net.ResolveUDPAddr(\"udp\", s.task.ServerIp+\":0\")\n\tif err != nil {\n\t\tlogs.Error(\"build local reply addr error\", err)\n\t\treturn\n\t}\n\treply, err := net.ListenUDP(\"udp\", replyAddr)\n\tif err != nil {\n\t\ts.sendReply(c, addrTypeNotSupported)\n\t\tlogs.Error(\"listen local reply udp port error\")\n\t\treturn\n\t}\n\t// reply the local addr\n\ts.sendUdpReply(c, reply, succeeded, common.GetServerIpByClientIp(c.RemoteAddr().(*net.TCPAddr).IP))\n\tdefer reply.Close()\n\t// new a tunnel to client\n\tlink := conn.NewLink(\"udp5\", \"\", s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String(), false)\n\ttarget, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task)\n\tif err != nil {\n\t\tlogs.Warn(\"get connection from client id %d  error %s\", s.task.Client.Id, err.Error())\n\t\treturn\n\t}\n\n\tvar clientAddr net.Addr\n\t// copy buffer\n\tgo func() {\n\t\tb := common.BufPoolUdp.Get().([]byte)\n\t\tdefer common.BufPoolUdp.Put(b)\n\t\tdefer c.Close()\n\n\t\tfor {\n\t\t\tn, laddr, err := reply.ReadFrom(b)\n\t\t\tif err != nil {\n\t\t\t\tlogs.Error(\"read data from %s err %s\", reply.LocalAddr().String(), err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif clientAddr == nil {\n\t\t\t\tclientAddr = laddr\n\t\t\t}\n\t\t\tif _, err := target.Write(b[:n]); err != nil {\n\t\t\t\tlogs.Error(\"write data to client error\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tvar l int32\n\t\tb := common.BufPoolUdp.Get().([]byte)\n\t\tdefer common.BufPoolUdp.Put(b)\n\t\tdefer c.Close()\n\t\tfor {\n\t\t\tif err := binary.Read(target, binary.LittleEndian, &l); err != nil || l >= common.PoolSizeUdp || l <= 0 {\n\t\t\t\tlogs.Warn(\"read len bytes error\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbinary.Read(target, binary.LittleEndian, b[:l])\n\t\t\tif err != nil {\n\t\t\t\tlogs.Warn(\"read data form client error\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif _, err := reply.WriteTo(b[:l], clientAddr); err != nil {\n\t\t\t\tlogs.Warn(\"write data to user \", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tb := common.BufPoolUdp.Get().([]byte)\n\tdefer common.BufPoolUdp.Put(b)\n\tdefer target.Close()\n\tfor {\n\t\t_, err := c.Read(b)\n\t\tif err != nil {\n\t\t\tc.Close()\n\t\t\treturn\n\t\t}\n\t}\n}\n\n//new conn\nfunc (s *Sock5ModeServer) handleConn(c net.Conn) {\n\tbuf := make([]byte, 2)\n\tif _, err := io.ReadFull(c, buf); err != nil {\n\t\tlogs.Warn(\"negotiation err\", err)\n\t\tc.Close()\n\t\treturn\n\t}\n\n\tif version := buf[0]; version != 5 {\n\t\tlogs.Warn(\"only support socks5, request from: \", c.RemoteAddr())\n\t\tc.Close()\n\t\treturn\n\t}\n\tnMethods := buf[1]\n\n\tmethods := make([]byte, nMethods)\n\tif len, err := c.Read(methods); len != int(nMethods) || err != nil {\n\t\tlogs.Warn(\"wrong method\")\n\t\tc.Close()\n\t\treturn\n\t}\n\tif (s.task.Client.Cnf.U != \"\" && s.task.Client.Cnf.P != \"\") || (s.task.MultiAccount != nil && len(s.task.MultiAccount.AccountMap) > 0) {\n\t\tbuf[1] = UserPassAuth\n\t\tc.Write(buf)\n\t\tif err := s.Auth(c); err != nil {\n\t\t\tc.Close()\n\t\t\tlogs.Warn(\"Validation failed:\", err)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tbuf[1] = 0\n\t\tc.Write(buf)\n\t}\n\ts.handleRequest(c)\n}\n\n//socks5 auth\nfunc (s *Sock5ModeServer) Auth(c net.Conn) error {\n\theader := []byte{0, 0}\n\tif _, err := io.ReadAtLeast(c, header, 2); err != nil {\n\t\treturn err\n\t}\n\tif header[0] != userAuthVersion {\n\t\treturn errors.New(\"验证方式不被支持\")\n\t}\n\tuserLen := int(header[1])\n\tuser := make([]byte, userLen)\n\tif _, err := io.ReadAtLeast(c, user, userLen); err != nil {\n\t\treturn err\n\t}\n\tif _, err := c.Read(header[:1]); err != nil {\n\t\treturn errors.New(\"密码长度获取错误\")\n\t}\n\tpassLen := int(header[0])\n\tpass := make([]byte, passLen)\n\tif _, err := io.ReadAtLeast(c, pass, passLen); err != nil {\n\t\treturn err\n\t}\n\n\tvar U, P string\n\tif s.task.MultiAccount != nil {\n\t\t// enable multi user auth\n\t\tU = string(user)\n\t\tvar ok bool\n\t\tP, ok = s.task.MultiAccount.AccountMap[U]\n\t\tif !ok {\n\t\t\treturn errors.New(\"验证不通过\")\n\t\t}\n\t} else {\n\t\tU = s.task.Client.Cnf.U\n\t\tP = s.task.Client.Cnf.P\n\t}\n\n\tif string(user) == U && string(pass) == P {\n\t\tif _, err := c.Write([]byte{userAuthVersion, authSuccess}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t} else {\n\t\tif _, err := c.Write([]byte{userAuthVersion, authFailure}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn errors.New(\"验证不通过\")\n\t}\n}\n\n//start\nfunc (s *Sock5ModeServer) Start() error {\n\treturn conn.NewTcpListenerAndProcess(s.task.ServerIp+\":\"+strconv.Itoa(s.task.Port), func(c net.Conn) {\n\t\tif err := s.CheckFlowAndConnNum(s.task.Client); err != nil {\n\t\t\tlogs.Warn(\"client id %d, task id %d, error %s, when socks5 connection\", s.task.Client.Id, s.task.Id, err.Error())\n\t\t\tc.Close()\n\t\t\treturn\n\t\t}\n\t\tlogs.Trace(\"New socks5 connection,client %d,remote address %s\", s.task.Client.Id, c.RemoteAddr())\n\t\ts.handleConn(c)\n\t\ts.task.Client.AddConn()\n\t}, &s.listener)\n}\n\n//new\nfunc NewSock5ModeServer(bridge NetBridge, task *file.Tunnel) *Sock5ModeServer {\n\ts := new(Sock5ModeServer)\n\ts.bridge = bridge\n\ts.task = task\n\treturn s\n}\n\n//close\nfunc (s *Sock5ModeServer) Close() error {\n\treturn s.listener.Close()\n}\n"
  },
  {
    "path": "server/proxy/tcp.go",
    "content": "package proxy\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"ehang.io/nps/bridge\"\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server/connection\"\n\t\"github.com/astaxie/beego\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\ntype TunnelModeServer struct {\n\tBaseServer\n\tprocess  process\n\tlistener net.Listener\n}\n\n//tcp|http|host\nfunc NewTunnelModeServer(process process, bridge NetBridge, task *file.Tunnel) *TunnelModeServer {\n\ts := new(TunnelModeServer)\n\ts.bridge = bridge\n\ts.process = process\n\ts.task = task\n\treturn s\n}\n\n//开始\nfunc (s *TunnelModeServer) Start() error {\n\treturn conn.NewTcpListenerAndProcess(s.task.ServerIp+\":\"+strconv.Itoa(s.task.Port), func(c net.Conn) {\n\t\tif err := s.CheckFlowAndConnNum(s.task.Client); err != nil {\n\t\t\tlogs.Warn(\"client id %d, task id %d,error %s, when tcp connection\", s.task.Client.Id, s.task.Id, err.Error())\n\t\t\tc.Close()\n\t\t\treturn\n\t\t}\n\t\tlogs.Trace(\"new tcp connection,local port %d,client %d,remote address %s\", s.task.Port, s.task.Client.Id, c.RemoteAddr())\n\t\ts.process(conn.NewConn(c), s)\n\t\ts.task.Client.AddConn()\n\t}, &s.listener)\n}\n\n//close\nfunc (s *TunnelModeServer) Close() error {\n\treturn s.listener.Close()\n}\n\n//web管理方式\ntype WebServer struct {\n\tBaseServer\n}\n\n//开始\nfunc (s *WebServer) Start() error {\n\tp, _ := beego.AppConfig.Int(\"web_port\")\n\tif p == 0 {\n\t\tstop := make(chan struct{})\n\t\t<-stop\n\t}\n\tbeego.BConfig.WebConfig.Session.SessionOn = true\n\tbeego.SetStaticPath(beego.AppConfig.String(\"web_base_url\")+\"/static\", filepath.Join(common.GetRunPath(), \"web\", \"static\"))\n\tbeego.SetViewsPath(filepath.Join(common.GetRunPath(), \"web\", \"views\"))\n\terr := errors.New(\"Web management startup failure \")\n\tvar l net.Listener\n\tif l, err = connection.GetWebManagerListener(); err == nil {\n\t\tbeego.InitBeforeHTTPRun()\n\t\tif beego.AppConfig.String(\"web_open_ssl\") == \"true\" {\n\t\t\tkeyPath := beego.AppConfig.String(\"web_key_file\")\n\t\t\tcertPath := beego.AppConfig.String(\"web_cert_file\")\n\t\t\terr = http.ServeTLS(l, beego.BeeApp.Handlers, certPath, keyPath)\n\t\t} else {\n\t\t\terr = http.Serve(l, beego.BeeApp.Handlers)\n\t\t}\n\t} else {\n\t\tlogs.Error(err)\n\t}\n\treturn err\n}\n\nfunc (s *WebServer) Close() error {\n\treturn nil\n}\n\n//new\nfunc NewWebServer(bridge *bridge.Bridge) *WebServer {\n\ts := new(WebServer)\n\ts.bridge = bridge\n\treturn s\n}\n\ntype process func(c *conn.Conn, s *TunnelModeServer) error\n\n//tcp proxy\nfunc ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {\n\ttargetAddr, err := s.task.Target.GetRandomTarget()\n\tif err != nil {\n\t\tc.Close()\n\t\tlogs.Warn(\"tcp port %d ,client id %d,task id %d connect error %s\", s.task.Port, s.task.Client.Id, s.task.Id, err.Error())\n\t\treturn err\n\t}\n\treturn s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy)\n}\n\n//http proxy\nfunc ProcessHttp(c *conn.Conn, s *TunnelModeServer) error {\n\t_, addr, rb, err, r := c.GetHost()\n\tif err != nil {\n\t\tc.Close()\n\t\tlogs.Info(err)\n\t\treturn err\n\t}\n\tif r.Method == \"CONNECT\" {\n\t\tc.Write([]byte(\"HTTP/1.1 200 Connection established\\r\\n\\r\\n\"))\n\t\trb = nil\n\t}\n\tif err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil {\n\t\treturn err\n\t}\n\treturn s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy)\n}\n"
  },
  {
    "path": "server/proxy/transport.go",
    "content": "// +build !windows\n\npackage proxy\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\t\"syscall\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n)\n\nfunc HandleTrans(c *conn.Conn, s *TunnelModeServer) error {\n\tif addr, err := getAddress(c.Conn); err != nil {\n\t\treturn err\n\t} else {\n\t\treturn s.DealClient(c, s.task.Client, addr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy)\n\t}\n}\n\nconst SO_ORIGINAL_DST = 80\n\nfunc getAddress(conn net.Conn) (string, error) {\n\tsysrawconn, f := conn.(syscall.Conn)\n\tif !f {\n\t\treturn \"\", nil\n\t}\n\trawConn, err := sysrawconn.SyscallConn()\n\tif err != nil {\n\t\treturn \"\", nil\n\t}\n\tvar ip string\n\tvar port uint16\n\terr = rawConn.Control(func(fd uintptr) {\n\t\taddr, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tip = net.IP(addr.Multiaddr[4:8]).String()\n\t\tport = uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3])\n\t})\n\treturn ip + \":\" + strconv.Itoa(int(port)), nil\n}\n"
  },
  {
    "path": "server/proxy/transport_windows.go",
    "content": "// +build windows\n\npackage proxy\n\nimport (\n\t\"ehang.io/nps/lib/conn\"\n)\n\nfunc HandleTrans(c *conn.Conn, s *TunnelModeServer) error {\n\treturn nil\n}\n"
  },
  {
    "path": "server/proxy/udp.go",
    "content": "package proxy\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"ehang.io/nps/bridge\"\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/conn\"\n\t\"ehang.io/nps/lib/file\"\n\t\"github.com/astaxie/beego/logs\"\n)\n\ntype UdpModeServer struct {\n\tBaseServer\n\taddrMap  sync.Map\n\tlistener *net.UDPConn\n}\n\nfunc NewUdpModeServer(bridge *bridge.Bridge, task *file.Tunnel) *UdpModeServer {\n\ts := new(UdpModeServer)\n\ts.bridge = bridge\n\ts.task = task\n\treturn s\n}\n\n//开始\nfunc (s *UdpModeServer) Start() error {\n\tvar err error\n\tif s.task.ServerIp == \"\" {\n\t\ts.task.ServerIp = \"0.0.0.0\"\n\t}\n\ts.listener, err = net.ListenUDP(\"udp\", &net.UDPAddr{net.ParseIP(s.task.ServerIp), s.task.Port, \"\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tbuf := common.BufPoolUdp.Get().([]byte)\n\t\tn, addr, err := s.listener.ReadFromUDP(buf)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tlogs.Trace(\"New udp connection,client %d,remote address %s\", s.task.Client.Id, addr)\n\t\tgo s.process(addr, buf[:n])\n\t}\n\treturn nil\n}\n\nfunc (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {\n\tif v, ok := s.addrMap.Load(addr.String()); ok {\n\t\tclientConn, ok := v.(io.ReadWriteCloser)\n\t\tif ok {\n\t\t\tclientConn.Write(data)\n\t\t\ts.task.Flow.Add(int64(len(data)), 0)\n\t\t}\n\t} else {\n\t\tif err := s.CheckFlowAndConnNum(s.task.Client); err != nil {\n\t\t\tlogs.Warn(\"client id %d, task id %d,error %s, when udp connection\", s.task.Client.Id, s.task.Id, err.Error())\n\t\t\treturn\n\t\t}\n\t\tdefer s.task.Client.AddConn()\n\t\tlink := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String(), s.task.Target.LocalProxy)\n\t\tif clientConn, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task); err != nil {\n\t\t\treturn\n\t\t} else {\n\t\t\ttarget := conn.GetConn(clientConn, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, nil, true)\n\t\t\ts.addrMap.Store(addr.String(), target)\n\t\t\tdefer target.Close()\n\n\t\t\ttarget.Write(data)\n\n\t\t\tbuf := common.BufPoolUdp.Get().([]byte)\n\t\t\tdefer common.BufPoolUdp.Put(buf)\n\n\t\t\ts.task.Flow.Add(int64(len(data)), 0)\n\t\t\tfor {\n\t\t\t\tclientConn.SetReadDeadline(time.Now().Add(time.Minute * 10))\n\t\t\t\tif n, err := target.Read(buf); err != nil {\n\t\t\t\t\ts.addrMap.Delete(addr.String())\n\t\t\t\t\tlogs.Warn(err)\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\ts.listener.WriteTo(buf[:n], addr)\n\t\t\t\t\ts.task.Flow.Add(0, int64(n))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *UdpModeServer) Close() error {\n\treturn s.listener.Close()\n}\n"
  },
  {
    "path": "server/server.go",
    "content": "package server\n\nimport (\n\t\"ehang.io/nps/lib/version\"\n\t\"errors\"\n\t\"math\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"ehang.io/nps/bridge\"\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server/proxy\"\n\t\"ehang.io/nps/server/tool\"\n\t\"github.com/astaxie/beego\"\n\t\"github.com/astaxie/beego/logs\"\n\t\"github.com/shirou/gopsutil/v3/cpu\"\n\t\"github.com/shirou/gopsutil/v3/load\"\n\t\"github.com/shirou/gopsutil/v3/mem\"\n\t\"github.com/shirou/gopsutil/v3/net\"\n)\n\nvar (\n\tBridge  *bridge.Bridge\n\tRunList sync.Map //map[int]interface{}\n)\n\nfunc init() {\n\tRunList = sync.Map{}\n}\n\n//init task from db\nfunc InitFromCsv() {\n\t//Add a public password\n\tif vkey := beego.AppConfig.String(\"public_vkey\"); vkey != \"\" {\n\t\tc := file.NewClient(vkey, true, true)\n\t\tfile.GetDb().NewClient(c)\n\t\tRunList.Store(c.Id, nil)\n\t\t//RunList[c.Id] = nil\n\t}\n\t//Initialize services in server-side files\n\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tif value.(*file.Tunnel).Status {\n\t\t\tAddTask(value.(*file.Tunnel))\n\t\t}\n\t\treturn true\n\t})\n}\n\n//get bridge command\nfunc DealBridgeTask() {\n\tfor {\n\t\tselect {\n\t\tcase t := <-Bridge.OpenTask:\n\t\t\tAddTask(t)\n\t\tcase t := <-Bridge.CloseTask:\n\t\t\tStopServer(t.Id)\n\t\tcase id := <-Bridge.CloseClient:\n\t\t\tDelTunnelAndHostByClientId(id, true)\n\t\t\tif v, ok := file.GetDb().JsonDb.Clients.Load(id); ok {\n\t\t\t\tif v.(*file.Client).NoStore {\n\t\t\t\t\tfile.GetDb().DelClient(id)\n\t\t\t\t}\n\t\t\t}\n\t\tcase tunnel := <-Bridge.OpenTask:\n\t\t\tStartTask(tunnel.Id)\n\t\tcase s := <-Bridge.SecretChan:\n\t\t\tlogs.Trace(\"New secret connection, addr\", s.Conn.Conn.RemoteAddr())\n\t\t\tif t := file.GetDb().GetTaskByMd5Password(s.Password); t != nil {\n\t\t\t\tif t.Status {\n\t\t\t\t\tgo proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target.TargetStr, nil, common.CONN_TCP, nil, t.Flow, t.Target.LocalProxy)\n\t\t\t\t} else {\n\t\t\t\t\ts.Conn.Close()\n\t\t\t\t\tlogs.Trace(\"This key %s cannot be processed,status is close\", s.Password)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogs.Trace(\"This key %s cannot be processed\", s.Password)\n\t\t\t\ts.Conn.Close()\n\t\t\t}\n\t\t}\n\t}\n}\n\n//start a new server\nfunc StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string, bridgeDisconnect int) {\n\tBridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String(\"ip_limit\")), RunList, bridgeDisconnect)\n\tgo func() {\n\t\tif err := Bridge.StartTunnel(); err != nil {\n\t\t\tlogs.Error(\"start server bridge error\", err)\n\t\t\tos.Exit(0)\n\t\t}\n\t}()\n\tif p, err := beego.AppConfig.Int(\"p2p_port\"); err == nil {\n\t\tgo proxy.NewP2PServer(p).Start()\n\t\tgo proxy.NewP2PServer(p + 1).Start()\n\t\tgo proxy.NewP2PServer(p + 2).Start()\n\t}\n\tgo DealBridgeTask()\n\tgo dealClientFlow()\n\tif svr := NewMode(Bridge, cnf); svr != nil {\n\t\tif err := svr.Start(); err != nil {\n\t\t\tlogs.Error(err)\n\t\t}\n\t\tRunList.Store(cnf.Id, svr)\n\t\t//RunList[cnf.Id] = svr\n\t} else {\n\t\tlogs.Error(\"Incorrect startup mode %s\", cnf.Mode)\n\t}\n}\n\nfunc dealClientFlow() {\n\tticker := time.NewTicker(time.Minute)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tdealClientData()\n\t\t}\n\t}\n}\n\n//new a server by mode name\nfunc NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service {\n\tvar service proxy.Service\n\tswitch c.Mode {\n\tcase \"tcp\", \"file\":\n\t\tservice = proxy.NewTunnelModeServer(proxy.ProcessTunnel, Bridge, c)\n\tcase \"socks5\":\n\t\tservice = proxy.NewSock5ModeServer(Bridge, c)\n\tcase \"httpProxy\":\n\t\tservice = proxy.NewTunnelModeServer(proxy.ProcessHttp, Bridge, c)\n\tcase \"tcpTrans\":\n\t\tservice = proxy.NewTunnelModeServer(proxy.HandleTrans, Bridge, c)\n\tcase \"udp\":\n\t\tservice = proxy.NewUdpModeServer(Bridge, c)\n\tcase \"webServer\":\n\t\tInitFromCsv()\n\t\tt := &file.Tunnel{\n\t\t\tPort:   0,\n\t\t\tMode:   \"httpHostServer\",\n\t\t\tStatus: true,\n\t\t}\n\t\tAddTask(t)\n\t\tservice = proxy.NewWebServer(Bridge)\n\tcase \"httpHostServer\":\n\t\thttpPort, _ := beego.AppConfig.Int(\"http_proxy_port\")\n\t\thttpsPort, _ := beego.AppConfig.Int(\"https_proxy_port\")\n\t\tuseCache, _ := beego.AppConfig.Bool(\"http_cache\")\n\t\tcacheLen, _ := beego.AppConfig.Int(\"http_cache_length\")\n\t\taddOrigin, _ := beego.AppConfig.Bool(\"http_add_origin_header\")\n\t\tservice = proxy.NewHttp(Bridge, c, httpPort, httpsPort, useCache, cacheLen, addOrigin)\n\t}\n\treturn service\n}\n\n//stop server\nfunc StopServer(id int) error {\n\t//if v, ok := RunList[id]; ok {\n\tif v, ok := RunList.Load(id); ok {\n\t\tif svr, ok := v.(proxy.Service); ok {\n\t\t\tif err := svr.Close(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlogs.Info(\"stop server id %d\", id)\n\t\t} else {\n\t\t\tlogs.Warn(\"stop server id %d error\", id)\n\t\t}\n\t\tif t, err := file.GetDb().GetTask(id); err != nil {\n\t\t\treturn err\n\t\t} else {\n\t\t\tt.Status = false\n\t\t\tfile.GetDb().UpdateTask(t)\n\t\t}\n\t\t//delete(RunList, id)\n\t\tRunList.Delete(id)\n\t\treturn nil\n\t}\n\treturn errors.New(\"task is not running\")\n}\n\n//add task\nfunc AddTask(t *file.Tunnel) error {\n\tif t.Mode == \"secret\" || t.Mode == \"p2p\" {\n\t\tlogs.Info(\"secret task %s start \", t.Remark)\n\t\t//RunList[t.Id] = nil\n\t\tRunList.Store(t.Id, nil)\n\t\treturn nil\n\t}\n\tif b := tool.TestServerPort(t.Port, t.Mode); !b && t.Mode != \"httpHostServer\" {\n\t\tlogs.Error(\"taskId %d start error port %d open failed\", t.Id, t.Port)\n\t\treturn errors.New(\"the port open error\")\n\t}\n\tif minute, err := beego.AppConfig.Int(\"flow_store_interval\"); err == nil && minute > 0 {\n\t\tgo flowSession(time.Minute * time.Duration(minute))\n\t}\n\tif svr := NewMode(Bridge, t); svr != nil {\n\t\tlogs.Info(\"tunnel task %s start mode：%s port %d\", t.Remark, t.Mode, t.Port)\n\t\t//RunList[t.Id] = svr\n\t\tRunList.Store(t.Id, svr)\n\t\tgo func() {\n\t\t\tif err := svr.Start(); err != nil {\n\t\t\t\tlogs.Error(\"clientId %d taskId %d start error %s\", t.Client.Id, t.Id, err)\n\t\t\t\t//delete(RunList, t.Id)\n\t\t\t\tRunList.Delete(t.Id)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t} else {\n\t\treturn errors.New(\"the mode is not correct\")\n\t}\n\treturn nil\n}\n\n//start task\nfunc StartTask(id int) error {\n\tif t, err := file.GetDb().GetTask(id); err != nil {\n\t\treturn err\n\t} else {\n\t\tAddTask(t)\n\t\tt.Status = true\n\t\tfile.GetDb().UpdateTask(t)\n\t}\n\treturn nil\n}\n\n//delete task\nfunc DelTask(id int) error {\n\t//if _, ok := RunList[id]; ok {\n\tif _, ok := RunList.Load(id); ok {\n\t\tif err := StopServer(id); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn file.GetDb().DelTask(id)\n}\n\n//get task list by page num\nfunc GetTunnel(start, length int, typeVal string, clientId int, search string) ([]*file.Tunnel, int) {\n\tlist := make([]*file.Tunnel, 0)\n\tvar cnt int\n\tkeys := file.GetMapKeys(file.GetDb().JsonDb.Tasks, false, \"\", \"\")\n\tfor _, key := range keys {\n\t\tif value, ok := file.GetDb().JsonDb.Tasks.Load(key); ok {\n\t\t\tv := value.(*file.Tunnel)\n\t\t\tif (typeVal != \"\" && v.Mode != typeVal || (clientId != 0 && v.Client.Id != clientId)) || (typeVal == \"\" && clientId != v.Client.Id) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif search != \"\" && !(v.Id == common.GetIntNoErrByStr(search) || v.Port == common.GetIntNoErrByStr(search) || strings.Contains(v.Password, search) || strings.Contains(v.Remark, search)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcnt++\n\t\t\tif _, ok := Bridge.Client.Load(v.Client.Id); ok {\n\t\t\t\tv.Client.IsConnect = true\n\t\t\t} else {\n\t\t\t\tv.Client.IsConnect = false\n\t\t\t}\n\t\t\tif start--; start < 0 {\n\t\t\t\tif length--; length >= 0 {\n\t\t\t\t\t//if _, ok := RunList[v.Id]; ok {\n\t\t\t\t\tif _, ok := RunList.Load(v.Id); ok {\n\t\t\t\t\t\tv.RunStatus = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tv.RunStatus = false\n\t\t\t\t\t}\n\t\t\t\t\tlist = append(list, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn list, cnt\n}\n\n//get client list\nfunc GetClientList(start, length int, search, sort, order string, clientId int) (list []*file.Client, cnt int) {\n\tlist, cnt = file.GetDb().GetClientList(start, length, search, sort, order, clientId)\n\tdealClientData()\n\treturn\n}\n\nfunc dealClientData() {\n\tfile.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\tv := value.(*file.Client)\n\t\tif vv, ok := Bridge.Client.Load(v.Id); ok {\n\t\t\tv.IsConnect = true\n\t\t\tv.Version = vv.(*bridge.Client).Version\n\t\t} else {\n\t\t\tv.IsConnect = false\n\t\t}\n\t\tv.Flow.InletFlow = 0\n\t\tv.Flow.ExportFlow = 0\n\t\tfile.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\t\th := value.(*file.Host)\n\t\t\tif h.Client.Id == v.Id {\n\t\t\t\tv.Flow.InletFlow += h.Flow.InletFlow\n\t\t\t\tv.Flow.ExportFlow += h.Flow.ExportFlow\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\t\tt := value.(*file.Tunnel)\n\t\t\tif t.Client.Id == v.Id {\n\t\t\t\tv.Flow.InletFlow += t.Flow.InletFlow\n\t\t\t\tv.Flow.ExportFlow += t.Flow.ExportFlow\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\treturn true\n\t})\n\treturn\n}\n\n//delete all host and tasks by client id\nfunc DelTunnelAndHostByClientId(clientId int, justDelNoStore bool) {\n\tvar ids []int\n\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tv := value.(*file.Tunnel)\n\t\tif justDelNoStore && !v.NoStore {\n\t\t\treturn true\n\t\t}\n\t\tif v.Client.Id == clientId {\n\t\t\tids = append(ids, v.Id)\n\t\t}\n\t\treturn true\n\t})\n\tfor _, id := range ids {\n\t\tDelTask(id)\n\t}\n\tids = ids[:0]\n\tfile.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {\n\t\tv := value.(*file.Host)\n\t\tif justDelNoStore && !v.NoStore {\n\t\t\treturn true\n\t\t}\n\t\tif v.Client.Id == clientId {\n\t\t\tids = append(ids, v.Id)\n\t\t}\n\t\treturn true\n\t})\n\tfor _, id := range ids {\n\t\tfile.GetDb().DelHost(id)\n\t}\n}\n\n//close the client\nfunc DelClientConnect(clientId int) {\n\tBridge.DelClient(clientId)\n}\n\nfunc GetDashboardData() map[string]interface{} {\n\tdata := make(map[string]interface{})\n\tdata[\"version\"] = version.VERSION\n\tdata[\"hostCount\"] = common.GeSynctMapLen(file.GetDb().JsonDb.Hosts)\n\tdata[\"clientCount\"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients)\n\tif beego.AppConfig.String(\"public_vkey\") != \"\" { //remove public vkey\n\t\tdata[\"clientCount\"] = data[\"clientCount\"].(int) - 1\n\t}\n\tdealClientData()\n\tc := 0\n\tvar in, out int64\n\tfile.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\tv := value.(*file.Client)\n\t\tif v.IsConnect {\n\t\t\tc += 1\n\t\t}\n\t\tin += v.Flow.InletFlow\n\t\tout += v.Flow.ExportFlow\n\t\treturn true\n\t})\n\tdata[\"clientOnlineCount\"] = c\n\tdata[\"inletFlowCount\"] = int(in)\n\tdata[\"exportFlowCount\"] = int(out)\n\tvar tcp, udp, secret, socks5, p2p, http int\n\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tswitch value.(*file.Tunnel).Mode {\n\t\tcase \"tcp\":\n\t\t\ttcp += 1\n\t\tcase \"socks5\":\n\t\t\tsocks5 += 1\n\t\tcase \"httpProxy\":\n\t\t\thttp += 1\n\t\tcase \"udp\":\n\t\t\tudp += 1\n\t\tcase \"p2p\":\n\t\t\tp2p += 1\n\t\tcase \"secret\":\n\t\t\tsecret += 1\n\t\t}\n\t\treturn true\n\t})\n\n\tdata[\"tcpC\"] = tcp\n\tdata[\"udpCount\"] = udp\n\tdata[\"socks5Count\"] = socks5\n\tdata[\"httpProxyCount\"] = http\n\tdata[\"secretCount\"] = secret\n\tdata[\"p2pCount\"] = p2p\n\tdata[\"bridgeType\"] = beego.AppConfig.String(\"bridge_type\")\n\tdata[\"httpProxyPort\"] = beego.AppConfig.String(\"http_proxy_port\")\n\tdata[\"httpsProxyPort\"] = beego.AppConfig.String(\"https_proxy_port\")\n\tdata[\"ipLimit\"] = beego.AppConfig.String(\"ip_limit\")\n\tdata[\"flowStoreInterval\"] = beego.AppConfig.String(\"flow_store_interval\")\n\tdata[\"serverIp\"] = beego.AppConfig.String(\"p2p_ip\")\n\tdata[\"p2pPort\"] = beego.AppConfig.String(\"p2p_port\")\n\tdata[\"logLevel\"] = beego.AppConfig.String(\"log_level\")\n\ttcpCount := 0\n\n\tfile.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\ttcpCount += int(value.(*file.Client).NowConn)\n\t\treturn true\n\t})\n\tdata[\"tcpCount\"] = tcpCount\n\tcpuPercet, _ := cpu.Percent(0, true)\n\tvar cpuAll float64\n\tfor _, v := range cpuPercet {\n\t\tcpuAll += v\n\t}\n\tloads, _ := load.Avg()\n\tdata[\"load\"] = loads.String()\n\tdata[\"cpu\"] = math.Round(cpuAll / float64(len(cpuPercet)))\n\tswap, _ := mem.SwapMemory()\n\tdata[\"swap_mem\"] = math.Round(swap.UsedPercent)\n\tvir, _ := mem.VirtualMemory()\n\tdata[\"virtual_mem\"] = math.Round(vir.UsedPercent)\n\tconn, _ := net.ProtoCounters(nil)\n\tio1, _ := net.IOCounters(false)\n\ttime.Sleep(time.Millisecond * 500)\n\tio2, _ := net.IOCounters(false)\n\tif len(io2) > 0 && len(io1) > 0 {\n\t\tdata[\"io_send\"] = (io2[0].BytesSent - io1[0].BytesSent) * 2\n\t\tdata[\"io_recv\"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2\n\t}\n\tfor _, v := range conn {\n\t\tdata[v.Protocol] = v.Stats[\"CurrEstab\"]\n\t}\n\t//chart\n\tvar fg int\n\tif len(tool.ServerStatus) >= 10 {\n\t\tfg = len(tool.ServerStatus) / 10\n\t\tfor i := 0; i <= 9; i++ {\n\t\t\tdata[\"sys\"+strconv.Itoa(i+1)] = tool.ServerStatus[i*fg]\n\t\t}\n\t}\n\treturn data\n}\n\nfunc flowSession(m time.Duration) {\n\tticker := time.NewTicker(m)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tfile.GetDb().JsonDb.StoreHostToJsonFile()\n\t\t\tfile.GetDb().JsonDb.StoreTasksToJsonFile()\n\t\t\tfile.GetDb().JsonDb.StoreClientsToJsonFile()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/test/test.go",
    "content": "package test\n\nimport (\n\t\"log\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/file\"\n\t\"github.com/astaxie/beego\"\n)\n\nfunc TestServerConfig() {\n\tvar postTcpArr []int\n\tvar postUdpArr []int\n\tfile.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {\n\t\tv := value.(*file.Tunnel)\n\t\tif v.Mode == \"udp\" {\n\t\t\tisInArr(&postUdpArr, v.Port, v.Remark, \"udp\")\n\t\t} else if v.Port != 0 {\n\n\t\t\tisInArr(&postTcpArr, v.Port, v.Remark, \"tcp\")\n\t\t}\n\t\treturn true\n\t})\n\tp, err := beego.AppConfig.Int(\"web_port\")\n\tif err != nil {\n\t\tlog.Fatalln(\"Getting web management port error :\", err)\n\t} else {\n\t\tisInArr(&postTcpArr, p, \"Web Management port\", \"tcp\")\n\t}\n\n\tif p := beego.AppConfig.String(\"bridge_port\"); p != \"\" {\n\t\tif port, err := strconv.Atoi(p); err != nil {\n\t\t\tlog.Fatalln(\"get Server and client communication portserror:\", err)\n\t\t} else if beego.AppConfig.String(\"bridge_type\") == \"kcp\" {\n\t\t\tisInArr(&postUdpArr, port, \"Server and client communication ports\", \"udp\")\n\t\t} else {\n\t\t\tisInArr(&postTcpArr, port, \"Server and client communication ports\", \"tcp\")\n\t\t}\n\t}\n\n\tif p := beego.AppConfig.String(\"httpProxyPort\"); p != \"\" {\n\t\tif port, err := strconv.Atoi(p); err != nil {\n\t\t\tlog.Fatalln(\"get http port error:\", err)\n\t\t} else {\n\t\t\tisInArr(&postTcpArr, port, \"https port\", \"tcp\")\n\t\t}\n\t}\n\tif p := beego.AppConfig.String(\"https_proxy_port\"); p != \"\" {\n\t\tif b, err := beego.AppConfig.Bool(\"https_just_proxy\"); !(err == nil && b) {\n\t\t\tif port, err := strconv.Atoi(p); err != nil {\n\t\t\t\tlog.Fatalln(\"get https port error\", err)\n\t\t\t} else {\n\t\t\t\tif beego.AppConfig.String(\"pemPath\") != \"\" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String(\"pemPath\"))) {\n\t\t\t\t\tlog.Fatalf(\"ssl certFile %s is not exist\", beego.AppConfig.String(\"pemPath\"))\n\t\t\t\t}\n\t\t\t\tif beego.AppConfig.String(\"keyPath\") != \"\" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String(\"keyPath\"))) {\n\t\t\t\t\tlog.Fatalf(\"ssl keyFile %s is not exist\", beego.AppConfig.String(\"pemPath\"))\n\t\t\t\t}\n\t\t\t\tisInArr(&postTcpArr, port, \"http port\", \"tcp\")\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc isInArr(arr *[]int, val int, remark string, tp string) {\n\tfor _, v := range *arr {\n\t\tif v == val {\n\t\t\tlog.Fatalf(\"the port %d is reused,remark: %s\", val, remark)\n\t\t}\n\t}\n\tif tp == \"tcp\" {\n\t\tif !common.TestTcpPort(val) {\n\t\t\tlog.Fatalf(\"open the %d port error ,remark: %s\", val, remark)\n\t\t}\n\t} else {\n\t\tif !common.TestUdpPort(val) {\n\t\t\tlog.Fatalf(\"open the %d port error ,remark: %s\", val, remark)\n\t\t}\n\t}\n\t*arr = append(*arr, val)\n\treturn\n}\n"
  },
  {
    "path": "server/tool/utils.go",
    "content": "package tool\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"github.com/astaxie/beego\"\n\t\"github.com/shirou/gopsutil/v3/cpu\"\n\t\"github.com/shirou/gopsutil/v3/load\"\n\t\"github.com/shirou/gopsutil/v3/mem\"\n\t\"github.com/shirou/gopsutil/v3/net\"\n)\n\nvar (\n\tports        []int\n\tServerStatus []map[string]interface{}\n)\n\nfunc StartSystemInfo() {\n\tif b, err := beego.AppConfig.Bool(\"system_info_display\"); err == nil && b {\n\t\tServerStatus = make([]map[string]interface{}, 0, 1500)\n\t\tgo getSeverStatus()\n\t}\n}\n\nfunc InitAllowPort() {\n\tp := beego.AppConfig.String(\"allow_ports\")\n\tports = common.GetPorts(p)\n}\n\nfunc TestServerPort(p int, m string) (b bool) {\n\tif m == \"p2p\" || m == \"secret\" {\n\t\treturn true\n\t}\n\tif p > 65535 || p < 0 {\n\t\treturn false\n\t}\n\tif len(ports) != 0 {\n\t\tif !common.InIntArr(ports, p) {\n\t\t\treturn false\n\t\t}\n\t}\n\tif m == \"udp\" {\n\t\tb = common.TestUdpPort(p)\n\t} else {\n\t\tb = common.TestTcpPort(p)\n\t}\n\treturn\n}\n\nfunc getSeverStatus() {\n\tfor {\n\t\tif len(ServerStatus) < 10 {\n\t\t\ttime.Sleep(time.Second)\n\t\t} else {\n\t\t\ttime.Sleep(time.Minute)\n\t\t}\n\t\tcpuPercet, _ := cpu.Percent(0, true)\n\t\tvar cpuAll float64\n\t\tfor _, v := range cpuPercet {\n\t\t\tcpuAll += v\n\t\t}\n\t\tm := make(map[string]interface{})\n\t\tloads, _ := load.Avg()\n\t\tm[\"load1\"] = loads.Load1\n\t\tm[\"load5\"] = loads.Load5\n\t\tm[\"load15\"] = loads.Load15\n\t\tm[\"cpu\"] = math.Round(cpuAll / float64(len(cpuPercet)))\n\t\tswap, _ := mem.SwapMemory()\n\t\tm[\"swap_mem\"] = math.Round(swap.UsedPercent)\n\t\tvir, _ := mem.VirtualMemory()\n\t\tm[\"virtual_mem\"] = math.Round(vir.UsedPercent)\n\t\tconn, _ := net.ProtoCounters(nil)\n\t\tio1, _ := net.IOCounters(false)\n\t\ttime.Sleep(time.Millisecond * 500)\n\t\tio2, _ := net.IOCounters(false)\n\t\tif len(io2) > 0 && len(io1) > 0 {\n\t\t\tm[\"io_send\"] = (io2[0].BytesSent - io1[0].BytesSent) * 2\n\t\t\tm[\"io_recv\"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2\n\t\t}\n\t\tt := time.Now()\n\t\tm[\"time\"] = strconv.Itoa(t.Hour()) + \":\" + strconv.Itoa(t.Minute()) + \":\" + strconv.Itoa(t.Second())\n\n\t\tfor _, v := range conn {\n\t\t\tm[v.Protocol] = v.Stats[\"CurrEstab\"]\n\t\t}\n\t\tif len(ServerStatus) >= 1440 {\n\t\t\tServerStatus = ServerStatus[1:]\n\t\t}\n\t\tServerStatus = append(ServerStatus, m)\n\t}\n}\n"
  },
  {
    "path": "web/controllers/auth.go",
    "content": "package controllers\n\nimport (\n\t\"encoding/hex\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/crypt\"\n\t\"github.com/astaxie/beego\"\n)\n\ntype AuthController struct {\n\tbeego.Controller\n}\n\nfunc (s *AuthController) GetAuthKey() {\n\tm := make(map[string]interface{})\n\tdefer func() {\n\t\ts.Data[\"json\"] = m\n\t\ts.ServeJSON()\n\t}()\n\tif cryptKey := beego.AppConfig.String(\"auth_crypt_key\"); len(cryptKey) != 16 {\n\t\tm[\"status\"] = 0\n\t\treturn\n\t} else {\n\t\tb, err := crypt.AesEncrypt([]byte(beego.AppConfig.String(\"auth_key\")), []byte(cryptKey))\n\t\tif err != nil {\n\t\t\tm[\"status\"] = 0\n\t\t\treturn\n\t\t}\n\t\tm[\"status\"] = 1\n\t\tm[\"crypt_auth_key\"] = hex.EncodeToString(b)\n\t\tm[\"crypt_type\"] = \"aes cbc\"\n\t\treturn\n\t}\n}\n\nfunc (s *AuthController) GetTime() {\n\tm := make(map[string]interface{})\n\tm[\"time\"] = time.Now().Unix()\n\ts.Data[\"json\"] = m\n\ts.ServeJSON()\n}\n"
  },
  {
    "path": "web/controllers/base.go",
    "content": "package controllers\n\nimport (\n\t\"html\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/crypt\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server\"\n\t\"github.com/astaxie/beego\"\n)\n\ntype BaseController struct {\n\tbeego.Controller\n\tcontrollerName string\n\tactionName     string\n}\n\n//初始化参数\nfunc (s *BaseController) Prepare() {\n\ts.Data[\"web_base_url\"] = beego.AppConfig.String(\"web_base_url\")\n\tcontrollerName, actionName := s.GetControllerAndAction()\n\ts.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])\n\ts.actionName = strings.ToLower(actionName)\n\t// web api verify\n\t// param 1 is md5(authKey+Current timestamp)\n\t// param 2 is timestamp (It's limited to 20 seconds.)\n\tmd5Key := s.getEscapeString(\"auth_key\")\n\ttimestamp := s.GetIntNoErr(\"timestamp\")\n\tconfigKey := beego.AppConfig.String(\"auth_key\")\n\ttimeNowUnix := time.Now().Unix()\n\tif !(md5Key != \"\" && (math.Abs(float64(timeNowUnix-int64(timestamp))) <= 20) && (crypt.Md5(configKey+strconv.Itoa(timestamp)) == md5Key)) {\n\t\tif s.GetSession(\"auth\") != true {\n\t\t\ts.Redirect(beego.AppConfig.String(\"web_base_url\")+\"/login/index\", 302)\n\t\t}\n\t} else {\n\t\ts.SetSession(\"isAdmin\", true)\n\t\ts.Data[\"isAdmin\"] = true\n\t}\n\tif s.GetSession(\"isAdmin\") != nil && !s.GetSession(\"isAdmin\").(bool) {\n\t\ts.Ctx.Input.SetData(\"client_id\", s.GetSession(\"clientId\").(int))\n\t\ts.Ctx.Input.SetParam(\"client_id\", strconv.Itoa(s.GetSession(\"clientId\").(int)))\n\t\ts.Data[\"isAdmin\"] = false\n\t\ts.Data[\"username\"] = s.GetSession(\"username\")\n\t\ts.CheckUserAuth()\n\t} else {\n\t\ts.Data[\"isAdmin\"] = true\n\t}\n\ts.Data[\"https_just_proxy\"], _ = beego.AppConfig.Bool(\"https_just_proxy\")\n\ts.Data[\"allow_user_login\"], _ = beego.AppConfig.Bool(\"allow_user_login\")\n\ts.Data[\"allow_flow_limit\"], _ = beego.AppConfig.Bool(\"allow_flow_limit\")\n\ts.Data[\"allow_rate_limit\"], _ = beego.AppConfig.Bool(\"allow_rate_limit\")\n\ts.Data[\"allow_connection_num_limit\"], _ = beego.AppConfig.Bool(\"allow_connection_num_limit\")\n\ts.Data[\"allow_multi_ip\"], _ = beego.AppConfig.Bool(\"allow_multi_ip\")\n\ts.Data[\"system_info_display\"], _ = beego.AppConfig.Bool(\"system_info_display\")\n\ts.Data[\"allow_tunnel_num_limit\"], _ = beego.AppConfig.Bool(\"allow_tunnel_num_limit\")\n\ts.Data[\"allow_local_proxy\"], _ = beego.AppConfig.Bool(\"allow_local_proxy\")\n\ts.Data[\"allow_user_change_username\"], _ = beego.AppConfig.Bool(\"allow_user_change_username\")\n}\n\n//加载模板\nfunc (s *BaseController) display(tpl ...string) {\n\ts.Data[\"web_base_url\"] = beego.AppConfig.String(\"web_base_url\")\n\tvar tplname string\n\tif s.Data[\"menu\"] == nil {\n\t\ts.Data[\"menu\"] = s.actionName\n\t}\n\tif len(tpl) > 0 {\n\t\ttplname = strings.Join([]string{tpl[0], \"html\"}, \".\")\n\t} else {\n\t\ttplname = s.controllerName + \"/\" + s.actionName + \".html\"\n\t}\n\tip := s.Ctx.Request.Host\n\ts.Data[\"ip\"] = common.GetIpByAddr(ip)\n\ts.Data[\"bridgeType\"] = beego.AppConfig.String(\"bridge_type\")\n\tif common.IsWindows() {\n\t\ts.Data[\"win\"] = \".exe\"\n\t}\n\ts.Data[\"p\"] = server.Bridge.TunnelPort\n\ts.Data[\"proxyPort\"] = beego.AppConfig.String(\"hostPort\")\n\ts.Layout = \"public/layout.html\"\n\ts.TplName = tplname\n}\n\n//错误\nfunc (s *BaseController) error() {\n\ts.Data[\"web_base_url\"] = beego.AppConfig.String(\"web_base_url\")\n\ts.Layout = \"public/layout.html\"\n\ts.TplName = \"public/error.html\"\n}\n\n//getEscapeString\nfunc (s *BaseController) getEscapeString(key string) string {\n\treturn html.EscapeString(s.GetString(key))\n}\n\n//去掉没有err返回值的int\nfunc (s *BaseController) GetIntNoErr(key string, def ...int) int {\n\tstrv := s.Ctx.Input.Query(key)\n\tif len(strv) == 0 && len(def) > 0 {\n\t\treturn def[0]\n\t}\n\tval, _ := strconv.Atoi(strv)\n\treturn val\n}\n\n//获取去掉错误的bool值\nfunc (s *BaseController) GetBoolNoErr(key string, def ...bool) bool {\n\tstrv := s.Ctx.Input.Query(key)\n\tif len(strv) == 0 && len(def) > 0 {\n\t\treturn def[0]\n\t}\n\tval, _ := strconv.ParseBool(strv)\n\treturn val\n}\n\n//ajax正确返回\nfunc (s *BaseController) AjaxOk(str string) {\n\ts.Data[\"json\"] = ajax(str, 1)\n\ts.ServeJSON()\n\ts.StopRun()\n}\n\n//ajax错误返回\nfunc (s *BaseController) AjaxErr(str string) {\n\ts.Data[\"json\"] = ajax(str, 0)\n\ts.ServeJSON()\n\ts.StopRun()\n}\n\n//组装ajax\nfunc ajax(str string, status int) map[string]interface{} {\n\tjson := make(map[string]interface{})\n\tjson[\"status\"] = status\n\tjson[\"msg\"] = str\n\treturn json\n}\n\n//ajax table返回\nfunc (s *BaseController) AjaxTable(list interface{}, cnt int, recordsTotal int, kwargs map[string]interface{}) {\n\tjson := make(map[string]interface{})\n\tjson[\"rows\"] = list\n\tjson[\"total\"] = recordsTotal\n\tif kwargs != nil {\n\t\tfor k, v := range kwargs {\n\t\t\tif v != nil {\n\t\t\t\tjson[k] = v\n\t\t\t}\n\t\t}\n\t}\n\ts.Data[\"json\"] = json\n\ts.ServeJSON()\n\ts.StopRun()\n}\n\n//ajax table参数\nfunc (s *BaseController) GetAjaxParams() (start, limit int) {\n\treturn s.GetIntNoErr(\"offset\"), s.GetIntNoErr(\"limit\")\n}\n\nfunc (s *BaseController) SetInfo(name string) {\n\ts.Data[\"name\"] = name\n}\n\nfunc (s *BaseController) SetType(name string) {\n\ts.Data[\"type\"] = name\n}\n\nfunc (s *BaseController) CheckUserAuth() {\n\tif s.controllerName == \"client\" {\n\t\tif s.actionName == \"add\" {\n\t\t\ts.StopRun()\n\t\t\treturn\n\t\t}\n\t\tif id := s.GetIntNoErr(\"id\"); id != 0 {\n\t\t\tif id != s.GetSession(\"clientId\").(int) {\n\t\t\t\ts.StopRun()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tif s.controllerName == \"index\" {\n\t\tif id := s.GetIntNoErr(\"id\"); id != 0 {\n\t\t\tbelong := false\n\t\t\tif strings.Contains(s.actionName, \"h\") {\n\t\t\t\tif v, ok := file.GetDb().JsonDb.Hosts.Load(id); ok {\n\t\t\t\t\tif v.(*file.Host).Client.Id == s.GetSession(\"clientId\").(int) {\n\t\t\t\t\t\tbelong = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif v, ok := file.GetDb().JsonDb.Tasks.Load(id); ok {\n\t\t\t\t\tif v.(*file.Tunnel).Client.Id == s.GetSession(\"clientId\").(int) {\n\t\t\t\t\t\tbelong = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !belong {\n\t\t\t\ts.StopRun()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "web/controllers/client.go",
    "content": "package controllers\n\nimport (\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/lib/rate\"\n\t\"ehang.io/nps/server\"\n\t\"github.com/astaxie/beego\"\n)\n\ntype ClientController struct {\n\tBaseController\n}\n\nfunc (s *ClientController) List() {\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"menu\"] = \"client\"\n\t\ts.SetInfo(\"client\")\n\t\ts.display(\"client/list\")\n\t\treturn\n\t}\n\tstart, length := s.GetAjaxParams()\n\tclientIdSession := s.GetSession(\"clientId\")\n\tvar clientId int\n\tif clientIdSession == nil {\n\t\tclientId = 0\n\t} else {\n\t\tclientId = clientIdSession.(int)\n\t}\n\tlist, cnt := server.GetClientList(start, length, s.getEscapeString(\"search\"), s.getEscapeString(\"sort\"), s.getEscapeString(\"order\"), clientId)\n\tcmd := make(map[string]interface{})\n\tip := s.Ctx.Request.Host\n\tcmd[\"ip\"] = common.GetIpByAddr(ip)\n\tcmd[\"bridgeType\"] = beego.AppConfig.String(\"bridge_type\")\n\tcmd[\"bridgePort\"] = server.Bridge.TunnelPort\n\ts.AjaxTable(list, cnt, cnt, cmd)\n}\n\n//添加客户端\nfunc (s *ClientController) Add() {\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"menu\"] = \"client\"\n\t\ts.SetInfo(\"add client\")\n\t\ts.display()\n\t} else {\n\t\tt := &file.Client{\n\t\t\tVerifyKey: s.getEscapeString(\"vkey\"),\n\t\t\tId:        int(file.GetDb().JsonDb.GetClientId()),\n\t\t\tStatus:    true,\n\t\t\tRemark:    s.getEscapeString(\"remark\"),\n\t\t\tCnf: &file.Config{\n\t\t\t\tU:        s.getEscapeString(\"u\"),\n\t\t\t\tP:        s.getEscapeString(\"p\"),\n\t\t\t\tCompress: common.GetBoolByStr(s.getEscapeString(\"compress\")),\n\t\t\t\tCrypt:    s.GetBoolNoErr(\"crypt\"),\n\t\t\t},\n\t\t\tConfigConnAllow: s.GetBoolNoErr(\"config_conn_allow\"),\n\t\t\tRateLimit:       s.GetIntNoErr(\"rate_limit\"),\n\t\t\tMaxConn:         s.GetIntNoErr(\"max_conn\"),\n\t\t\tWebUserName:     s.getEscapeString(\"web_username\"),\n\t\t\tWebPassword:     s.getEscapeString(\"web_password\"),\n\t\t\tMaxTunnelNum:    s.GetIntNoErr(\"max_tunnel\"),\n\t\t\tFlow: &file.Flow{\n\t\t\t\tExportFlow: 0,\n\t\t\t\tInletFlow:  0,\n\t\t\t\tFlowLimit:  int64(s.GetIntNoErr(\"flow_limit\")),\n\t\t\t},\n\t\t}\n\t\tif err := file.GetDb().NewClient(t); err != nil {\n\t\t\ts.AjaxErr(err.Error())\n\t\t}\n\t\ts.AjaxOk(\"add success\")\n\t}\n}\nfunc (s *ClientController) GetClient() {\n\tif s.Ctx.Request.Method == \"POST\" {\n\t\tid := s.GetIntNoErr(\"id\")\n\t\tdata := make(map[string]interface{})\n\t\tif c, err := file.GetDb().GetClient(id); err != nil {\n\t\t\tdata[\"code\"] = 0\n\t\t} else {\n\t\t\tdata[\"code\"] = 1\n\t\t\tdata[\"data\"] = c\n\t\t}\n\t\ts.Data[\"json\"] = data\n\t\ts.ServeJSON()\n\t}\n}\n\n//修改客户端\nfunc (s *ClientController) Edit() {\n\tid := s.GetIntNoErr(\"id\")\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"menu\"] = \"client\"\n\t\tif c, err := file.GetDb().GetClient(id); err != nil {\n\t\t\ts.error()\n\t\t} else {\n\t\t\ts.Data[\"c\"] = c\n\t\t}\n\t\ts.SetInfo(\"edit client\")\n\t\ts.display()\n\t} else {\n\t\tif c, err := file.GetDb().GetClient(id); err != nil {\n\t\t\ts.error()\n\t\t\ts.AjaxErr(\"client ID not found\")\n\t\t\treturn\n\t\t} else {\n\t\t\tif s.getEscapeString(\"web_username\") != \"\" {\n\t\t\t\tif s.getEscapeString(\"web_username\") == beego.AppConfig.String(\"web_username\") || !file.GetDb().VerifyUserName(s.getEscapeString(\"web_username\"), c.Id) {\n\t\t\t\t\ts.AjaxErr(\"web login username duplicate, please reset\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif s.GetSession(\"isAdmin\").(bool) {\n\t\t\t\tif !file.GetDb().VerifyVkey(s.getEscapeString(\"vkey\"), c.Id) {\n\t\t\t\t\ts.AjaxErr(\"Vkey duplicate, please reset\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tc.VerifyKey = s.getEscapeString(\"vkey\")\n\t\t\t\tc.Flow.FlowLimit = int64(s.GetIntNoErr(\"flow_limit\"))\n\t\t\t\tc.RateLimit = s.GetIntNoErr(\"rate_limit\")\n\t\t\t\tc.MaxConn = s.GetIntNoErr(\"max_conn\")\n\t\t\t\tc.MaxTunnelNum = s.GetIntNoErr(\"max_tunnel\")\n\t\t\t}\n\t\t\tc.Remark = s.getEscapeString(\"remark\")\n\t\t\tc.Cnf.U = s.getEscapeString(\"u\")\n\t\t\tc.Cnf.P = s.getEscapeString(\"p\")\n\t\t\tc.Cnf.Compress = common.GetBoolByStr(s.getEscapeString(\"compress\"))\n\t\t\tc.Cnf.Crypt = s.GetBoolNoErr(\"crypt\")\n\t\t\tb, err := beego.AppConfig.Bool(\"allow_user_change_username\")\n\t\t\tif s.GetSession(\"isAdmin\").(bool) || (err == nil && b) {\n\t\t\t\tc.WebUserName = s.getEscapeString(\"web_username\")\n\t\t\t}\n\t\t\tc.WebPassword = s.getEscapeString(\"web_password\")\n\t\t\tc.ConfigConnAllow = s.GetBoolNoErr(\"config_conn_allow\")\n\t\t\tif c.Rate != nil {\n\t\t\t\tc.Rate.Stop()\n\t\t\t}\n\t\t\tif c.RateLimit > 0 {\n\t\t\t\tc.Rate = rate.NewRate(int64(c.RateLimit * 1024))\n\t\t\t\tc.Rate.Start()\n\t\t\t} else {\n\t\t\t\tc.Rate = rate.NewRate(int64(2 << 23))\n\t\t\t\tc.Rate.Start()\n\t\t\t}\n\t\t\tfile.GetDb().JsonDb.StoreClientsToJsonFile()\n\t\t}\n\t\ts.AjaxOk(\"save success\")\n\t}\n}\n\n//更改状态\nfunc (s *ClientController) ChangeStatus() {\n\tid := s.GetIntNoErr(\"id\")\n\tif client, err := file.GetDb().GetClient(id); err == nil {\n\t\tclient.Status = s.GetBoolNoErr(\"status\")\n\t\tif client.Status == false {\n\t\t\tserver.DelClientConnect(client.Id)\n\t\t}\n\t\ts.AjaxOk(\"modified success\")\n\t}\n\ts.AjaxErr(\"modified fail\")\n}\n\n//删除客户端\nfunc (s *ClientController) Del() {\n\tid := s.GetIntNoErr(\"id\")\n\tif err := file.GetDb().DelClient(id); err != nil {\n\t\ts.AjaxErr(\"delete error\")\n\t}\n\tserver.DelTunnelAndHostByClientId(id, false)\n\tserver.DelClientConnect(id)\n\ts.AjaxOk(\"delete success\")\n}\n"
  },
  {
    "path": "web/controllers/index.go",
    "content": "package controllers\n\nimport (\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server\"\n\t\"ehang.io/nps/server/tool\"\n\n\t\"github.com/astaxie/beego\"\n)\n\ntype IndexController struct {\n\tBaseController\n}\n\nfunc (s *IndexController) Index() {\n\ts.Data[\"web_base_url\"] = beego.AppConfig.String(\"web_base_url\")\n\ts.Data[\"data\"] = server.GetDashboardData()\n\ts.SetInfo(\"dashboard\")\n\ts.display(\"index/index\")\n}\nfunc (s *IndexController) Help() {\n\ts.SetInfo(\"about\")\n\ts.display(\"index/help\")\n}\n\nfunc (s *IndexController) Tcp() {\n\ts.SetInfo(\"tcp\")\n\ts.SetType(\"tcp\")\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) Udp() {\n\ts.SetInfo(\"udp\")\n\ts.SetType(\"udp\")\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) Socks5() {\n\ts.SetInfo(\"socks5\")\n\ts.SetType(\"socks5\")\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) Http() {\n\ts.SetInfo(\"http proxy\")\n\ts.SetType(\"httpProxy\")\n\ts.display(\"index/list\")\n}\nfunc (s *IndexController) File() {\n\ts.SetInfo(\"file server\")\n\ts.SetType(\"file\")\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) Secret() {\n\ts.SetInfo(\"secret\")\n\ts.SetType(\"secret\")\n\ts.display(\"index/list\")\n}\nfunc (s *IndexController) P2p() {\n\ts.SetInfo(\"p2p\")\n\ts.SetType(\"p2p\")\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) Host() {\n\ts.SetInfo(\"host\")\n\ts.SetType(\"hostServer\")\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) All() {\n\ts.Data[\"menu\"] = \"client\"\n\tclientId := s.getEscapeString(\"client_id\")\n\ts.Data[\"client_id\"] = clientId\n\ts.SetInfo(\"client id:\" + clientId)\n\ts.display(\"index/list\")\n}\n\nfunc (s *IndexController) GetTunnel() {\n\tstart, length := s.GetAjaxParams()\n\ttaskType := s.getEscapeString(\"type\")\n\tclientId := s.GetIntNoErr(\"client_id\")\n\tlist, cnt := server.GetTunnel(start, length, taskType, clientId, s.getEscapeString(\"search\"))\n\ts.AjaxTable(list, cnt, cnt, nil)\n}\n\nfunc (s *IndexController) Add() {\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"type\"] = s.getEscapeString(\"type\")\n\t\ts.Data[\"client_id\"] = s.getEscapeString(\"client_id\")\n\t\ts.SetInfo(\"add tunnel\")\n\t\ts.display()\n\t} else {\n\t\tt := &file.Tunnel{\n\t\t\tPort:      s.GetIntNoErr(\"port\"),\n\t\t\tServerIp:  s.getEscapeString(\"server_ip\"),\n\t\t\tMode:      s.getEscapeString(\"type\"),\n\t\t\tTarget:    &file.Target{TargetStr: s.getEscapeString(\"target\"), LocalProxy: s.GetBoolNoErr(\"local_proxy\")},\n\t\t\tId:        int(file.GetDb().JsonDb.GetTaskId()),\n\t\t\tStatus:    true,\n\t\t\tRemark:    s.getEscapeString(\"remark\"),\n\t\t\tPassword:  s.getEscapeString(\"password\"),\n\t\t\tLocalPath: s.getEscapeString(\"local_path\"),\n\t\t\tStripPre:  s.getEscapeString(\"strip_pre\"),\n\t\t\tFlow:      &file.Flow{},\n\t\t}\n\t\tif !tool.TestServerPort(t.Port, t.Mode) {\n\t\t\ts.AjaxErr(\"The port cannot be opened because it may has been occupied or is no longer allowed.\")\n\t\t}\n\t\tvar err error\n\t\tif t.Client, err = file.GetDb().GetClient(s.GetIntNoErr(\"client_id\")); err != nil {\n\t\t\ts.AjaxErr(err.Error())\n\t\t}\n\t\tif t.Client.MaxTunnelNum != 0 && t.Client.GetTunnelNum() >= t.Client.MaxTunnelNum {\n\t\t\ts.AjaxErr(\"The number of tunnels exceeds the limit\")\n\t\t}\n\t\tif err := file.GetDb().NewTask(t); err != nil {\n\t\t\ts.AjaxErr(err.Error())\n\t\t}\n\t\tif err := server.AddTask(t); err != nil {\n\t\t\ts.AjaxErr(err.Error())\n\t\t} else {\n\t\t\ts.AjaxOk(\"add success\")\n\t\t}\n\t}\n}\nfunc (s *IndexController) GetOneTunnel() {\n\tid := s.GetIntNoErr(\"id\")\n\tdata := make(map[string]interface{})\n\tif t, err := file.GetDb().GetTask(id); err != nil {\n\t\tdata[\"code\"] = 0\n\t} else {\n\t\tdata[\"code\"] = 1\n\t\tdata[\"data\"] = t\n\t}\n\ts.Data[\"json\"] = data\n\ts.ServeJSON()\n}\nfunc (s *IndexController) Edit() {\n\tid := s.GetIntNoErr(\"id\")\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\tif t, err := file.GetDb().GetTask(id); err != nil {\n\t\t\ts.error()\n\t\t} else {\n\t\t\ts.Data[\"t\"] = t\n\t\t}\n\t\ts.SetInfo(\"edit tunnel\")\n\t\ts.display()\n\t} else {\n\t\tif t, err := file.GetDb().GetTask(id); err != nil {\n\t\t\ts.error()\n\t\t} else {\n\t\t\tif client, err := file.GetDb().GetClient(s.GetIntNoErr(\"client_id\")); err != nil {\n\t\t\t\ts.AjaxErr(\"modified error,the client is not exist\")\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tt.Client = client\n\t\t\t}\n\t\t\tif s.GetIntNoErr(\"port\") != t.Port {\n\t\t\t\tif !tool.TestServerPort(s.GetIntNoErr(\"port\"), t.Mode) {\n\t\t\t\t\ts.AjaxErr(\"The port cannot be opened because it may has been occupied or is no longer allowed.\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Port = s.GetIntNoErr(\"port\")\n\t\t\t}\n\t\t\tt.ServerIp = s.getEscapeString(\"server_ip\")\n\t\t\tt.Mode = s.getEscapeString(\"type\")\n\t\t\tt.Target = &file.Target{TargetStr: s.getEscapeString(\"target\")}\n\t\t\tt.Password = s.getEscapeString(\"password\")\n\t\t\tt.Id = id\n\t\t\tt.LocalPath = s.getEscapeString(\"local_path\")\n\t\t\tt.StripPre = s.getEscapeString(\"strip_pre\")\n\t\t\tt.Remark = s.getEscapeString(\"remark\")\n\t\t\tt.Target.LocalProxy = s.GetBoolNoErr(\"local_proxy\")\n\t\t\tfile.GetDb().UpdateTask(t)\n\t\t\tserver.StopServer(t.Id)\n\t\t\tserver.StartTask(t.Id)\n\t\t}\n\t\ts.AjaxOk(\"modified success\")\n\t}\n}\n\nfunc (s *IndexController) Stop() {\n\tid := s.GetIntNoErr(\"id\")\n\tif err := server.StopServer(id); err != nil {\n\t\ts.AjaxErr(\"stop error\")\n\t}\n\ts.AjaxOk(\"stop success\")\n}\n\nfunc (s *IndexController) Del() {\n\tid := s.GetIntNoErr(\"id\")\n\tif err := server.DelTask(id); err != nil {\n\t\ts.AjaxErr(\"delete error\")\n\t}\n\ts.AjaxOk(\"delete success\")\n}\n\nfunc (s *IndexController) Start() {\n\tid := s.GetIntNoErr(\"id\")\n\tif err := server.StartTask(id); err != nil {\n\t\ts.AjaxErr(\"start error\")\n\t}\n\ts.AjaxOk(\"start success\")\n}\n\nfunc (s *IndexController) HostList() {\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"client_id\"] = s.getEscapeString(\"client_id\")\n\t\ts.Data[\"menu\"] = \"host\"\n\t\ts.SetInfo(\"host list\")\n\t\ts.display(\"index/hlist\")\n\t} else {\n\t\tstart, length := s.GetAjaxParams()\n\t\tclientId := s.GetIntNoErr(\"client_id\")\n\t\tlist, cnt := file.GetDb().GetHost(start, length, clientId, s.getEscapeString(\"search\"))\n\t\ts.AjaxTable(list, cnt, cnt, nil)\n\t}\n}\n\nfunc (s *IndexController) GetHost() {\n\tif s.Ctx.Request.Method == \"POST\" {\n\t\tdata := make(map[string]interface{})\n\t\tif h, err := file.GetDb().GetHostById(s.GetIntNoErr(\"id\")); err != nil {\n\t\t\tdata[\"code\"] = 0\n\t\t} else {\n\t\t\tdata[\"data\"] = h\n\t\t\tdata[\"code\"] = 1\n\t\t}\n\t\ts.Data[\"json\"] = data\n\t\ts.ServeJSON()\n\t}\n}\n\nfunc (s *IndexController) DelHost() {\n\tid := s.GetIntNoErr(\"id\")\n\tif err := file.GetDb().DelHost(id); err != nil {\n\t\ts.AjaxErr(\"delete error\")\n\t}\n\ts.AjaxOk(\"delete success\")\n}\n\nfunc (s *IndexController) AddHost() {\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"client_id\"] = s.getEscapeString(\"client_id\")\n\t\ts.Data[\"menu\"] = \"host\"\n\t\ts.SetInfo(\"add host\")\n\t\ts.display(\"index/hadd\")\n\t} else {\n\t\th := &file.Host{\n\t\t\tId:           int(file.GetDb().JsonDb.GetHostId()),\n\t\t\tHost:         s.getEscapeString(\"host\"),\n\t\t\tTarget:       &file.Target{TargetStr: s.getEscapeString(\"target\"), LocalProxy: s.GetBoolNoErr(\"local_proxy\")},\n\t\t\tHeaderChange: s.getEscapeString(\"header\"),\n\t\t\tHostChange:   s.getEscapeString(\"hostchange\"),\n\t\t\tRemark:       s.getEscapeString(\"remark\"),\n\t\t\tLocation:     s.getEscapeString(\"location\"),\n\t\t\tFlow:         &file.Flow{},\n\t\t\tScheme:       s.getEscapeString(\"scheme\"),\n\t\t\tKeyFilePath:  s.getEscapeString(\"key_file_path\"),\n\t\t\tCertFilePath: s.getEscapeString(\"cert_file_path\"),\n\t\t}\n\t\tvar err error\n\t\tif h.Client, err = file.GetDb().GetClient(s.GetIntNoErr(\"client_id\")); err != nil {\n\t\t\ts.AjaxErr(\"add error the client can not be found\")\n\t\t}\n\t\tif err := file.GetDb().NewHost(h); err != nil {\n\t\t\ts.AjaxErr(\"add fail\" + err.Error())\n\t\t}\n\t\ts.AjaxOk(\"add success\")\n\t}\n}\n\nfunc (s *IndexController) EditHost() {\n\tid := s.GetIntNoErr(\"id\")\n\tif s.Ctx.Request.Method == \"GET\" {\n\t\ts.Data[\"menu\"] = \"host\"\n\t\tif h, err := file.GetDb().GetHostById(id); err != nil {\n\t\t\ts.error()\n\t\t} else {\n\t\t\ts.Data[\"h\"] = h\n\t\t}\n\t\ts.SetInfo(\"edit\")\n\t\ts.display(\"index/hedit\")\n\t} else {\n\t\tif h, err := file.GetDb().GetHostById(id); err != nil {\n\t\t\ts.error()\n\t\t} else {\n\t\t\tif h.Host != s.getEscapeString(\"host\") {\n\t\t\t\ttmpHost := new(file.Host)\n\t\t\t\ttmpHost.Host = s.getEscapeString(\"host\")\n\t\t\t\ttmpHost.Location = s.getEscapeString(\"location\")\n\t\t\t\ttmpHost.Scheme = s.getEscapeString(\"scheme\")\n\t\t\t\tif file.GetDb().IsHostExist(tmpHost) {\n\t\t\t\t\ts.AjaxErr(\"host has exist\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif client, err := file.GetDb().GetClient(s.GetIntNoErr(\"client_id\")); err != nil {\n\t\t\t\ts.AjaxErr(\"modified error,the client is not exist\")\n\t\t\t} else {\n\t\t\t\th.Client = client\n\t\t\t}\n\t\t\th.Host = s.getEscapeString(\"host\")\n\t\t\th.Target = &file.Target{TargetStr: s.getEscapeString(\"target\")}\n\t\t\th.HeaderChange = s.getEscapeString(\"header\")\n\t\t\th.HostChange = s.getEscapeString(\"hostchange\")\n\t\t\th.Remark = s.getEscapeString(\"remark\")\n\t\t\th.Location = s.getEscapeString(\"location\")\n\t\t\th.Scheme = s.getEscapeString(\"scheme\")\n\t\t\th.KeyFilePath = s.getEscapeString(\"key_file_path\")\n\t\t\th.CertFilePath = s.getEscapeString(\"cert_file_path\")\n\t\t\th.Target.LocalProxy = s.GetBoolNoErr(\"local_proxy\")\n\t\t\tfile.GetDb().JsonDb.StoreHostToJsonFile()\n\t\t}\n\t\ts.AjaxOk(\"modified success\")\n\t}\n}\n"
  },
  {
    "path": "web/controllers/login.go",
    "content": "package controllers\n\nimport (\n\t\"math/rand\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"ehang.io/nps/lib/common\"\n\t\"ehang.io/nps/lib/file\"\n\t\"ehang.io/nps/server\"\n\t\"github.com/astaxie/beego\"\n)\n\ntype LoginController struct {\n\tbeego.Controller\n}\n\nvar ipRecord sync.Map\n\ntype record struct {\n\thasLoginFailTimes int\n\tlastLoginTime     time.Time\n}\n\nfunc (self *LoginController) Index() {\n\t// Try login implicitly, will succeed if it's configured as no-auth(empty username&password).\n\twebBaseUrl := beego.AppConfig.String(\"web_base_url\")\n\tif self.doLogin(\"\", \"\", false) {\n\t\tself.Redirect(webBaseUrl+\"/index/index\", 302)\n\t}\n\tself.Data[\"web_base_url\"] = webBaseUrl\n\tself.Data[\"register_allow\"], _ = beego.AppConfig.Bool(\"allow_user_register\")\n\tself.TplName = \"login/index.html\"\n}\n\nfunc (self *LoginController) Verify() {\n\tusername := self.GetString(\"username\")\n\tpassword := self.GetString(\"password\")\n\tif self.doLogin(username, password, true) {\n\t\tself.Data[\"json\"] = map[string]interface{}{\"status\": 1, \"msg\": \"login success\"}\n\t} else {\n\t\tself.Data[\"json\"] = map[string]interface{}{\"status\": 0, \"msg\": \"username or password incorrect\"}\n\t}\n\tself.ServeJSON()\n}\n\nfunc (self *LoginController) doLogin(username, password string, explicit bool) bool {\n\tclearIprecord()\n\tip, _, _ := net.SplitHostPort(self.Ctx.Request.RemoteAddr)\n\tif v, ok := ipRecord.Load(ip); ok {\n\t\tvv := v.(*record)\n\t\tif (time.Now().Unix() - vv.lastLoginTime.Unix()) >= 60 {\n\t\t\tvv.hasLoginFailTimes = 0\n\t\t}\n\t\tif vv.hasLoginFailTimes >= 10 {\n\t\t\treturn false\n\t\t}\n\t}\n\tvar auth bool\n\tif password == beego.AppConfig.String(\"web_password\") && username == beego.AppConfig.String(\"web_username\") {\n\t\tself.SetSession(\"isAdmin\", true)\n\t\tself.DelSession(\"clientId\")\n\t\tself.DelSession(\"username\")\n\t\tauth = true\n\t\tserver.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Input.IP()), time.Now().Add(time.Hour*time.Duration(2)))\n\t}\n\tb, err := beego.AppConfig.Bool(\"allow_user_login\")\n\tif err == nil && b && !auth {\n\t\tfile.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {\n\t\t\tv := value.(*file.Client)\n\t\t\tif !v.Status || v.NoDisplay {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif v.WebUserName == \"\" && v.WebPassword == \"\" {\n\t\t\t\tif username != \"user\" || v.VerifyKey != password {\n\t\t\t\t\treturn true\n\t\t\t\t} else {\n\t\t\t\t\tauth = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !auth && v.WebPassword == password && v.WebUserName == username {\n\t\t\t\tauth = true\n\t\t\t}\n\t\t\tif auth {\n\t\t\t\tself.SetSession(\"isAdmin\", false)\n\t\t\t\tself.SetSession(\"clientId\", v.Id)\n\t\t\t\tself.SetSession(\"username\", v.WebUserName)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\tif auth {\n\t\tself.SetSession(\"auth\", true)\n\t\tipRecord.Delete(ip)\n\t\treturn true\n\n\t}\n\tif v, load := ipRecord.LoadOrStore(ip, &record{hasLoginFailTimes: 1, lastLoginTime: time.Now()}); load && explicit {\n\t\tvv := v.(*record)\n\t\tvv.lastLoginTime = time.Now()\n\t\tvv.hasLoginFailTimes += 1\n\t\tipRecord.Store(ip, vv)\n\t}\n\treturn false\n}\nfunc (self *LoginController) Register() {\n\tif self.Ctx.Request.Method == \"GET\" {\n\t\tself.Data[\"web_base_url\"] = beego.AppConfig.String(\"web_base_url\")\n\t\tself.TplName = \"login/register.html\"\n\t} else {\n\t\tif b, err := beego.AppConfig.Bool(\"allow_user_register\"); err != nil || !b {\n\t\t\tself.Data[\"json\"] = map[string]interface{}{\"status\": 0, \"msg\": \"register is not allow\"}\n\t\t\tself.ServeJSON()\n\t\t\treturn\n\t\t}\n\t\tif self.GetString(\"username\") == \"\" || self.GetString(\"password\") == \"\" || self.GetString(\"username\") == beego.AppConfig.String(\"web_username\") {\n\t\t\tself.Data[\"json\"] = map[string]interface{}{\"status\": 0, \"msg\": \"please check your input\"}\n\t\t\tself.ServeJSON()\n\t\t\treturn\n\t\t}\n\t\tt := &file.Client{\n\t\t\tId:          int(file.GetDb().JsonDb.GetClientId()),\n\t\t\tStatus:      true,\n\t\t\tCnf:         &file.Config{},\n\t\t\tWebUserName: self.GetString(\"username\"),\n\t\t\tWebPassword: self.GetString(\"password\"),\n\t\t\tFlow:        &file.Flow{},\n\t\t}\n\t\tif err := file.GetDb().NewClient(t); err != nil {\n\t\t\tself.Data[\"json\"] = map[string]interface{}{\"status\": 0, \"msg\": err.Error()}\n\t\t} else {\n\t\t\tself.Data[\"json\"] = map[string]interface{}{\"status\": 1, \"msg\": \"register success\"}\n\t\t}\n\t\tself.ServeJSON()\n\t}\n}\n\nfunc (self *LoginController) Out() {\n\tself.SetSession(\"auth\", false)\n\tself.Redirect(beego.AppConfig.String(\"web_base_url\")+\"/login/index\", 302)\n}\n\nfunc clearIprecord() {\n\trand.Seed(time.Now().UnixNano())\n\tx := rand.Intn(100)\n\tif x == 1 {\n\t\tipRecord.Range(func(key, value interface{}) bool {\n\t\t\tv := value.(*record)\n\t\t\tif time.Now().Unix()-v.lastLoginTime.Unix() >= 60 {\n\t\t\t\tipRecord.Delete(key)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "web/routers/router.go",
    "content": "package routers\n\nimport (\n\t\"ehang.io/nps/web/controllers\"\n\t\"github.com/astaxie/beego\"\n)\n\nfunc Init() {\n\tweb_base_url := beego.AppConfig.String(\"web_base_url\")\n\tif len(web_base_url) > 0 {\n\t\tns := beego.NewNamespace(web_base_url,\n\t\t\tbeego.NSRouter(\"/\", &controllers.IndexController{}, \"*:Index\"),\n\t\t\tbeego.NSAutoRouter(&controllers.IndexController{}),\n\t\t\tbeego.NSAutoRouter(&controllers.LoginController{}),\n\t\t\tbeego.NSAutoRouter(&controllers.ClientController{}),\n\t\t\tbeego.NSAutoRouter(&controllers.AuthController{}),\n\t\t)\n\t\tbeego.AddNamespace(ns)\n\t} else {\n\t\tbeego.Router(\"/\", &controllers.IndexController{}, \"*:Index\")\n\t\tbeego.AutoRouter(&controllers.IndexController{})\n\t\tbeego.AutoRouter(&controllers.LoginController{})\n\t\tbeego.AutoRouter(&controllers.ClientController{})\n\t\tbeego.AutoRouter(&controllers.AuthController{})\n\t}\n}\n"
  },
  {
    "path": "web/static/css/datatables.css",
    "content": "/*\n * This combined file was created by the DataTables downloader builder:\n *   https://datatables.net/download\n *\n * To rebuild or modify this file with the latest versions of the included\n * software please visit:\n *   https://datatables.net/download/#dt/dt-1.10.20\n *\n * Included libraries:\n *   DataTables 1.10.20\n */\n\n/*\n * Table styles\n */\ntable.dataTable {\n  width: 100%;\n  margin: 0 auto;\n  clear: both;\n  border-collapse: separate;\n  border-spacing: 0;\n  /*\n   * Header and footer styles\n   */\n  /*\n   * Body styles\n   */\n}\ntable.dataTable thead th,\ntable.dataTable tfoot th {\n  font-weight: bold;\n}\ntable.dataTable thead th,\ntable.dataTable thead td {\n  padding: 10px 18px;\n  border-bottom: 1px solid #111;\n}\ntable.dataTable thead th:active,\ntable.dataTable thead td:active {\n  outline: none;\n}\ntable.dataTable tfoot th,\ntable.dataTable tfoot td {\n  padding: 10px 18px 6px 18px;\n  border-top: 1px solid #111;\n}\ntable.dataTable thead .sorting,\ntable.dataTable thead .sorting_asc,\ntable.dataTable thead .sorting_desc,\ntable.dataTable thead .sorting_asc_disabled,\ntable.dataTable thead .sorting_desc_disabled {\n  cursor: pointer;\n  *cursor: hand;\n  background-repeat: no-repeat;\n  background-position: center right;\n}\ntable.dataTable thead .sorting {\n  background-image: url(\"DataTables-1.10.20/images/sort_both.png\");\n}\ntable.dataTable thead .sorting_asc {\n  background-image: url(\"DataTables-1.10.20/images/sort_asc.png\");\n}\ntable.dataTable thead .sorting_desc {\n  background-image: url(\"DataTables-1.10.20/images/sort_desc.png\");\n}\ntable.dataTable thead .sorting_asc_disabled {\n  background-image: url(\"DataTables-1.10.20/images/sort_asc_disabled.png\");\n}\ntable.dataTable thead .sorting_desc_disabled {\n  background-image: url(\"DataTables-1.10.20/images/sort_desc_disabled.png\");\n}\ntable.dataTable tbody tr {\n  background-color: #ffffff;\n}\ntable.dataTable tbody tr.selected {\n  background-color: #B0BED9;\n}\ntable.dataTable tbody th,\ntable.dataTable tbody td {\n  padding: 8px 10px;\n}\ntable.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {\n  border-top: 1px solid #ddd;\n}\ntable.dataTable.row-border tbody tr:first-child th,\ntable.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,\ntable.dataTable.display tbody tr:first-child td {\n  border-top: none;\n}\ntable.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {\n  border-top: 1px solid #ddd;\n  border-right: 1px solid #ddd;\n}\ntable.dataTable.cell-border tbody tr th:first-child,\ntable.dataTable.cell-border tbody tr td:first-child {\n  border-left: 1px solid #ddd;\n}\ntable.dataTable.cell-border tbody tr:first-child th,\ntable.dataTable.cell-border tbody tr:first-child td {\n  border-top: none;\n}\ntable.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {\n  background-color: #f9f9f9;\n}\ntable.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {\n  background-color: #acbad4;\n}\ntable.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {\n  background-color: #f6f6f6;\n}\ntable.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {\n  background-color: #aab7d1;\n}\ntable.dataTable.order-column tbody tr > .sorting_1,\ntable.dataTable.order-column tbody tr > .sorting_2,\ntable.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,\ntable.dataTable.display tbody tr > .sorting_2,\ntable.dataTable.display tbody tr > .sorting_3 {\n  background-color: #fafafa;\n}\ntable.dataTable.order-column tbody tr.selected > .sorting_1,\ntable.dataTable.order-column tbody tr.selected > .sorting_2,\ntable.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,\ntable.dataTable.display tbody tr.selected > .sorting_2,\ntable.dataTable.display tbody tr.selected > .sorting_3 {\n  background-color: #acbad5;\n}\ntable.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {\n  background-color: #f1f1f1;\n}\ntable.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {\n  background-color: #f3f3f3;\n}\ntable.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {\n  background-color: whitesmoke;\n}\ntable.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {\n  background-color: #a6b4cd;\n}\ntable.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {\n  background-color: #a8b5cf;\n}\ntable.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {\n  background-color: #a9b7d1;\n}\ntable.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {\n  background-color: #fafafa;\n}\ntable.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {\n  background-color: #fcfcfc;\n}\ntable.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {\n  background-color: #fefefe;\n}\ntable.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {\n  background-color: #acbad5;\n}\ntable.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {\n  background-color: #aebcd6;\n}\ntable.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {\n  background-color: #afbdd8;\n}\ntable.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {\n  background-color: #eaeaea;\n}\ntable.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {\n  background-color: #ececec;\n}\ntable.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {\n  background-color: #efefef;\n}\ntable.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {\n  background-color: #a2aec7;\n}\ntable.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {\n  background-color: #a3b0c9;\n}\ntable.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {\n  background-color: #a5b2cb;\n}\ntable.dataTable.no-footer {\n  border-bottom: 1px solid #111;\n}\ntable.dataTable.nowrap th, table.dataTable.nowrap td {\n  white-space: nowrap;\n}\ntable.dataTable.compact thead th,\ntable.dataTable.compact thead td {\n  padding: 4px 17px 4px 4px;\n}\ntable.dataTable.compact tfoot th,\ntable.dataTable.compact tfoot td {\n  padding: 4px;\n}\ntable.dataTable.compact tbody th,\ntable.dataTable.compact tbody td {\n  padding: 4px;\n}\ntable.dataTable th.dt-left,\ntable.dataTable td.dt-left {\n  text-align: left;\n}\ntable.dataTable th.dt-center,\ntable.dataTable td.dt-center,\ntable.dataTable td.dataTables_empty {\n  text-align: center;\n}\ntable.dataTable th.dt-right,\ntable.dataTable td.dt-right {\n  text-align: right;\n}\ntable.dataTable th.dt-justify,\ntable.dataTable td.dt-justify {\n  text-align: justify;\n}\ntable.dataTable th.dt-nowrap,\ntable.dataTable td.dt-nowrap {\n  white-space: nowrap;\n}\ntable.dataTable thead th.dt-head-left,\ntable.dataTable thead td.dt-head-left,\ntable.dataTable tfoot th.dt-head-left,\ntable.dataTable tfoot td.dt-head-left {\n  text-align: left;\n}\ntable.dataTable thead th.dt-head-center,\ntable.dataTable thead td.dt-head-center,\ntable.dataTable tfoot th.dt-head-center,\ntable.dataTable tfoot td.dt-head-center {\n  text-align: center;\n}\ntable.dataTable thead th.dt-head-right,\ntable.dataTable thead td.dt-head-right,\ntable.dataTable tfoot th.dt-head-right,\ntable.dataTable tfoot td.dt-head-right {\n  text-align: right;\n}\ntable.dataTable thead th.dt-head-justify,\ntable.dataTable thead td.dt-head-justify,\ntable.dataTable tfoot th.dt-head-justify,\ntable.dataTable tfoot td.dt-head-justify {\n  text-align: justify;\n}\ntable.dataTable thead th.dt-head-nowrap,\ntable.dataTable thead td.dt-head-nowrap,\ntable.dataTable tfoot th.dt-head-nowrap,\ntable.dataTable tfoot td.dt-head-nowrap {\n  white-space: nowrap;\n}\ntable.dataTable tbody th.dt-body-left,\ntable.dataTable tbody td.dt-body-left {\n  text-align: left;\n}\ntable.dataTable tbody th.dt-body-center,\ntable.dataTable tbody td.dt-body-center {\n  text-align: center;\n}\ntable.dataTable tbody th.dt-body-right,\ntable.dataTable tbody td.dt-body-right {\n  text-align: right;\n}\ntable.dataTable tbody th.dt-body-justify,\ntable.dataTable tbody td.dt-body-justify {\n  text-align: justify;\n}\ntable.dataTable tbody th.dt-body-nowrap,\ntable.dataTable tbody td.dt-body-nowrap {\n  white-space: nowrap;\n}\n\ntable.dataTable,\ntable.dataTable th,\ntable.dataTable td {\n  box-sizing: content-box;\n}\n\n/*\n * Control feature layout\n */\n.dataTables_wrapper {\n  position: relative;\n  clear: both;\n  *zoom: 1;\n  zoom: 1;\n}\n.dataTables_wrapper .dataTables_length {\n  float: left;\n}\n.dataTables_wrapper .dataTables_filter {\n  float: right;\n  text-align: right;\n}\n.dataTables_wrapper .dataTables_filter input {\n  margin-left: 0.5em;\n}\n.dataTables_wrapper .dataTables_info {\n  clear: both;\n  float: left;\n  padding-top: 0.755em;\n}\n.dataTables_wrapper .dataTables_paginate {\n  float: right;\n  text-align: right;\n  padding-top: 0.25em;\n}\n.dataTables_wrapper .dataTables_paginate .paginate_button {\n  box-sizing: border-box;\n  display: inline-block;\n  min-width: 1.5em;\n  padding: 0.5em 1em;\n  margin-left: 2px;\n  text-align: center;\n  text-decoration: none !important;\n  cursor: pointer;\n  *cursor: hand;\n  color: #333 !important;\n  border: 1px solid transparent;\n  border-radius: 2px;\n}\n.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {\n  color: #333 !important;\n  border: 1px solid #979797;\n  background-color: white;\n  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));\n  /* Chrome,Safari4+ */\n  background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);\n  /* Chrome10+,Safari5.1+ */\n  background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);\n  /* FF3.6+ */\n  background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);\n  /* IE10+ */\n  background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);\n  /* Opera 11.10+ */\n  background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);\n  /* W3C */\n}\n.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {\n  cursor: default;\n  color: #666 !important;\n  border: 1px solid transparent;\n  background: transparent;\n  box-shadow: none;\n}\n.dataTables_wrapper .dataTables_paginate .paginate_button:hover {\n  color: white !important;\n  border: 1px solid #111;\n  background-color: #585858;\n  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));\n  /* Chrome,Safari4+ */\n  background: -webkit-linear-gradient(top, #585858 0%, #111 100%);\n  /* Chrome10+,Safari5.1+ */\n  background: -moz-linear-gradient(top, #585858 0%, #111 100%);\n  /* FF3.6+ */\n  background: -ms-linear-gradient(top, #585858 0%, #111 100%);\n  /* IE10+ */\n  background: -o-linear-gradient(top, #585858 0%, #111 100%);\n  /* Opera 11.10+ */\n  background: linear-gradient(to bottom, #585858 0%, #111 100%);\n  /* W3C */\n}\n.dataTables_wrapper .dataTables_paginate .paginate_button:active {\n  outline: none;\n  background-color: #2b2b2b;\n  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));\n  /* Chrome,Safari4+ */\n  background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);\n  /* Chrome10+,Safari5.1+ */\n  background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);\n  /* FF3.6+ */\n  background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);\n  /* IE10+ */\n  background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);\n  /* Opera 11.10+ */\n  background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);\n  /* W3C */\n  box-shadow: inset 0 0 3px #111;\n}\n.dataTables_wrapper .dataTables_paginate .ellipsis {\n  padding: 0 1em;\n}\n.dataTables_wrapper .dataTables_processing {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: 100%;\n  height: 40px;\n  margin-left: -50%;\n  margin-top: -25px;\n  padding-top: 20px;\n  text-align: center;\n  font-size: 1.2em;\n  background-color: white;\n  background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));\n  background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);\n  background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);\n  background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);\n  background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);\n  background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);\n}\n.dataTables_wrapper .dataTables_length,\n.dataTables_wrapper .dataTables_filter,\n.dataTables_wrapper .dataTables_info,\n.dataTables_wrapper .dataTables_processing,\n.dataTables_wrapper .dataTables_paginate {\n  color: #333;\n}\n.dataTables_wrapper .dataTables_scroll {\n  clear: both;\n}\n.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {\n  *margin-top: -1px;\n  -webkit-overflow-scrolling: touch;\n}\n.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {\n  vertical-align: middle;\n}\n.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,\n.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,\n.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {\n  height: 0;\n  overflow: hidden;\n  margin: 0 !important;\n  padding: 0 !important;\n}\n.dataTables_wrapper.no-footer .dataTables_scrollBody {\n  border-bottom: 1px solid #111;\n}\n.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,\n.dataTables_wrapper.no-footer div.dataTables_scrollBody > table {\n  border-bottom: none;\n}\n.dataTables_wrapper:after {\n  visibility: hidden;\n  display: block;\n  content: \"\";\n  clear: both;\n  height: 0;\n}\n\n@media screen and (max-width: 767px) {\n  .dataTables_wrapper .dataTables_info,\n  .dataTables_wrapper .dataTables_paginate {\n    float: none;\n    text-align: center;\n  }\n  .dataTables_wrapper .dataTables_paginate {\n    margin-top: 0.5em;\n  }\n}\n@media screen and (max-width: 640px) {\n  .dataTables_wrapper .dataTables_length,\n  .dataTables_wrapper .dataTables_filter {\n    float: none;\n    text-align: center;\n  }\n  .dataTables_wrapper .dataTables_filter {\n    margin-top: 0.5em;\n  }\n}\n\n\n"
  },
  {
    "path": "web/static/css/style.css",
    "content": "@import url(\"https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700\");\n@import url(\"https://fonts.googleapis.com/css?family=Roboto:400,300,500,700\");\n/*\n *\n *   INSPINIA - Responsive Admin Theme\n *   version 2.9.3\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') 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  -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}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-weight: 100;\n}\n.h1,\n.h2,\n.h3,\nh1,\nh2,\nh3 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n}\nh1 {\n  font-size: 30px;\n}\nh2 {\n  font-size: 24px;\n}\nh3 {\n  font-size: 16px;\n}\nh4 {\n  font-size: 14px;\n}\nh5 {\n  font-size: 12px;\n}\nh6 {\n  font-size: 10px;\n}\nh3,\nh4,\nh5 {\n  margin-top: 5px;\n  font-weight: 600;\n}\n.nav > li > a {\n  color: #a7b1c2;\n  font-weight: 600;\n  padding: 14px 20px 14px 25px;\n  display: block;\n}\n.nav.metismenu > li {\n  display: block;\n  width: 100%;\n  position: relative;\n}\n.nav.metismenu .dropdown-menu > li > a {\n  padding: 3px 20px;\n  display: block;\n}\n.nav.navbar-right > li > a {\n  color: #999c9e;\n}\n.nav > li.active > a {\n  color: #ffffff;\n}\n.navbar-default .nav > li > a:hover,\n.navbar-default .nav > li > a:focus {\n  background-color: #293846;\n  color: white;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n  background: #fff;\n}\n.nav.navbar-top-links > li > a:hover,\n.nav.navbar-top-links > li > a:focus {\n  background-color: transparent;\n}\n.nav > li > a i {\n  margin-right: 6px;\n}\n.navbar {\n  border: 0;\n}\n.navbar-default {\n  background-color: transparent;\n  border-color: #2f4050;\n}\n.navbar-top-links li {\n  display: inline-block;\n  align-self: center;\n}\n.body-small .navbar-top-links li:last-child {\n  margin-right: 0;\n}\n.navbar-top-links li a {\n  padding: 20px 10px;\n  min-height: 50px;\n}\n.dropdown-menu {\n  border: medium none;\n  border-radius: 3px;\n  box-shadow: 0 0 3px rgba(86, 96, 117, 0.7);\n  display: none;\n  float: left;\n  font-size: 12px;\n  left: 0;\n  list-style: none outside none;\n  padding: 0;\n  position: absolute;\n  text-shadow: none;\n  top: 100%;\n  z-index: 1000;\n}\n.dropdown-menu > li > a {\n  border-radius: 3px;\n  color: inherit;\n  line-height: 25px;\n  margin: 4px;\n  text-align: left;\n  font-weight: normal;\n  display: block;\n  padding: 3px 20px;\n}\n.dropdown-menu > li > a:focus,\n.dropdown-menu > li > a:hover {\n  color: #262626;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:focus,\n.dropdown-menu > .active > a:hover {\n  color: #fff;\n  text-decoration: none;\n  background-color: #1ab394;\n  outline: 0;\n}\n.dropdown-menu > li > a.font-bold {\n  font-weight: 600;\n}\n.navbar-top-links .dropdown-menu li {\n  display: block;\n}\n.navbar-top-links .dropdown-menu li:last-child {\n  margin-right: 0;\n}\n.navbar-top-links .dropdown-menu li a {\n  padding: 3px 20px;\n  min-height: 0;\n}\n.navbar-top-links .dropdown-menu li a div {\n  white-space: normal;\n}\n.navbar-top-links .dropdown-messages,\n.navbar-top-links .dropdown-tasks,\n.navbar-top-links .dropdown-alerts {\n  width: 310px;\n  min-width: 0;\n}\n.navbar-top-links .dropdown-messages {\n  margin-left: 5px;\n}\n.navbar-top-links .dropdown-tasks {\n  margin-left: -59px;\n}\n.navbar-top-links .dropdown-alerts {\n  margin-left: -123px;\n}\n.navbar-top-links .dropdown-user {\n  right: 0;\n  left: auto;\n}\n.dropdown-messages,\n.dropdown-alerts {\n  padding: 10px 10px 10px 10px;\n}\n.dropdown-messages li a,\n.dropdown-alerts li a {\n  font-size: 12px;\n}\n.dropdown-messages li em,\n.dropdown-alerts li em {\n  font-size: 10px;\n}\n.nav.navbar-top-links .dropdown-alerts a {\n  font-size: 12px;\n}\n.nav-header {\n  padding: 33px 25px;\n  background-color: #2f4050;\n//  background-image: url(\"patterns/header-profile.png\");\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-right: 4px solid transparent;\n  border-left: 4px solid transparent;\n}*/\n.profile-element .dropdown-toggle::after {\n  display: none;\n}\n.pace-done .nav-header {\n  transition: all 0.4s;\n}\nul.nav-second-level {\n  background: #293846;\n}\n.nav > li.active {\n  border-left: 4px solid #19aa8d;\n  background: #293846;\n}\n.nav.nav-second-level > li.active {\n  border: none;\n}\n.nav.nav-second-level.collapse[style] {\n  height: auto !important;\n}\n.nav-header a {\n  color: #DFE4ED;\n}\n.nav-header .text-muted {\n  color: #8095a8 !important;\n}\n.minimalize-styl-2 {\n  padding: 4px 12px;\n  margin: 14px 5px 5px 20px;\n  font-size: 14px;\n  float: left;\n}\n.navbar-form-custom {\n  float: left;\n  height: 50px;\n  padding: 0;\n  width: 200px;\n  display: block;\n}\n.navbar-form-custom .form-group {\n  margin-bottom: 0;\n}\n.nav.navbar-top-links a {\n  font-size: 14px;\n}\n.navbar-form-custom .form-control {\n  background: none repeat scroll 0 0 rgba(0, 0, 0, 0);\n  border: medium none;\n  font-size: 14px;\n  height: 60px;\n  margin: 0;\n  z-index: 2000;\n}\n/*.nav.navbar-top-links .dropdown-toggle::after {\n  display: none;\n}\n*/\n.navbar.navbar-static-top {\n  padding: 0;\n  width: 100%;\n  align-items: inherit;\n}\n.navbar-static-top .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n.count-info .label {\n  line-height: 12px;\n  padding: 2px 5px;\n  position: absolute;\n  right: 6px;\n  top: 12px;\n}\n.arrow {\n  float: right;\n}\n.fa.arrow:before {\n  content: \"\\f104\";\n}\n.active > a > .fa.arrow:before {\n  content: \"\\f107\";\n}\n.nav-second-level li,\n.nav-third-level li {\n  border-bottom: none !important;\n}\n.nav.nav-third-level > li.active {\n  border: none;\n}\n.nav-second-level li a {\n  padding: 7px 10px 7px 10px;\n  padding-left: 52px;\n}\n.fixed-sidebar.mini-navbar .nav-second-level.collapsing li a,\n.nav-second-level.collapsing li a {\n  min-width: 220px;\n}\n.body-small .nav-second-level.collapsing li a,\n.mini-navbar .nav-second-level.collapsing li a {\n  min-width: 140px;\n}\n.nav-third-level li a,\n.fixed-sidebar.mini-navbar .nav-second-level li .nav-third-level li a {\n  padding-left: 62px;\n}\n.nav-second-level li:last-child {\n  padding-bottom: 10px;\n}\nbody:not(.fixed-sidebar):not(.canvas-menu).mini-navbar .nav li:hover > .nav-second-level,\n.mini-navbar .nav li:focus > .nav-second-level {\n  display: block;\n  border-radius: 0 2px 2px 0;\n  min-width: 160px;\n  height: auto;\n}\nbody.mini-navbar .navbar-default .nav > li > .nav-second-level li a {\n  font-size: 12px;\n  border-radius: 3px;\n}\n.fixed-nav .slimScrollDiv #side-menu {\n  padding-bottom: 60px;\n}\n.mini-navbar .nav-second-level li a {\n  padding: 10px 10px 10px 15px;\n}\n.mini-navbar .nav .nav-second-level {\n  position: absolute;\n  left: 70px;\n  top: 0;\n  background-color: #2f4050;\n  padding: 10px 10px 10px 10px;\n  font-size: 12px;\n}\n.canvas-menu.mini-navbar .nav-second-level {\n  background: #293846;\n}\n.mini-navbar li.active .nav-second-level {\n  left: 65px;\n}\n.navbar-default .special_link a {\n  background: #1ab394;\n  color: white;\n}\n.navbar-default .special_link a:hover {\n  background: #17987e !important;\n  color: white;\n}\n.navbar-default .special_link a span.label {\n  background: #fff;\n  color: #1ab394;\n}\n.navbar-default .landing_link a {\n  background: #1cc09f;\n  color: white;\n}\n.navbar-default .landing_link a:hover {\n  background: #1ab394 !important;\n  color: white;\n}\n.navbar-default .landing_link a span.label {\n  background: #fff;\n  color: #1cc09f;\n}\n.logo-element {\n  text-align: center;\n  font-size: 18px;\n  font-weight: 600;\n  color: white;\n  display: none;\n  padding: 18px 0;\n}\n.navbar-static-side {\n  transition: width 0s;\n}\n.footer {\n  transition: margin 0s;\n}\n.pace-done .navbar-static-side,\n.pace-done .nav-header,\n.pace-done li.active,\n.pace-done #page-wrapper,\n.pace-done .footer {\n  -webkit-transition: all 0.4s;\n  -moz-transition: all 0.4s;\n  -o-transition: all 0.4s;\n  transition: all 0.4s;\n}\n.navbar-fixed-top {\n  background: #fff;\n  transition-duration: 0.4s;\n  border-bottom: 1px solid #e7eaec !important;\n  z-index: 2030;\n  position: fixed;\n  right: 0;\n  left: 0;\n  padding: 0;\n  top: 0;\n}\n.navbar-fixed-top .navbar-form-custom .form-control {\n  height: 50px;\n}\n.navbar-fixed-top,\n.navbar-static-top {\n  background: #f3f3f4;\n}\n.fixed-nav #wrapper {\n  margin-top: 0;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n  -moz-border-bottom-colors: none;\n  -moz-border-left-colors: none;\n  -moz-border-right-colors: none;\n  -moz-border-top-colors: none;\n  background: none;\n  border-color: #dddddd #dddddd rgba(0, 0, 0, 0);\n  border-bottom: #f3f3f4;\n  border-image: none;\n  border-style: solid;\n  border-width: 1px;\n  color: #555555;\n  cursor: default;\n}\n.nav.nav-tabs li {\n  background: none;\n  border: none;\n}\nbody.fixed-nav #wrapper .navbar-static-side,\nbody.fixed-nav #wrapper #page-wrapper {\n  margin-top: 60px;\n}\nbody.top-navigation.fixed-nav #wrapper #page-wrapper {\n  margin-top: 0;\n}\nbody.fixed-nav.fixed-nav-basic .navbar-fixed-top {\n  left: 220px;\n}\nbody.fixed-nav.fixed-nav-basic.mini-navbar .navbar-fixed-top {\n  left: 70px;\n}\nbody.fixed-nav.fixed-nav-basic.fixed-sidebar.mini-navbar .navbar-fixed-top {\n  left: 0;\n}\nbody.fixed-nav.fixed-nav-basic #wrapper .navbar-static-side {\n  margin-top: 0;\n}\nbody.fixed-nav.fixed-nav-basic.body-small .navbar-fixed-top {\n  left: 0;\n}\nbody.fixed-nav.fixed-nav-basic.fixed-sidebar.mini-navbar.body-small .navbar-fixed-top {\n  left: 220px;\n}\n.fixed-nav .minimalize-styl-2 {\n  margin: 10px 5px 5px 15px;\n}\n.body-small .navbar-fixed-top {\n  margin-left: 0;\n}\nbody.mini-navbar .navbar-static-side {\n  width: 70px;\n}\nbody.mini-navbar .profile-element,\nbody.mini-navbar .nav-label,\nbody.mini-navbar .navbar-default .nav li a span {\n  display: none;\n}\nbody.canvas-menu .profile-element {\n  display: block;\n}\nbody:not(.fixed-sidebar):not(.canvas-menu).mini-navbar .nav-second-level {\n  display: none;\n}\nbody.mini-navbar .navbar-default .nav > li > a {\n  font-size: 16px;\n}\nbody.mini-navbar .logo-element {\n  display: block;\n}\nbody.canvas-menu .logo-element {\n  display: none;\n}\nbody.mini-navbar .nav-header {\n  padding: 0;\n  background-color: #1ab394;\n}\nbody.canvas-menu .nav-header {\n  padding: 33px 25px;\n}\nbody.canvas-menu .sidebar-collapse li {\n  width: 100%;\n}\nbody.mini-navbar #page-wrapper {\n  width: calc(100% - 70px);\n}\nbody.canvas-menu.mini-navbar #page-wrapper,\nbody.canvas-menu.mini-navbar .footer {\n  margin: 0;\n  width: 100%;\n}\nbody.fixed-sidebar .navbar-static-side,\nbody.canvas-menu .navbar-static-side {\n  width: 220px;\n  z-index: 2001;\n  height: 100vh;\n  position: fixed;\n}\nbody.fixed-sidebar.mini-navbar .navbar-static-side {\n  width: 0;\n}\nbody.fixed-sidebar #page-wrapper {\n  margin: 0 0 0 220px;\n}\nbody.fixed-sidebar.body-small #page-wrapper {\n  margin: 0;\n}\nbody.fixed-sidebar.mini-navbar #page-wrapper {\n  margin: 0 0 0 0;\n  width: 100%;\n}\nbody.body-small.fixed-sidebar.mini-navbar #page-wrapper {\n  margin: 0 0 0 220px;\n}\nbody.body-small.fixed-sidebar.mini-navbar .navbar-static-side {\n  width: 220px;\n}\n.fixed-sidebar.mini-navbar .nav li:focus > .nav-second-level,\n.canvas-menu.mini-navbar .nav li:focus > .nav-second-level {\n  display: block;\n  height: auto;\n}\nbody.fixed-sidebar.mini-navbar .navbar-default .nav > li > .nav-second-level li a {\n  font-size: 12px;\n  border-radius: 3px;\n}\nbody.canvas-menu.mini-navbar .navbar-default .nav > li > .nav-second-level li a {\n  font-size: 13px;\n  border-radius: 3px;\n}\n.fixed-sidebar.mini-navbar .nav-second-level li a,\n.canvas-menu.mini-navbar .nav-second-level li a {\n  padding: 10px 10px 10px 15px;\n}\n.fixed-sidebar.mini-navbar .nav-second-level,\n.canvas-menu.mini-navbar .nav-second-level {\n  position: relative;\n  padding: 0;\n  font-size: 13px;\n}\n.fixed-sidebar.mini-navbar li.active .nav-second-level,\n.canvas-menu.mini-navbar li.active .nav-second-level {\n  left: 0;\n}\nbody.fixed-sidebar.mini-navbar .navbar-default .nav > li > a,\nbody.canvas-menu.mini-navbar .navbar-default .nav > li > a {\n  font-size: 13px;\n}\nbody.fixed-sidebar.mini-navbar .nav-label,\nbody.fixed-sidebar.mini-navbar .navbar-default .nav li a span,\nbody.canvas-menu.mini-navbar .nav-label,\nbody.canvas-menu.mini-navbar .navbar-default .nav li a span {\n  display: inline;\n}\nbody.canvas-menu.mini-navbar .navbar-default .nav li .profile-element a span {\n  display: block;\n}\n.canvas-menu.mini-navbar .nav-second-level li a,\n.fixed-sidebar.mini-navbar .nav-second-level li a {\n  padding: 7px 10px 7px 52px;\n}\n.fixed-sidebar.mini-navbar .nav-second-level,\n.canvas-menu.mini-navbar .nav-second-level {\n  left: 0;\n}\nbody.canvas-menu nav.navbar-static-side {\n  z-index: 2001;\n  background: #2f4050;\n  height: 100%;\n  position: fixed;\n  display: none;\n}\nbody.canvas-menu.mini-navbar nav.navbar-static-side {\n  display: block;\n  width: 220px;\n}\n.top-navigation #page-wrapper {\n  width: 100%;\n}\n.top-navigation .navbar-nav .dropdown-menu > .active > a {\n  background: white;\n  color: #1ab394;\n  font-weight: bold;\n}\n.white-bg .navbar-fixed-top,\n.white-bg .navbar-static-top {\n  background: #fff;\n}\n.top-navigation .navbar {\n  margin-bottom: 0;\n}\n.top-navigation .nav > li > a {\n  padding: 15px 20px;\n  color: #676a6c;\n}\n.top-navigation .nav > li a:hover,\n.top-navigation .nav > li a:focus {\n  background: #fff;\n  color: #1ab394;\n}\n.top-navigation .navbar .nav > li.active {\n  background: #fff;\n  border: none;\n}\n.top-navigation .nav > li.active > a {\n  color: #1ab394;\n}\n.top-navigation .navbar-right {\n  margin-right: 10px;\n}\n.top-navigation .navbar-nav .dropdown-menu {\n  box-shadow: none;\n  border: 1px solid #e7eaec;\n}\n.top-navigation .dropdown-menu > li > a {\n  margin: 0;\n  padding: 7px 20px;\n}\n.navbar .dropdown-menu {\n  margin-top: 0;\n}\n.top-navigation .navbar-brand {\n  background: #1ab394;\n  color: #fff;\n  padding: 15px 25px;\n  font-size: 18px;\n  line-height: 20px;\n}\n.top-navigation .navbar-top-links li:last-child {\n  margin-right: 0;\n}\n.top-navigation.mini-navbar #page-wrapper,\n.top-navigation.body-small.fixed-sidebar.mini-navbar #page-wrapper,\n.mini-navbar .top-navigation #page-wrapper,\n.body-small.fixed-sidebar.mini-navbar .top-navigation #page-wrapper,\n.canvas-menu #page-wrapper {\n  margin: 0;\n  width: 100%;\n}\n.top-navigation.fixed-nav #wrapper,\n.fixed-nav #wrapper.top-navigation {\n  margin-top: 50px;\n}\n.top-navigation .footer.fixed {\n  margin-left: 0 !important;\n}\n.top-navigation .wrapper.wrapper-content {\n  padding: 40px;\n}\n.top-navigation.body-small .wrapper.wrapper-content,\n.body-small .top-navigation .wrapper.wrapper-content {\n  padding: 40px 0 40px 0;\n}\n.navbar-toggler {\n  background-color: #1ab394;\n  color: #fff;\n  padding: 6px 12px;\n  font-size: 14px;\n  margin: 8px;\n}\n.top-navigation .navbar-nav .open .dropdown-menu > li > a,\n.top-navigation .navbar-nav .open .dropdown-menu .dropdown-header {\n  padding: 10px 15px 10px 20px;\n}\n@media (max-width: 768px) {\n  .top-navigation .navbar-header {\n    display: block;\n    float: none;\n  }\n}\n.menu-visible-lg,\n.menu-visible-md {\n  display: none !important;\n}\n@media (min-width: 1200px) {\n  .menu-visible-lg {\n    display: block !important;\n  }\n}\n@media (min-width: 992px) {\n  .menu-visible-md {\n    display: block !important;\n  }\n}\n@media (max-width: 767px) {\n  .menu-visible-md {\n    display: block !important;\n  }\n  .menu-visible-lg {\n    display: block !important;\n  }\n}\nbutton:focus {\n  outline: 0 !important;\n}\n.btn {\n  border-radius: 3px;\n  font-size: inherit;\n}\n.btn:focus {\n  box-shadow: none;\n}\n.btn-xs {\n  font-size: 0.7rem;\n  padding: 0.2rem 0.4rem;\n}\n.btn-group-sm > .btn,\n.btn-sm {\n  font-size: .8rem;\n}\n.float-e-margins .btn {\n  margin-bottom: 5px;\n}\n.btn-w-m {\n  min-width: 120px;\n}\n.btn-primary.btn-outline {\n  color: #1ab394;\n}\n.btn-success.btn-outline {\n  color: #1c84c6;\n}\n.btn-info.btn-outline {\n  color: #23c6c8;\n}\n.btn-warning.btn-outline {\n  color: #f8ac59;\n}\n.btn-danger.btn-outline {\n  color: #ed5565;\n}\n.btn-primary.btn-outline:hover,\n.btn-success.btn-outline:hover,\n.btn-info.btn-outline:hover,\n.btn-warning.btn-outline:hover,\n.btn-danger.btn-outline:hover {\n  color: #fff;\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, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-primary {\n  color: #fff;\n  background-color: #1ab394;\n  border-color: #1ab394;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus {\n  background-color: #18a689;\n  border-color: #18a689;\n  color: #FFFFFF;\n}\n.btn-primary.disabled,\n.btn-primary:disabled {\n  color: #fff;\n  background-color: #18a689;\n  border-color: #18a689;\n}\n.btn-primary:not(:disabled):not(.disabled):active,\n.btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n  color: #fff;\n  background-color: #18a689;\n  border-color: #18a689;\n}\n.btn-primary:not(:disabled):not(.disabled):active:focus,\n.btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-success {\n  color: #fff;\n  background-color: #1c84c6;\n  border-color: #1c84c6;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus {\n  color: #fff;\n  background-color: #1a7bb9;\n  border-color: #1a7bb9;\n}\n.btn-success.disabled,\n.btn-success:disabled {\n  color: #fff;\n  background-color: #1a7bb9;\n  border-color: #1a7bb9;\n}\n.btn-success:not(:disabled):not(.disabled):active,\n.btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n  color: #fff;\n  background-color: #1a7bb9;\n  border-color: #1a7bb9;\n}\n.btn-success:not(:disabled):not(.disabled):active:focus,\n.btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-info {\n  color: #fff;\n  background-color: #23c6c8;\n  border-color: #23c6c8;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus {\n  color: #fff;\n  background-color: #21b9bb;\n  border-color: #21b9bb;\n}\n.btn-info.disabled,\n.btn-info:disabled {\n  color: #fff;\n  background-color: #21b9bb;\n  border-color: #21b9bb;\n}\n.btn-info:not(:disabled):not(.disabled):active,\n.btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n  color: #fff;\n  background-color: #21b9bb;\n  border-color: #21b9bb;\n}\n.btn-info:not(:disabled):not(.disabled):active:focus,\n.btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default {\n  color: inherit;\n  background: white;\n  border: 1px solid #e7eaec;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default:active,\n.btn-default.active,\n.open .dropdown-toggle.btn-default,\n.btn-default:active:focus,\n.btn-default:active:hover,\n.btn-default.active:hover,\n.btn-default.active:focus {\n  color: inherit;\n  border: 1px solid #d2d2d2;\n}\n.btn-default:active,\n.btn-default.active,\n.open .dropdown-toggle.btn-default {\n  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset;\n}\n.btn-default.disabled,\n.btn-default.disabled:hover,\n.btn-default.disabled:focus,\n.btn-default.disabled:active,\n.btn-default.disabled.active,\n.btn-default[disabled],\n.btn-default[disabled]:hover,\n.btn-default[disabled]:focus,\n.btn-default[disabled]:active,\n.btn-default.active[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-default:hover,\nfieldset[disabled] .btn-default:focus,\nfieldset[disabled] .btn-default:active,\nfieldset[disabled] .btn-default.active {\n  color: #cacaca;\n}\n.btn-warning {\n  color: #ffffff;\n  background-color: #f8ac59;\n  border-color: #f8ac59;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus {\n  color: #ffffff;\n  background-color: #f7a54a;\n  border-color: #f7a54a;\n}\n.btn-warning.disabled,\n.btn-warning:disabled {\n  color: #ffffff;\n  background-color: #f7a54a;\n  border-color: #f7a54a;\n}\n.btn-warning:not(:disabled):not(.disabled):active,\n.btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n  color: #ffffff;\n  background-color: #f7a54a;\n  border-color: #f7a54a;\n}\n.btn-warning:not(:disabled):not(.disabled):active:focus,\n.btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-danger {\n  color: #fff;\n  background-color: #ed5565;\n  border-color: #ed5565;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus {\n  color: #fff;\n  background-color: #ec4758;\n  border-color: #ec4758;\n}\n.btn-danger.disabled,\n.btn-danger:disabled {\n  color: #fff;\n  background-color: #ec4758;\n  border-color: #ec4758;\n}\n.btn-danger:not(:disabled):not(.disabled):active,\n.btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n  color: #fff;\n  background-color: #ec4758;\n  border-color: #ec4758;\n}\n.btn-danger:not(:disabled):not(.disabled):active:focus,\n.btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-link {\n  color: inherit;\n}\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active,\n.btn-link.active,\n.open .dropdown-toggle.btn-link {\n  color: #1ab394;\n  text-decoration: none;\n}\n.btn-link:active,\n.btn-link.active,\n.open .dropdown-toggle.btn-link {\n  background-image: none;\n  box-shadow: none;\n}\n.btn-link.disabled,\n.btn-link.disabled:hover,\n.btn-link.disabled:focus,\n.btn-link.disabled:active,\n.btn-link.disabled.active,\n.btn-link[disabled],\n.btn-link[disabled]:hover,\n.btn-link[disabled]:focus,\n.btn-link[disabled]:active,\n.btn-link.active[disabled],\nfieldset[disabled] .btn-link,\nfieldset[disabled] .btn-link:hover,\nfieldset[disabled] .btn-link:focus,\nfieldset[disabled] .btn-link:active,\nfieldset[disabled] .btn-link.active {\n  color: #cacaca;\n}\n.btn-white {\n  color: inherit;\n  background: white;\n  border: 1px solid #e7eaec;\n}\n.btn-white:hover,\n.btn-white:focus,\n.btn-white:active,\n.btn-white.active,\n.open .dropdown-toggle.btn-white,\n.btn-white:active:focus,\n.btn-white:active:hover,\n.btn-white.active:hover,\n.btn-white.active:focus {\n  color: inherit;\n  border: 1px solid #d2d2d2;\n}\n.btn-white:active,\n.btn-white.active {\n  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset;\n}\n.btn-white:active,\n.btn-white.active,\n.open .dropdown-toggle.btn-white {\n  background-image: none;\n}\n.btn-white.disabled,\n.btn-white.disabled:hover,\n.btn-white.disabled:focus,\n.btn-white.disabled:active,\n.btn-white.disabled.active,\n.btn-white[disabled],\n.btn-white[disabled]:hover,\n.btn-white[disabled]:focus,\n.btn-white[disabled]:active,\n.btn-white.active[disabled],\nfieldset[disabled] .btn-white,\nfieldset[disabled] .btn-white:hover,\nfieldset[disabled] .btn-white:focus,\nfieldset[disabled] .btn-white:active,\nfieldset[disabled] .btn-white.active {\n  color: #cacaca;\n}\n.form-control,\n.form-control:focus,\n.has-error .form-control:focus,\n.has-success .form-control:focus,\n.has-warning .form-control:focus,\n.navbar-collapse,\n.navbar-form,\n.navbar-form-custom .form-control:focus,\n.navbar-form-custom .form-control:hover,\n.open .btn.dropdown-toggle,\n.panel,\n.popover,\n.progress,\n.progress-bar {\n  box-shadow: none;\n}\n.btn-outline {\n  color: inherit;\n  background-color: transparent;\n  transition: all .5s;\n}\n.btn-rounded {\n  border-radius: 50px;\n}\n.btn-large-dim {\n  width: 90px;\n  height: 90px;\n  font-size: 42px;\n}\nbutton.dim {\n  display: inline-block;\n  text-decoration: none;\n  text-transform: uppercase;\n  text-align: center;\n  padding-top: 6px;\n  margin-right: 10px;\n  position: relative;\n  cursor: pointer;\n  border-radius: 5px;\n  font-weight: 600;\n  margin-bottom: 20px !important;\n}\nbutton.dim:active {\n  top: 3px;\n}\nbutton.btn-primary.dim {\n  box-shadow: inset 0 0 0 #16987e, 0 5px 0 0 #16987e, 0 10px 5px #999999 !important;\n}\nbutton.btn-primary.dim:active {\n  box-shadow: inset 0 0 0 #16987e, 0 2px 0 0 #16987e, 0 5px 3px #999999 !important;\n}\nbutton.btn-default.dim {\n  box-shadow: inset 0 0 0 #b3b3b3, 0 5px 0 0 #b3b3b3, 0 10px 5px #999999 !important;\n}\nbutton.btn-default.dim:active {\n  box-shadow: inset 0 0 0 #b3b3b3, 0 2px 0 0 #b3b3b3, 0 5px 3px #999999 !important;\n}\nbutton.btn-warning.dim {\n  box-shadow: inset 0 0 0 #f79d3c, 0 5px 0 0 #f79d3c, 0 10px 5px #999999 !important;\n}\nbutton.btn-warning.dim:active {\n  box-shadow: inset 0 0 0 #f79d3c, 0 2px 0 0 #f79d3c, 0 5px 3px #999999 !important;\n}\nbutton.btn-info.dim {\n  box-shadow: inset 0 0 0 #1eacae, 0 5px 0 0 #1eacae, 0 10px 5px #999999 !important;\n}\nbutton.btn-info.dim:active {\n  box-shadow: inset 0 0 0 #1eacae, 0 2px 0 0 #1eacae, 0 5px 3px #999999 !important;\n}\nbutton.btn-success.dim {\n  box-shadow: inset 0 0 0 #1872ab, 0 5px 0 0 #1872ab, 0 10px 5px #999999 !important;\n}\nbutton.btn-success.dim:active {\n  box-shadow: inset 0 0 0 #1872ab, 0 2px 0 0 #1872ab, 0 5px 3px #999999 !important;\n}\nbutton.btn-danger.dim {\n  box-shadow: inset 0 0 0 #ea394c, 0 5px 0 0 #ea394c, 0 10px 5px #999999 !important;\n}\nbutton.btn-danger.dim:active {\n  box-shadow: inset 0 0 0 #ea394c, 0 2px 0 0 #ea394c, 0 5px 3px #999999 !important;\n}\nbutton.dim:before {\n  font-size: 50px;\n  line-height: 1em;\n  font-weight: normal;\n  color: #fff;\n  display: block;\n  padding-top: 10px;\n}\nbutton.dim:active:before {\n  top: 7px;\n  font-size: 50px;\n}\n.btn:focus {\n  outline: none !important;\n}\n.label {\n  background-color: #d1dade;\n  color: #5e5e5e;\n  font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n  font-weight: 600;\n  padding: 3px 8px;\n  text-shadow: none;\n  border-radius: 0.25em;\n  line-height: 1;\n  white-space: nowrap;\n}\n.nav .label,\n.ibox .label {\n  font-size: 10px;\n}\n.badge {\n  background-color: #d1dade;\n  color: #5e5e5e;\n  font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n  font-size: 11px;\n  font-weight: 600;\n  padding-bottom: 4px;\n  padding-left: 6px;\n  padding-right: 6px;\n  text-shadow: none;\n  white-space: nowrap;\n}\n.label-primary,\n.badge-primary {\n  background-color: #1ab394;\n  color: #FFFFFF;\n}\n.label-success,\n.badge-success {\n  background-color: #1c84c6;\n  color: #FFFFFF;\n}\n.label-warning,\n.badge-warning {\n  background-color: #f8ac59;\n  color: #FFFFFF;\n}\n.label-warning-light,\n.badge-warning-light {\n  background-color: #f8ac59;\n  color: #ffffff;\n}\n.label-danger,\n.badge-danger {\n  background-color: #ed5565;\n  color: #FFFFFF;\n}\n.label-info,\n.badge-info {\n  background-color: #23c6c8;\n  color: #FFFFFF;\n}\n.label-inverse,\n.badge-inverse {\n  background-color: #262626;\n  color: #FFFFFF;\n}\n.label-white,\n.badge-white {\n  background-color: #FFFFFF;\n  color: #5E5E5E;\n}\n.label-white,\n.badge-disable {\n  background-color: #2A2E36;\n  color: #8B91A0;\n}\n/* TOOGLE SWICH */\n.onoffswitch {\n  position: relative;\n  width: 64px;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n}\n.onoffswitch-checkbox {\n  display: none;\n}\n.onoffswitch-label {\n  display: block;\n  overflow: hidden;\n  cursor: pointer;\n  border: 2px solid #1ab394;\n  border-radius: 2px;\n}\n.onoffswitch-inner {\n  width: 200%;\n  margin-left: -100%;\n  -moz-transition: margin 0.3s ease-in 0s;\n  -webkit-transition: margin 0.3s ease-in 0s;\n  -o-transition: margin 0.3s ease-in 0s;\n  transition: margin 0.3s ease-in 0s;\n}\n.onoffswitch-inner:before,\n.onoffswitch-inner:after {\n  float: left;\n  width: 50%;\n  height: 20px;\n  padding: 0;\n  line-height: 20px;\n  font-size: 12px;\n  color: white;\n  font-family: Trebuchet, Arial, sans-serif;\n  font-weight: bold;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n.onoffswitch-inner:before {\n  content: \"ON\";\n  padding-left: 10px;\n  background-color: #1ab394;\n  color: #FFFFFF;\n}\n.onoffswitch-inner:after {\n  content: \"OFF\";\n  padding-right: 10px;\n  background-color: #FFFFFF;\n  color: #999999;\n  text-align: right;\n}\n.onoffswitch-switch {\n  width: 20px;\n  margin: 0;\n  background: #FFFFFF;\n  border: 2px solid #1ab394;\n  border-radius: 2px;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 44px;\n  -moz-transition: all 0.3s ease-in 0s;\n  -webkit-transition: all 0.3s ease-in 0s;\n  -o-transition: all 0.3s ease-in 0s;\n  transition: all 0.3s ease-in 0s;\n}\n.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {\n  margin-left: 0;\n}\n.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {\n  right: 0;\n}\n.onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-inner:before {\n  background-color: #919191;\n}\n.onoffswitch-checkbox:disabled + .onoffswitch-label,\n.onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-switch {\n  border-color: #919191;\n}\n/* CHOSEN PLUGIN */\n.chosen-container-single .chosen-single {\n  background: #ffffff;\n  box-shadow: none;\n  -moz-box-sizing: border-box;\n  border-radius: 2px;\n  cursor: text;\n  height: auto !important;\n  margin: 0;\n  min-height: 30px;\n  overflow: hidden;\n  padding: 4px 12px;\n  position: relative;\n  width: 100%;\n}\n.chosen-container-multi .chosen-choices li.search-choice {\n  background: #f1f1f1;\n  border: 1px solid #e5e6e7;\n  border-radius: 2px;\n  box-shadow: none;\n  color: #333333;\n  cursor: default;\n  line-height: 13px;\n  margin: 3px 0 3px 5px;\n  padding: 3px 20px 3px 5px;\n  position: relative;\n}\n/* Tags Input Plugin */\n.bootstrap-tagsinput {\n  border: 1px solid #e5e6e7;\n  box-shadow: none;\n}\n/* PAGINATIN */\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  border-color: #DDDDDD;\n  cursor: default;\n  z-index: 2;\n}\n.pagination > li > a,\n.pagination > li > span {\n  background-color: #FFFFFF;\n  border: 1px solid #DDDDDD;\n  color: inherit;\n  float: left;\n  line-height: 1.42857;\n  margin-left: -1px;\n  padding: 4px 10px;\n  position: relative;\n  text-decoration: none;\n}\n.page-item.active .page-link {\n  background-color: #1ab394;\n  border-color: #1ab394;\n}\n.page-link:focus {\n  box-shadow: none;\n}\n.page-link:hover {\n  color: #676a6c;\n}\n.pagination .footable-page.active a {\n  background-color: #1ab394;\n  border-color: #1ab394;\n  color: white;\n}\n/* TOOLTIPS */\n.tooltip-inner {\n  background-color: #2f4050;\n}\n.tooltip.top .tooltip-arrow {\n  border-top-color: #2f4050;\n}\n.tooltip.right .tooltip-arrow {\n  border-right-color: #2f4050;\n}\n.tooltip.bottom .tooltip-arrow {\n  border-bottom-color: #2f4050;\n}\n.tooltip.left .tooltip-arrow {\n  border-left-color: #2f4050;\n}\n/* EASY PIE CHART*/\n.easypiechart {\n  position: relative;\n  text-align: center;\n}\n.easypiechart .h2 {\n  margin-left: 10px;\n  margin-top: 10px;\n  display: inline-block;\n}\n.easypiechart canvas {\n  top: 0;\n  left: 0;\n}\n.easypiechart .easypie-text {\n  line-height: 1;\n  position: absolute;\n  top: 33px;\n  width: 100%;\n  z-index: 1;\n}\n.easypiechart img {\n  margin-top: -4px;\n}\n.jqstooltip {\n  -webkit-box-sizing: content-box;\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n}\n/* FULLCALENDAR */\n.fc-state-default {\n  background-color: #ffffff;\n  background-image: none;\n  background-repeat: repeat-x;\n  box-shadow: none;\n  color: #333333;\n  text-shadow: none;\n}\n.fc-state-default {\n  border: 1px solid;\n}\n.fc-button {\n  color: inherit;\n  border: 1px solid #e7eaec;\n  cursor: pointer;\n  display: inline-block;\n  height: 1.9em;\n  line-height: 1.9em;\n  overflow: hidden;\n  padding: 0 0.6em;\n  position: relative;\n  white-space: nowrap;\n}\n.fc-state-active {\n  background-color: #1ab394;\n  border-color: #1ab394;\n  color: #ffffff;\n}\n.fc-header-title h2 {\n  font-size: 16px;\n  font-weight: 600;\n  color: inherit;\n}\n.fc-content .fc-widget-header,\n.fc-content .fc-widget-content {\n  border-color: #e7eaec;\n  font-weight: normal;\n}\n.fc-border-separate tbody {\n  background-color: #F8F8F8;\n}\n.fc-state-highlight {\n  background: none repeat scroll 0 0 #FCF8E3;\n}\n.external-event {\n  padding: 5px 10px;\n  border-radius: 2px;\n  cursor: pointer;\n  margin-bottom: 5px;\n}\n.fc-ltr .fc-event-hori.fc-event-end,\n.fc-rtl .fc-event-hori.fc-event-start {\n  border-radius: 2px;\n}\n.fc-event,\n.fc-agenda .fc-event-time,\n.fc-event a {\n  padding: 4px 6px;\n  background-color: #1ab394;\n  /* background color */\n  border-color: #1ab394;\n  /* border color */\n}\n.fc-event-time,\n.fc-event-title {\n  color: #717171;\n  padding: 0 1px;\n}\n.ui-calendar .fc-event-time,\n.ui-calendar .fc-event-title {\n  color: #fff;\n}\n.fc-event-container a.fc-event {\n  color: #fff;\n}\n/* Chat */\n.chat-activity-list .chat-element {\n  border-bottom: 1px solid #e7eaec;\n}\n.chat-element:first-child {\n  margin-top: 0;\n}\n.chat-element {\n  padding-bottom: 15px;\n}\n.chat-element,\n.chat-element .media {\n  margin-top: 15px;\n}\n.chat-element,\n.media-body {\n  overflow: hidden;\n}\n.chat-element .media-body {\n  display: block;\n  width: auto;\n}\n.chat-element > .float-left {\n  margin-right: 10px;\n}\n.chat-element img.rounded-circle,\n.dropdown-messages-box img.rounded-circle {\n  width: 38px;\n  height: 38px;\n}\n.chat-element .well {\n  border: 1px solid #e7eaec;\n  box-shadow: none;\n  margin-top: 10px;\n  margin-bottom: 5px;\n  padding: 10px 20px;\n  font-size: 11px;\n  line-height: 16px;\n}\n.chat-element .actions {\n  margin-top: 10px;\n}\n.chat-element .photos {\n  margin: 10px 0;\n}\n.right.chat-element > .float-right {\n  margin-left: 10px;\n}\n.chat-photo {\n  max-height: 180px;\n  border-radius: 4px;\n  overflow: hidden;\n  margin-right: 10px;\n  margin-bottom: 10px;\n}\n.chat {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.chat li {\n  margin-bottom: 10px;\n  padding-bottom: 5px;\n  border-bottom: 1px dotted #B3A9A9;\n}\n.chat li.left .chat-body {\n  margin-left: 60px;\n}\n.chat li.right .chat-body {\n  margin-right: 60px;\n}\n.chat li .chat-body p {\n  margin: 0;\n  color: #777777;\n}\n.panel .slidedown .glyphicon,\n.chat .glyphicon {\n  margin-right: 5px;\n}\n.chat-panel .panel-body {\n  height: 350px;\n  overflow-y: scroll;\n}\n/* LIST GROUP */\na.list-group-item.active,\na.list-group-item.active:hover,\na.list-group-item.active:focus {\n  background-color: #1ab394;\n  border-color: #1ab394;\n  color: #FFFFFF;\n  z-index: 2;\n}\n.list-group-item-heading {\n  margin-top: 10px;\n}\n.list-group-item-text {\n  margin: 0 0 10px;\n  color: inherit;\n  font-size: 12px;\n  line-height: inherit;\n}\n.no-padding .list-group-item {\n  border-left: none;\n  border-right: none;\n  border-bottom: none;\n}\n.no-padding .list-group-item:first-child {\n  border-left: none;\n  border-right: none;\n  border-bottom: none;\n  border-top: none;\n}\n.no-padding .list-group {\n  margin-bottom: 0;\n}\n.list-group-item {\n  background-color: inherit;\n  border: 1px solid #e7eaec;\n  display: block;\n  margin-bottom: -1px;\n  padding: 10px 15px;\n  position: relative;\n}\n.elements-list .list-group-item {\n  border-left: none;\n  border-right: none;\n  padding: 0;\n}\n.elements-list .list-group-item:first-child {\n  border-left: none;\n  border-right: none;\n  border-top: none !important;\n}\n.elements-list .list-group {\n  margin-bottom: 0;\n}\n.elements-list a {\n  color: inherit;\n}\n.elements-list .list-group-item a.active,\n.elements-list .list-group-item a:hover {\n  background: #f3f3f4;\n  color: inherit;\n  border-color: #e7eaec;\n  border-radius: 0;\n}\n.elements-list li.active {\n  transition: none;\n}\n.elements-list .nav-link {\n  padding: 15px 25px;\n}\n.element-detail-box {\n  padding: 25px;\n}\n/* FLOT CHART  */\n.flot-chart {\n  display: block;\n  height: 200px;\n}\n.widget .flot-chart.dashboard-chart {\n  display: block;\n  height: 120px;\n  margin-top: 40px;\n}\n.flot-chart.dashboard-chart {\n  display: block;\n  height: 180px;\n  margin-top: 40px;\n}\n.flot-chart-content {\n  width: 100%;\n  height: 100%;\n}\n.flot-chart-pie-content {\n  width: 200px;\n  height: 200px;\n  margin: auto;\n}\n.jqstooltip {\n  position: absolute;\n  display: block;\n  left: 0;\n  top: 0;\n  visibility: hidden;\n  background: #2b303a;\n  background-color: rgba(43, 48, 58, 0.8);\n  color: white;\n  text-align: left;\n  white-space: nowrap;\n  z-index: 10000;\n  padding: 5px 5px 5px 5px;\n  min-height: 22px;\n  border-radius: 3px;\n}\n.jqsfield {\n  color: white;\n  text-align: left;\n}\n.fh-150 {\n  height: 150px;\n}\n.fh-200 {\n  height: 200px;\n}\n.h-150 {\n  min-height: 150px;\n}\n.h-200 {\n  min-height: 200px;\n}\n.h-300 {\n  min-height: 300px;\n}\n.w-150 {\n  min-width: 150px;\n}\n.w-200 {\n  min-width: 200px;\n}\n.w-300 {\n  min-width: 300px;\n}\n.legendLabel {\n  padding-left: 5px;\n}\n.stat-list li:first-child {\n  margin-top: 0;\n}\n.stat-list {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n.stat-percent {\n  float: right;\n}\n.stat-list li {\n  margin-top: 15px;\n  position: relative;\n}\n/* DATATABLES */\ntable.dataTable thead .sorting,\ntable.dataTable thead .sorting_asc:after,\ntable.dataTable thead .sorting_desc,\ntable.dataTable thead .sorting_asc_disabled,\ntable.dataTable thead .sorting_desc_disabled {\n  background: transparent;\n}\n.dataTables_wrapper {\n  padding-bottom: 30px;\n}\n.dataTables_length {\n  float: left;\n}\n.dataTables_filter label {\n  margin-right: 5px;\n}\n.html5buttons {\n  float: right;\n}\n.html5buttons a {\n  border: 1px solid #e7eaec;\n  background: #fff;\n  color: #676a6c;\n  box-shadow: none;\n  padding: 6px 8px;\n  font-size: 12px;\n}\n.html5buttons a:hover,\n.html5buttons a:focus:active {\n  background-color: #eee;\n  color: inherit;\n  border-color: #d2d2d2;\n}\ndiv.dt-button-info {\n  z-index: 100;\n}\n@media (max-width: 768px) {\n  .html5buttons {\n    float: none;\n    margin-top: 10px;\n  }\n  .dataTables_length {\n    float: none;\n  }\n}\n/* CIRCLE */\n.img-circle {\n  border-radius: 50%;\n}\n.btn-circle {\n  width: 30px;\n  height: 30px;\n  padding: 6px 0;\n  border-radius: 15px;\n  text-align: center;\n  font-size: 12px;\n  line-height: 1.428571429;\n}\n.btn-circle.btn-lg {\n  width: 50px;\n  height: 50px;\n  padding: 10px 16px;\n  border-radius: 25px;\n  font-size: 18px;\n  line-height: 1.33;\n}\n.btn-circle.btn-xl {\n  width: 70px;\n  height: 70px;\n  padding: 10px 16px;\n  border-radius: 35px;\n  font-size: 24px;\n  line-height: 1.33;\n}\n.show-grid [class^=\"col-\"] {\n  padding-top: 10px;\n  padding-bottom: 10px;\n  border: 1px solid #ddd;\n  background-color: #eee !important;\n}\n.show-grid {\n  margin: 15px 0;\n}\n/* ANIMATION */\n.css-animation-box h1 {\n  font-size: 44px;\n}\n.animation-efect-links a {\n  padding: 4px 6px;\n  font-size: 12px;\n}\n#animation_box {\n  background-color: #f9f8f8;\n  border-radius: 16px;\n  width: 80%;\n  margin: 0 auto;\n  padding-top: 80px;\n}\n.animation-text-box {\n  position: absolute;\n  margin-top: 40px;\n  left: 50%;\n  margin-left: -100px;\n  width: 200px;\n}\n.animation-text-info {\n  position: absolute;\n  margin-top: -60px;\n  left: 50%;\n  margin-left: -100px;\n  width: 200px;\n  font-size: 10px;\n}\n.animation-text-box h2 {\n  font-size: 54px;\n  font-weight: 600;\n  margin-bottom: 5px;\n}\n.animation-text-box p {\n  font-size: 12px;\n  text-transform: uppercase;\n}\n/* PEACE */\n.pace {\n  -webkit-pointer-events: none;\n  pointer-events: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n}\n.pace-inactive {\n  display: none;\n}\n.pace .pace-progress {\n  background: #1ab394;\n  position: fixed;\n  z-index: 2040;\n  top: 0;\n  right: 100%;\n  width: 100%;\n  height: 2px;\n}\n.pace-inactive {\n  display: none;\n}\n/* WIDGETS */\n.widget {\n  border-radius: 5px;\n  padding: 15px 20px;\n  margin-bottom: 10px;\n  margin-top: 10px;\n}\n.widget.style1 h2 {\n  font-size: 30px;\n}\n.widget h2,\n.widget h3 {\n  margin-top: 5px;\n  margin-bottom: 0;\n}\n.widget-text-box {\n  padding: 20px;\n  border: 1px solid #e7eaec;\n  background: #ffffff;\n}\n.widget-head-color-box {\n  border-radius: 5px 5px 0 0;\n  margin-top: 10px;\n}\n.widget .flot-chart {\n  height: 100px;\n}\n.vertical-align div {\n  display: inline-block;\n  vertical-align: middle;\n}\n.vertical-align h2,\n.vertical-align h3 {\n  margin: 0;\n}\n.todo-list {\n  list-style: none outside none;\n  margin: 0;\n  padding: 0;\n  font-size: 14px;\n}\n.todo-list.small-list {\n  font-size: 12px;\n}\n.todo-list.small-list > li {\n  background: #f3f3f4;\n  border-left: none;\n  border-right: none;\n  border-radius: 4px;\n  color: inherit;\n  margin-bottom: 2px;\n  padding: 6px 6px 6px 12px;\n}\n.todo-list.small-list .btn-xs,\n.todo-list.small-list .btn-group-xs > .btn {\n  border-radius: 5px;\n  font-size: 10px;\n  line-height: 1.5;\n  padding: 1px 2px 1px 5px;\n}\n.todo-list > li {\n  background: #f3f3f4;\n  border-left: 6px solid #e7eaec;\n  border-right: 6px solid #e7eaec;\n  border-radius: 4px;\n  color: inherit;\n  margin-bottom: 2px;\n  padding: 10px;\n}\n.todo-list .handle {\n  cursor: move;\n  display: inline-block;\n  font-size: 16px;\n  margin: 0 5px;\n}\n.todo-list > li .label {\n  font-size: 9px;\n  margin-left: 10px;\n}\n.check-link {\n  font-size: 16px;\n}\n.todo-completed {\n  text-decoration: line-through;\n}\n.geo-statistic h1 {\n  font-size: 36px;\n  margin-bottom: 0;\n}\n.glyphicon.fa {\n  font-family: \"FontAwesome\";\n}\n/* INPUTS */\n.inline {\n  display: inline-block !important;\n}\n.input-s-sm {\n  width: 120px;\n}\n.input-s {\n  width: 200px;\n}\n.form-control {\n  font-size: 0.9rem;\n}\nselect.form-control:not([size]):not([multiple]) {\n  height: 2.05rem;\n}\n.input-sm,\n.form-control-sm {\n  height: 31px;\n}\n.input-s-lg {\n  width: 250px;\n}\n.i-checks {\n  padding-left: 0;\n}\n.form-control,\n.single-line {\n  background-color: #FFFFFF;\n  background-image: none;\n  border: 1px solid #e5e6e7;\n  border-radius: 1px;\n  color: inherit;\n  display: block;\n  padding: 6px 12px;\n  transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s;\n  width: 100%;\n}\n.form-control:focus,\n.single-line:focus {\n  border-color: #1ab394;\n}\n.has-success .form-control,\n.has-success .form-control:focus {\n  border-color: #1ab394;\n}\n.has-warning .form-control,\n.has-warning .form-control:focus {\n  border-color: #f8ac59;\n}\n.has-error .form-control,\n.has-error .form-control:focus {\n  border-color: #ed5565;\n}\n.has-success .control-label {\n  color: #1ab394;\n}\n.has-warning .control-label {\n  color: #f8ac59;\n}\n.has-error .control-label {\n  color: #ed5565;\n}\n.input-group-addon {\n  background-color: #fff;\n  border: 1px solid #E5E6E7;\n  border-radius: 1px;\n  color: inherit;\n  font-size: 14px;\n  font-weight: 400;\n  line-height: 1;\n  padding: 9px 12px 4px 12px;\n  text-align: center;\n}\n.input-daterange .input-group-addon {\n  margin: 0;\n}\n.input-group.date .input-group-addon {\n  border-right: 0;\n}\n.spinner-buttons.input-group-btn .btn-xs {\n  line-height: 1.13;\n}\n.spinner-buttons.input-group-btn {\n  width: 20%;\n}\n.noUi-connect {\n  background: none repeat scroll 0 0 #1ab394;\n  box-shadow: none;\n}\n.slider_red .noUi-connect {\n  background: none repeat scroll 0 0 #ed5565;\n  box-shadow: none;\n}\n/* UI Sortable */\n.ui-sortable .ibox-title {\n  cursor: move;\n}\n.ui-sortable-placeholder {\n  border: 1px dashed #cecece !important;\n  visibility: visible !important;\n  background: #e7eaec;\n}\n.ibox.ui-sortable-placeholder {\n  margin: 0 0 23px !important;\n}\n/* SWITCHES */\n.onoffswitch {\n  position: relative;\n  width: 54px;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n}\n.onoffswitch-checkbox {\n  display: none;\n}\n.onoffswitch-label {\n  display: block;\n  overflow: hidden;\n  cursor: pointer;\n  border: 2px solid #1AB394;\n  border-radius: 3px;\n}\n.onoffswitch-inner {\n  display: block;\n  width: 200%;\n  margin-left: -100%;\n  -moz-transition: margin 0.3s ease-in 0s;\n  -webkit-transition: margin 0.3s ease-in 0s;\n  -o-transition: margin 0.3s ease-in 0s;\n  transition: margin 0.3s ease-in 0s;\n}\n.onoffswitch-inner:before,\n.onoffswitch-inner:after {\n  display: block;\n  float: left;\n  width: 50%;\n  height: 16px;\n  padding: 0;\n  line-height: 16px;\n  font-size: 10px;\n  color: white;\n  font-family: Trebuchet, Arial, sans-serif;\n  font-weight: bold;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n.onoffswitch-inner:before {\n  content: \"ON\";\n  padding-left: 7px;\n  background-color: #1AB394;\n  color: #FFFFFF;\n}\n.onoffswitch-inner:after {\n  content: \"OFF\";\n  padding-right: 7px;\n  background-color: #FFFFFF;\n  color: #919191;\n  text-align: right;\n}\n.onoffswitch-switch {\n  display: block;\n  width: 18px;\n  margin: 0;\n  background: #FFFFFF;\n  border: 2px solid #1AB394;\n  border-radius: 3px;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 36px;\n  -moz-transition: all 0.3s ease-in 0s;\n  -webkit-transition: all 0.3s ease-in 0s;\n  -o-transition: all 0.3s ease-in 0s;\n  transition: all 0.3s ease-in 0s;\n}\n.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {\n  margin-left: 0;\n}\n.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {\n  right: 0;\n}\n/* jqGrid */\n.ui-jqgrid {\n  -moz-box-sizing: content-box;\n}\n.ui-jqgrid-btable {\n  border-collapse: separate;\n}\n.ui-jqgrid-htable {\n  border-collapse: separate;\n}\n.ui-jqgrid-titlebar {\n  height: 40px;\n  line-height: 15px;\n  color: #676a6c;\n  background-color: #F9F9F9;\n  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n}\n.ui-jqgrid .ui-jqgrid-title {\n  float: left;\n  margin: 1.1em 1em 0.2em;\n}\n.ui-jqgrid .ui-jqgrid-titlebar {\n  position: relative;\n  border-left: 0 solid;\n  border-right: 0 solid;\n  border-top: 0 solid;\n}\n.ui-widget-header {\n  background: none;\n  background-image: none;\n  background-color: #f5f5f6;\n  text-transform: uppercase;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.ui-jqgrid tr.ui-row-ltr td {\n  border-right-color: inherit;\n  border-right-style: solid;\n  border-right-width: 1px;\n  text-align: left;\n  border-color: #DDDDDD;\n  background-color: inherit;\n}\n.ui-search-toolbar input[type=\"text\"] {\n  font-size: 12px;\n  height: 15px;\n  border: 1px solid #CCCCCC;\n  border-radius: 0;\n}\n.ui-state-default,\n.ui-widget-content .ui-state-default,\n.ui-widget-header .ui-state-default {\n  background: #F9F9F9;\n  border: 1px solid #DDDDDD;\n  line-height: 15px;\n  font-weight: bold;\n  color: #676a6c;\n  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n}\n.ui-widget-content {\n  box-sizing: content-box;\n}\n.ui-icon-triangle-1-n {\n  background-position: 1px -16px;\n}\n.ui-jqgrid tr.ui-search-toolbar th {\n  border-top-width: 0 !important;\n  border-top-color: inherit !important;\n  border-top-style: ridge !important;\n}\n.ui-state-hover,\n.ui-widget-content .ui-state-hover,\n.ui-state-focus,\n.ui-widget-content .ui-state-focus,\n.ui-widget-header .ui-state-focus {\n  background: #f5f5f5;\n  border-collapse: separate;\n}\n.ui-state-highlight,\n.ui-widget-content .ui-state-highlight,\n.ui-widget-header .ui-state-highlight {\n  background: #f2fbff;\n}\n.ui-state-active,\n.ui-widget-content .ui-state-active,\n.ui-widget-header .ui-state-active {\n  border: 1px solid #dddddd;\n  background: #ffffff;\n  font-weight: normal;\n  color: #212121;\n}\n.ui-jqgrid .ui-pg-input {\n  font-size: inherit;\n  width: 50px;\n  border: 1px solid #CCCCCC;\n  height: 15px;\n}\n.ui-jqgrid .ui-pg-selbox {\n  display: block;\n  font-size: 1em;\n  height: 25px;\n  line-height: 18px;\n  margin: 0;\n  width: auto;\n}\n.ui-jqgrid .ui-pager-control {\n  position: relative;\n}\n.ui-jqgrid .ui-jqgrid-pager {\n  height: 32px;\n  position: relative;\n}\n.ui-pg-table .navtable .ui-corner-all {\n  border-radius: 0;\n}\n.ui-jqgrid .ui-pg-button:hover {\n  padding: 1px;\n  border: 0;\n}\n.ui-jqgrid .loading {\n  position: absolute;\n  top: 45%;\n  left: 45%;\n  width: auto;\n  height: auto;\n  z-index: 101;\n  padding: 6px;\n  margin: 5px;\n  text-align: center;\n  font-weight: bold;\n  display: none;\n  border-width: 2px !important;\n  font-size: 11px;\n}\n.ui-jqgrid .form-control {\n  height: 10px;\n  width: auto;\n  display: inline;\n  padding: 10px 12px;\n}\n.ui-jqgrid-pager {\n  height: 32px;\n}\n.ui-corner-all,\n.ui-corner-top,\n.ui-corner-left,\n.ui-corner-tl {\n  border-top-left-radius: 0;\n}\n.ui-corner-all,\n.ui-corner-top,\n.ui-corner-right,\n.ui-corner-tr {\n  border-top-right-radius: 0;\n}\n.ui-corner-all,\n.ui-corner-bottom,\n.ui-corner-left,\n.ui-corner-bl {\n  border-bottom-left-radius: 0;\n}\n.ui-corner-all,\n.ui-corner-bottom,\n.ui-corner-right,\n.ui-corner-br {\n  border-bottom-right-radius: 0;\n}\n.ui-widget-content {\n  border: 1px solid #ddd;\n}\n.ui-jqgrid .ui-jqgrid-titlebar {\n  padding: 0;\n}\n.ui-jqgrid .ui-jqgrid-titlebar {\n  border-bottom: 1px solid #ddd;\n}\n.ui-jqgrid tr.jqgrow td {\n  padding: 6px;\n}\n.ui-jqdialog .ui-jqdialog-titlebar {\n  padding: 10px 10px;\n}\n.ui-jqdialog .ui-jqdialog-title {\n  float: none !important;\n}\n.ui-jqdialog > .ui-resizable-se {\n  position: absolute;\n}\n/* Nestable list */\n.dd {\n  position: relative;\n  display: block;\n  margin: 0;\n  padding: 0;\n  list-style: none;\n  font-size: 13px;\n  line-height: 20px;\n}\n.dd-list {\n  display: block;\n  position: relative;\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n.dd-list .dd-list {\n  padding-left: 30px;\n}\n.dd-collapsed .dd-list {\n  display: none;\n}\n.dd-item,\n.dd-empty,\n.dd-placeholder {\n  display: block;\n  position: relative;\n  margin: 0;\n  padding: 0;\n  min-height: 20px;\n  font-size: 13px;\n  line-height: 20px;\n}\n.dd-handle {\n  display: block;\n  margin: 5px 0;\n  padding: 5px 10px;\n  color: #333;\n  text-decoration: none;\n  border: 1px solid #e7eaec;\n  background: #f5f5f5;\n  -webkit-border-radius: 3px;\n  border-radius: 3px;\n  box-sizing: border-box;\n  -moz-box-sizing: border-box;\n}\n.dd-handle span {\n  font-weight: bold;\n}\n.dd-handle:hover {\n  background: #f0f0f0;\n  cursor: pointer;\n  font-weight: bold;\n}\n.dd-item > button {\n  display: block;\n  position: relative;\n  cursor: pointer;\n  float: left;\n  width: 25px;\n  height: 20px;\n  margin: 5px 0;\n  padding: 0;\n  text-indent: 100%;\n  white-space: nowrap;\n  overflow: hidden;\n  border: 0;\n  background: transparent;\n  font-size: 12px;\n  line-height: 1;\n  text-align: center;\n  font-weight: bold;\n}\n.dd-item > button:before {\n  content: '+';\n  display: block;\n  position: absolute;\n  width: 100%;\n  text-align: center;\n  text-indent: 0;\n}\n.dd-item > button[data-action=\"collapse\"]:before {\n  content: '-';\n}\n#nestable2 .dd-item > button {\n  font-family: FontAwesome;\n  height: 34px;\n  width: 33px;\n  color: #c1c1c1;\n}\n#nestable2 .dd-item > button:before {\n  content: \"\\f067\";\n}\n#nestable2 .dd-item > button[data-action=\"collapse\"]:before {\n  content: \"\\f068\";\n}\n.dd-placeholder,\n.dd-empty {\n  margin: 5px 0;\n  padding: 0;\n  min-height: 30px;\n  background: #f2fbff;\n  border: 1px dashed #b6bcbf;\n  box-sizing: border-box;\n  -moz-box-sizing: border-box;\n}\n.dd-empty {\n  border: 1px dashed #bbb;\n  min-height: 100px;\n  background-color: #e5e5e5;\n  background-image: -webkit-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), -webkit-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);\n  background-image: -moz-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), -moz-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);\n  background-image: linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);\n  background-size: 60px 60px;\n  background-position: 0 0, 30px 30px;\n}\n.dd-dragel {\n  position: absolute;\n  z-index: 9999;\n  pointer-events: none;\n}\n.dd-dragel > .dd-item .dd-handle {\n  margin-top: 0;\n}\n.dd-dragel .dd-handle {\n  -webkit-box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);\n  box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);\n}\n/**\n* Nestable Extras\n*/\n.nestable-lists {\n  display: block;\n  clear: both;\n  padding: 30px 0;\n  width: 100%;\n  border: 0;\n  border-top: 2px solid #ddd;\n  border-bottom: 2px solid #ddd;\n}\n#nestable-menu {\n  padding: 0;\n  margin: 10px 0 20px 0;\n}\n#nestable-output,\n#nestable2-output {\n  width: 100%;\n  font-size: 0.75em;\n  line-height: 1.333333em;\n  font-family: open sans, lucida grande, lucida sans unicode, helvetica, arial, sans-serif;\n  padding: 5px;\n  box-sizing: border-box;\n  -moz-box-sizing: border-box;\n}\n#nestable2 .dd-handle {\n  color: inherit;\n  border: 1px dashed #e7eaec;\n  background: #f3f3f4;\n  padding: 10px;\n}\n#nestable2 span.label {\n  margin-right: 10px;\n}\n#nestable-output,\n#nestable2-output {\n  font-size: 12px;\n  padding: 25px;\n  box-sizing: border-box;\n  -moz-box-sizing: border-box;\n}\n/* CodeMirror */\n.CodeMirror {\n  border: 1px solid #eee;\n  height: auto;\n}\n.CodeMirror-scroll {\n  overflow-y: hidden;\n  overflow-x: auto;\n}\n/* Google Maps */\n.google-map {\n  height: 300px;\n}\n/* Validation */\nlabel.error {\n  color: #cc5965;\n  display: inline-block;\n  margin-left: 5px;\n}\n.form-control.error {\n  border: 1px dotted #cc5965;\n}\n/* ngGrid */\n.gridStyle {\n  border: 1px solid #d4d4d4;\n  width: 100%;\n  height: 400px;\n}\n.gridStyle2 {\n  border: 1px solid #d4d4d4;\n  width: 500px;\n  height: 300px;\n}\n.ngH eaderCell {\n  border-right: none;\n  border-bottom: 1px solid #e7eaec;\n}\n.ngCell {\n  border-right: none;\n}\n.ngTopPanel {\n  background: #F5F5F6;\n}\n.ngRow.even {\n  background: #f9f9f9;\n}\n.ngRow.selected {\n  background: #EBF2F1;\n}\n.ngRow {\n  border-bottom: 1px solid #e7eaec;\n}\n.ngCell {\n  background-color: transparent;\n}\n.ngHeaderCell {\n  border-right: none;\n}\n/* Toastr custom style */\n#toast-container > div {\n  -moz-box-shadow: 0 0 3px #999;\n  -webkit-box-shadow: 0 0 3px #999;\n  box-shadow: 0 0 3px #999;\n  opacity: .9;\n  -ms-filter: alpha(opacity=90);\n  filter: alpha(opacity=90);\n}\n#toast-container > :hover {\n  -moz-box-shadow: 0 0 4px #999;\n  -webkit-box-shadow: 0 0 4px #999;\n  box-shadow: 0 0 4px #999;\n  opacity: 1;\n  -ms-filter: alpha(opacity=100);\n  filter: alpha(opacity=100);\n  cursor: pointer;\n}\n.toast {\n  background-color: #1ab394;\n  border-color: #e7eaec;\n}\n.toast-success {\n  background-color: #1ab394;\n}\n.toast-error {\n  background-color: #ed5565;\n}\n.toast-info {\n  background-color: #23c6c8;\n}\n.toast-warning {\n  background-color: #f8ac59;\n}\n.toast-top-full-width {\n  margin-top: 20px;\n}\n.toast-bottom-full-width {\n  margin-bottom: 20px;\n}\n.toast {\n  z-index: 3000;\n}\n.toast.toast-bootstrap {\n  background-color: white;\n}\n.toast.toast-bootstrap .toast-body {\n  background-color: #fbfbfb;\n  font-size: .775rem;\n}\n/* Notifie */\n.cg-notify-message.inspinia-notify {\n  background: #fff;\n  padding: 0;\n  box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2);\n  -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2);\n  -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2);\n  border: none;\n  margin-top: 30px;\n  color: inherit;\n}\n.inspinia-notify.alert-warning {\n  border-left: 6px solid #f8ac59;\n}\n.inspinia-notify.alert-success {\n  border-left: 6px solid #1c84c6;\n}\n.inspinia-notify.alert-danger {\n  border-left: 6px solid #ed5565;\n}\n.inspinia-notify.alert-info {\n  border-left: 6px solid #1ab394;\n}\n/* Image cropper style */\n.img-container,\n.img-preview {\n  overflow: hidden;\n  text-align: center;\n  width: 100%;\n}\n.img-preview-sm {\n  height: 130px;\n  width: 200px;\n}\n/* Forum styles  */\n.forum-post-container .media {\n  margin: 10px 10px 10px 10px;\n  padding: 20px 10px 20px 10px;\n  border-bottom: 1px solid #f1f1f1;\n}\n.forum-avatar {\n  float: left;\n  margin-right: 20px;\n  text-align: center;\n  width: 110px;\n}\n.forum-avatar .rounded-circle {\n  height: 48px;\n  width: 48px;\n}\n.author-info {\n  color: #676a6c;\n  font-size: 11px;\n  margin-top: 5px;\n  text-align: center;\n}\n.forum-post-info {\n  padding: 9px 12px 6px 12px;\n  background: #f9f9f9;\n  border: 1px solid #f1f1f1;\n}\n.media-body > .media {\n  background: #f9f9f9;\n  border-radius: 3px;\n  border: 1px solid #f1f1f1;\n}\n.forum-post-container .media-body .photos {\n  margin: 10px 0;\n}\n.forum-photo {\n  max-width: 140px;\n  border-radius: 3px;\n}\n.media-body > .media .forum-avatar {\n  width: 70px;\n  margin-right: 10px;\n}\n.media-body > .media .forum-avatar .rounded-circle {\n  height: 38px;\n  width: 38px;\n}\n.mid-icon {\n  font-size: 66px;\n}\n.forum-item {\n  margin: 10px 0;\n  padding: 10px 0 20px;\n  border-bottom: 1px solid #f1f1f1;\n}\n.views-number {\n  font-size: 24px;\n  line-height: 18px;\n  font-weight: 400;\n}\n.forum-container,\n.forum-post-container {\n  padding: 30px !important;\n}\n.forum-item small {\n  color: #999;\n}\n.forum-item .forum-sub-title {\n  color: #999;\n  margin-left: 50px;\n}\n.forum-title {\n  margin: 15px 0 15px 0;\n}\n.forum-info {\n  text-align: center;\n}\n.forum-desc {\n  color: #999;\n}\n.forum-icon {\n  float: left;\n  width: 30px;\n  margin-right: 20px;\n  text-align: center;\n}\na.forum-item-title {\n  color: inherit;\n  display: block;\n  font-size: 18px;\n  font-weight: 600;\n}\na.forum-item-title:hover {\n  color: inherit;\n}\n.forum-icon .fa {\n  font-size: 30px;\n  margin-top: 8px;\n  color: #9b9b9b;\n}\n.forum-item.active .fa {\n  color: #1ab394;\n}\n.forum-item.active a.forum-item-title {\n  color: #1ab394;\n}\n@media (max-width: 992px) {\n  .forum-info {\n    margin: 15px 0 10px 0;\n    /* Comment this is you want to show forum info in small devices */\n    display: none;\n  }\n  .forum-desc {\n    float: none !important;\n  }\n}\n/* New Timeline style */\n.vertical-container {\n  /* this class is used to give a max-width to the element it is applied to, and center it horizontally when it reaches that max-width */\n  width: 90%;\n  max-width: 1170px;\n  margin: 0 auto;\n}\n.vertical-container::after {\n  /* clearfix */\n  content: '';\n  display: table;\n  clear: both;\n}\n#vertical-timeline {\n  position: relative;\n  padding: 0;\n  margin-top: 2em;\n  margin-bottom: 2em;\n}\n#vertical-timeline::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 18px;\n  height: 100%;\n  width: 4px;\n  background: #f1f1f1;\n}\n.vertical-timeline-content .btn {\n  float: right;\n}\n#vertical-timeline.light-timeline:before {\n  background: #e7eaec;\n}\n.dark-timeline .vertical-timeline-content:before {\n  border-color: transparent #f5f5f5 transparent transparent;\n}\n.dark-timeline.center-orientation .vertical-timeline-content:before {\n  border-color: transparent transparent transparent #f5f5f5;\n}\n.dark-timeline .vertical-timeline-block:nth-child(2n) .vertical-timeline-content:before,\n.dark-timeline.center-orientation .vertical-timeline-block:nth-child(2n) .vertical-timeline-content:before {\n  border-color: transparent #f5f5f5 transparent transparent;\n}\n.dark-timeline .vertical-timeline-content,\n.dark-timeline.center-orientation .vertical-timeline-content {\n  background: #f5f5f5;\n}\n@media only screen and (min-width: 1170px) {\n  #vertical-timeline.center-orientation {\n    margin-top: 3em;\n    margin-bottom: 3em;\n  }\n  #vertical-timeline.center-orientation:before {\n    left: 50%;\n    margin-left: -2px;\n  }\n}\n@media only screen and (max-width: 1170px) {\n  .center-orientation.dark-timeline .vertical-timeline-content:before {\n    border-color: transparent #f5f5f5 transparent transparent;\n  }\n}\n.vertical-timeline-block {\n  position: relative;\n  margin: 2em 0;\n}\n.vertical-timeline-block:after {\n  content: \"\";\n  display: table;\n  clear: both;\n}\n.vertical-timeline-block:first-child {\n  margin-top: 0;\n}\n.vertical-timeline-block:last-child {\n  margin-bottom: 0;\n}\n@media only screen and (min-width: 1170px) {\n  .center-orientation .vertical-timeline-block {\n    margin: 4em 0;\n  }\n  .center-orientation .vertical-timeline-block:first-child {\n    margin-top: 0;\n  }\n  .center-orientation .vertical-timeline-block:last-child {\n    margin-bottom: 0;\n  }\n}\n.vertical-timeline-icon {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 40px;\n  height: 40px;\n  border-radius: 50%;\n  font-size: 16px;\n  border: 3px solid #f1f1f1;\n  text-align: center;\n}\n.vertical-timeline-icon i {\n  display: block;\n  width: 24px;\n  height: 24px;\n  position: relative;\n  left: 50%;\n  top: 50%;\n  margin-left: -12px;\n  margin-top: -9px;\n}\n@media only screen and (min-width: 1170px) {\n  .center-orientation .vertical-timeline-icon {\n    width: 50px;\n    height: 50px;\n    left: 50%;\n    margin-left: -25px;\n    -webkit-transform: translateZ(0);\n    -webkit-backface-visibility: hidden;\n    font-size: 19px;\n  }\n  .center-orientation .vertical-timeline-icon i {\n    margin-left: -12px;\n    margin-top: -10px;\n  }\n  .center-orientation .cssanimations .vertical-timeline-icon.is-hidden {\n    visibility: hidden;\n  }\n}\n.vertical-timeline-content {\n  position: relative;\n  margin-left: 60px;\n  background: white;\n  border-radius: 0.25em;\n  padding: 1em;\n}\n.vertical-timeline-content:after {\n  content: \"\";\n  display: table;\n  clear: both;\n}\n.vertical-timeline-content h2 {\n  font-weight: 400;\n  margin-top: 4px;\n}\n.vertical-timeline-content p {\n  margin: 1em 0;\n  line-height: 1.6;\n}\n.vertical-timeline-content .vertical-date {\n  float: left;\n  font-weight: 500;\n}\n.vertical-date small {\n  color: #1ab394;\n  font-weight: 400;\n}\n.vertical-timeline-content::before {\n  content: '';\n  position: absolute;\n  top: 16px;\n  right: 100%;\n  height: 0;\n  width: 0;\n  border: 7px solid transparent;\n  border-right: 7px solid white;\n}\n@media only screen and (min-width: 768px) {\n  .vertical-timeline-content h2 {\n    font-size: 18px;\n  }\n  .vertical-timeline-content p {\n    font-size: 13px;\n  }\n}\n@media only screen and (min-width: 1170px) {\n  .center-orientation .vertical-timeline-content {\n    margin-left: 0;\n    padding: 1.6em;\n    width: 45%;\n  }\n  .center-orientation .vertical-timeline-content::before {\n    top: 24px;\n    left: 100%;\n    border-color: transparent;\n    border-left-color: white;\n  }\n  .center-orientation .vertical-timeline-content .btn {\n    float: left;\n  }\n  .center-orientation .vertical-timeline-content .vertical-date {\n    position: absolute;\n    width: 100%;\n    left: 122%;\n    top: 2px;\n    font-size: 14px;\n  }\n  .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content {\n    float: right;\n  }\n  .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content::before {\n    top: 24px;\n    left: auto;\n    right: 100%;\n    border-color: transparent;\n    border-right-color: white;\n  }\n  .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content .btn {\n    float: right;\n  }\n  .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content .vertical-date {\n    left: auto;\n    right: 122%;\n    text-align: right;\n  }\n  .center-orientation .cssanimations .vertical-timeline-content.is-hidden {\n    visibility: hidden;\n  }\n}\n/* Tabs */\n.tabs-container .panel-body {\n  background: #fff;\n  border: 1px solid #e7eaec;\n  border-radius: 2px;\n  padding: 20px;\n  position: relative;\n}\n.tabs-container .nav-tabs > li.active > a,\n.tabs-container .nav-tabs > li.active > a:hover,\n.tabs-container .nav-tabs > li.active > a:focus {\n  border: 1px solid #e7eaec;\n  border-bottom-color: transparent;\n  background-color: #fff;\n}\n.tabs-container .nav-tabs > li {\n  float: left;\n  margin-bottom: -1px;\n}\n.tabs-container .tab-pane .panel-body {\n  border-top: none;\n}\n.tabs-container .nav-tabs > li.active > a,\n.tabs-container .nav-tabs > li.active > a:hover,\n.tabs-container .nav-tabs > li.active > a:focus {\n  border: 1px solid #e7eaec;\n  border-bottom-color: transparent;\n}\n.tabs-container .nav-tabs {\n  border-bottom: 1px solid #e7eaec;\n}\n.tabs-container .tab-pane .panel-body {\n  border-top: none;\n}\n.tabs-container .tabs-left .tab-pane .panel-body,\n.tabs-container .tabs-right .tab-pane .panel-body {\n  border-top: 1px solid #e7eaec;\n}\n.tabs-container .tabs-below > .nav-tabs,\n.tabs-container .tabs-right > .nav-tabs,\n.tabs-container .tabs-left > .nav-tabs {\n  border-bottom: 0;\n}\n.tabs-container .tabs-left .panel-body {\n  position: static;\n}\n.tabs-container .tabs-left > .nav-tabs,\n.tabs-container .tabs-right > .nav-tabs {\n  width: 20%;\n}\n.tabs-container .tabs-left .panel-body {\n  width: 80%;\n  margin-left: 20%;\n}\n.tabs-container .tabs-right .panel-body {\n  width: 80%;\n  margin-right: 20%;\n}\n.tabs-container .tab-content > .tab-pane,\n.tabs-container .pill-content > .pill-pane {\n  display: none;\n}\n.tabs-container .tab-content > .active,\n.tabs-container .pill-content > .active {\n  display: block;\n}\n.tabs-container .tabs-below > .nav-tabs {\n  border-top: 1px solid #e7eaec;\n}\n.tabs-container .tabs-below > .nav-tabs > li {\n  margin-top: -1px;\n  margin-bottom: 0;\n}\n.tabs-container .tabs-below > .nav-tabs > li > a {\n  -webkit-border-radius: 0 0 4px 4px;\n  -moz-border-radius: 0 0 4px 4px;\n  border-radius: 0 0 4px 4px;\n}\n.tabs-container .tabs-below > .nav-tabs > li > a:hover,\n.tabs-container .tabs-below > .nav-tabs > li > a:focus {\n  border-top-color: #e7eaec;\n  border-bottom-color: transparent;\n}\n.tabs-container .tabs-left > .nav-tabs > li,\n.tabs-container .tabs-right > .nav-tabs > li {\n  float: none;\n  word-break: break-word;\n  width: 100%;\n}\n.tabs-container .tabs-left > .nav-tabs > li > a,\n.tabs-container .tabs-right > .nav-tabs > li > a {\n  margin-right: 0;\n  margin-bottom: 3px;\n}\n.tabs-container .tabs-left > .nav-tabs {\n  float: left;\n  margin-right: 19px;\n}\n.tabs-container .tabs-left > .nav-tabs > li > a {\n  margin-right: -1px;\n  -webkit-border-radius: 4px 0 0 4px;\n  -moz-border-radius: 4px 0 0 4px;\n  border-radius: 4px 0 0 4px;\n}\n.tabs-container .tabs-left > .nav-tabs a.active,\n.tabs-container .tabs-left > .nav-tabs a.active:hover,\n.tabs-container .tabs-left > .nav-tabs a.active:focus {\n  border-color: #e7eaec transparent #e7eaec #e7eaec;\n}\n.tabs-container .tabs-right > .nav-tabs {\n  float: right;\n  margin-left: 19px;\n}\n.tabs-container .tabs-right > .nav-tabs > li > a {\n  margin-left: -1px;\n  -webkit-border-radius: 0 4px 4px 0;\n  -moz-border-radius: 0 4px 4px 0;\n  border-radius: 0 4px 4px 0;\n}\n.tabs-container .tabs-right > .nav-tabs a.active,\n.tabs-container .tabs-right > .nav-tabs a.active:hover,\n.tabs-container .tabs-right > .nav-tabs a.active:focus {\n  border-color: #e7eaec #e7eaec #e7eaec transparent;\n  z-index: 1;\n}\n.tabs-container .tabs-right > .nav-tabs li {\n  z-index: 1;\n}\n.nav-tabs .nav-link:not(.active):focus,\n.nav-tabs .nav-link:not(.active):hover {\n  border-color: transparent;\n}\n@media (max-width: 767px) {\n  .tabs-container .nav-tabs > li {\n    float: none !important;\n  }\n  .tabs-container .nav-tabs > li.active > a {\n    border-bottom: 1px solid #e7eaec !important;\n    margin: 0;\n  }\n}\n/* jsvectormap */\n.jvectormap-container {\n  width: 100%;\n  height: 100%;\n  position: relative;\n  overflow: hidden;\n}\n.jvectormap-tip {\n  position: absolute;\n  display: none;\n  border: solid 1px #CDCDCD;\n  border-radius: 3px;\n  background: #292929;\n  color: white;\n  font-family: sans-serif, Verdana;\n  font-size: smaller;\n  padding: 5px;\n}\n.jvectormap-zoomin,\n.jvectormap-zoomout,\n.jvectormap-goback {\n  position: absolute;\n  left: 10px;\n  border-radius: 3px;\n  background: #1ab394;\n  padding: 3px;\n  color: white;\n  cursor: pointer;\n  line-height: 10px;\n  text-align: center;\n  box-sizing: content-box;\n}\n.jvectormap-zoomin,\n.jvectormap-zoomout {\n  width: 10px;\n  height: 10px;\n}\n.jvectormap-zoomin {\n  top: 10px;\n}\n.jvectormap-zoomout {\n  top: 30px;\n}\n.jvectormap-goback {\n  bottom: 10px;\n  z-index: 1000;\n  padding: 6px;\n}\n.jvectormap-spinner {\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  background: center no-repeat url(data:image/gif;base64,R0lGODlhIAAgAPMAAP///wAAAMbGxoSEhLa2tpqamjY2NlZWVtjY2OTk5Ly8vB4eHgQEBAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA==);\n}\n.jvectormap-legend-title {\n  font-weight: bold;\n  font-size: 14px;\n  text-align: center;\n}\n.jvectormap-legend-cnt {\n  position: absolute;\n}\n.jvectormap-legend-cnt-h {\n  bottom: 0;\n  right: 0;\n}\n.jvectormap-legend-cnt-v {\n  top: 0;\n  right: 0;\n}\n.jvectormap-legend {\n  background: black;\n  color: white;\n  border-radius: 3px;\n}\n.jvectormap-legend-cnt-h .jvectormap-legend {\n  float: left;\n  margin: 0 10px 10px 0;\n  padding: 3px 3px 1px 3px;\n}\n.jvectormap-legend-cnt-h .jvectormap-legend .jvectormap-legend-tick {\n  float: left;\n}\n.jvectormap-legend-cnt-v .jvectormap-legend {\n  margin: 10px 10px 0 0;\n  padding: 3px;\n}\n.jvectormap-legend-cnt-h .jvectormap-legend-tick {\n  width: 40px;\n}\n.jvectormap-legend-cnt-h .jvectormap-legend-tick-sample {\n  height: 15px;\n}\n.jvectormap-legend-cnt-v .jvectormap-legend-tick-sample {\n  height: 20px;\n  width: 20px;\n  display: inline-block;\n  vertical-align: middle;\n}\n.jvectormap-legend-tick-text {\n  font-size: 12px;\n}\n.jvectormap-legend-cnt-h .jvectormap-legend-tick-text {\n  text-align: center;\n}\n.jvectormap-legend-cnt-v .jvectormap-legend-tick-text {\n  display: inline-block;\n  vertical-align: middle;\n  line-height: 20px;\n  padding-left: 3px;\n}\n/*Slick Carousel */\n.slick-prev:before,\n.slick-next:before {\n  color: #1ab394 !important;\n}\n/* Payments */\n.payment-card {\n  background: #ffffff;\n  padding: 20px;\n  margin-bottom: 25px;\n  border: 1px solid #e7eaec;\n}\n.payment-icon-big {\n  font-size: 60px;\n  color: #d1dade;\n}\n.payments-method.panel-group .panel + .panel {\n  margin-top: -1px;\n}\n.payments-method .panel-heading {\n  padding: 15px;\n  background-color: #f3f3f4;\n}\n.payments-method .panel-default {\n  border: 1px solid #e7eaec;\n}\n.payments-method .panel {\n  border-radius: 0;\n}\n.payments-method .panel-heading h5 {\n  margin-bottom: 5px;\n}\n.payments-method .panel-heading i {\n  font-size: 26px;\n}\n/* Select2 custom styles */\n.select2-container--bootstrap4 .select2-results__option--highlighted,\n.select2-container--bootstrap4 .select2-results__option--highlighted.select2-results__option[aria-selected=true] {\n  background-color: #1ab394;\n}\n.select2-container--bootstrap4 .select2-selection,\n.select2-container--bootstrap4 .select2-dropdown.select2-dropdown--above,\n.select2-container--bootstrap4 .select2-dropdown {\n  border-color: #e7eaec;\n}\n.select2-container :focus {\n  outline: none;\n}\n.select2-container--bootstrap4.select2-container--focus .select2-selection {\n  box-shadow: none;\n  border-color: #1ab394;\n}\n.select2-container--bootstrap4 .select2-selection__clear {\n  margin-top: 0.9em;\n}\n/* Tour */\n.tour-tour .btn.btn-default {\n  background-color: #ffffff;\n  border: 1px solid #d2d2d2;\n  color: inherit;\n}\n.tour-step-backdrop {\n  z-index: 2101;\n}\n.tour-backdrop {\n  z-index: 2100;\n  opacity: .7;\n}\n.popover[class*=tour-] {\n  z-index: 2100;\n}\n.popover-header {\n  margin-top: 0;\n}\nbody.tour-open .animated {\n  animation-fill-mode: initial;\n}\n.tour-tour .btn.btn-secondary {\n  background-color: #ffffff;\n  border: 1px solid #d2d2d2;\n  color: inherit;\n}\n/* Resizable */\n.resizable-panels .ibox {\n  clear: none;\n  margin: 10px;\n  float: left;\n  overflow: hidden;\n  min-height: 150px;\n  min-width: 150px;\n}\n.resizable-panels .ibox .ibox-content {\n  height: calc(100% - 49px);\n}\n.ui-resizable-helper {\n  background: rgba(211, 211, 211, 0.4);\n}\n/* Wizard step fix */\n.wizard > .content > .body {\n  position: relative;\n}\n/* PDF js style */\n.pdf-toolbar {\n  max-width: 600px;\n  margin: 0 auto;\n}\n.pdf-toolbar .input-group {\n  width: 100px;\n}\n/* Dropzone */\n.dropzone {\n  min-height: 140px;\n  border: 1px dashed #1ab394;\n  background: white;\n  padding: 20px 20px;\n}\n.dropzone .dz-message {\n  font-size: 16px;\n}\n/* Activity stream */\n.stream {\n  position: relative;\n  padding: 10px 0;\n}\n.stream:first-child .stream-badge:before {\n  top: 10px;\n}\n.stream:last-child .stream-badge:before {\n  height: 30px;\n}\n.stream .stream-badge {\n  width: 50px;\n}\n.stream .stream-badge i {\n  border: 1px solid #e7eaec;\n  border-radius: 50%;\n  padding: 6px;\n  color: #808486;\n  position: absolute;\n  background-color: #ffffff;\n  left: 8px;\n}\n.stream .stream-badge i.fa-circle {\n  color: #ced0d1;\n}\n.stream .stream-badge i.bg-success {\n  color: #ffffff;\n  background-color: #1c84c6;\n  border-color: #1c84c6;\n}\n.stream .stream-badge i.bg-primary {\n  color: #ffffff;\n  background-color: #1ab394;\n  border-color: #1ab394;\n}\n.stream .stream-badge i.bg-warning {\n  color: #ffffff;\n  background-color: #f8ac59;\n  border-color: #f8ac59;\n}\n.stream .stream-badge i.bg-info {\n  color: #ffffff;\n  background-color: #23c6c8;\n  border-color: #23c6c8;\n}\n.stream .stream-badge i.bg-danger {\n  color: #ffffff;\n  background-color: #ed5565;\n  border-color: #ed5565;\n}\n.stream .stream-badge:before {\n  content: '';\n  width: 1px;\n  background-color: #e7eaec;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 20px;\n}\n.stream .stream-info {\n  font-size: 12px;\n  margin-bottom: 5px;\n}\n.stream .stream-info img {\n  border-radius: 50%;\n  width: 18px;\n  height: 18px;\n  margin-right: 2px;\n  margin-top: -4px;\n}\n.stream .stream-info .date {\n  color: #9a9d9f;\n  font-size: 80%;\n}\n.stream .stream-panel {\n  margin-left: 55px;\n}\n.stream-small {\n  margin: 10px 0;\n}\n.stream-small .label {\n  padding: 2px 6px;\n  margin-right: 2px;\n}\n/* Touch Spin */\n.bootstrap-touchspin-postfix.input-group-addon {\n  padding: inherit;\n}\n.bootstrap-touchspin-postfix .input-group-text {\n  background-color: inherit;\n  line-height: 1;\n  border: none;\n}\n/* Code */\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: #eff2f3;\n  border: 1px solid #d1dade;\n  border-radius: 2px;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\n.sidebar-panel {\n  width: 220px;\n  background: #ebebed;\n  padding: 10px 20px;\n  position: absolute;\n  right: 0;\n  height: calc(100% - 62px);\n}\n.sidebar-panel .feed-element img.rounded-circle {\n  width: 32px;\n  height: 32px;\n}\n.sidebar-panel .feed-element,\n.media-body,\n.sidebar-panel p {\n  font-size: 12px;\n}\n.sidebar-panel .feed-element {\n  margin-top: 20px;\n  padding-bottom: 0;\n}\n.sidebar-panel .list-group {\n  margin-bottom: 10px;\n}\n.sidebar-panel .list-group .list-group-item {\n  padding: 5px 0;\n  font-size: 12px;\n  border: 0;\n}\n.sidebar-content .wrapper,\n.wrapper.sidebar-content {\n  padding-right: 230px !important;\n}\n.body-small .sidebar-content .wrapper,\n.body-small .wrapper.sidebar-content {\n  padding-right: 20px !important;\n}\n#right-sidebar {\n  background-color: #fff;\n  border-left: 1px solid #e7eaec;\n  border-top: 1px solid #e7eaec;\n  overflow: hidden;\n  position: fixed;\n  top: 60px;\n  width: 260px !important;\n  z-index: 1009;\n  bottom: 0;\n  right: -260px;\n}\n#right-sidebar.sidebar-open {\n  right: 0;\n}\n#right-sidebar.sidebar-open.sidebar-top {\n  top: 0;\n  border-top: none;\n}\n.sidebar-container ul.nav-tabs {\n  border: none;\n}\n.sidebar-container ul.nav-tabs.navs-4 li {\n  width: 25%;\n}\n.sidebar-container ul.nav-tabs.navs-3 li {\n  width: 33.3333%;\n}\n.sidebar-container ul.nav-tabs.navs-2 li {\n  width: 50%;\n}\n.sidebar-container ul.nav-tabs li {\n  border: none;\n}\n.sidebar-container ul.nav-tabs li a {\n  border: none;\n  padding: 12px 10px;\n  margin: 0;\n  border-radius: 0;\n  background: #2f4050;\n  color: #fff;\n  text-align: center;\n  border-right: 1px solid #334556;\n}\n.sidebar-container ul.nav-tabs li.active a {\n  border: none;\n  background: #f9f9f9;\n  color: #676a6c;\n  font-weight: bold;\n}\n.sidebar-container .nav-tabs > li.active > a:hover,\n.sidebar-container .nav-tabs > li.active > a:focus {\n  border: none;\n}\n.sidebar-container ul.sidebar-list {\n  margin: 0;\n  padding: 0;\n}\n.sidebar-container ul.sidebar-list li {\n  border-bottom: 1px solid #e7eaec;\n  padding: 15px 20px;\n  list-style: none;\n  font-size: 12px;\n}\n.sidebar-container .sidebar-message:nth-child(2n+2) {\n  background: #f9f9f9;\n}\n.sidebar-container ul.sidebar-list li a {\n  text-decoration: none;\n  color: inherit;\n}\n.sidebar-container .sidebar-content {\n  padding: 15px 20px;\n  font-size: 12px;\n}\n.sidebar-container .sidebar-title {\n  background: #f9f9f9;\n  padding: 20px;\n  border-bottom: 1px solid #e7eaec;\n}\n.sidebar-container .sidebar-title h3 {\n  margin-bottom: 3px;\n  padding-left: 2px;\n}\n.sidebar-container .tab-content h4 {\n  margin-bottom: 5px;\n}\n.sidebar-container .sidebar-message > a > .float-left {\n  margin-right: 10px;\n}\n.sidebar-container .sidebar-message > a {\n  text-decoration: none;\n  color: inherit;\n}\n.sidebar-container .sidebar-message {\n  padding: 15px 20px;\n}\n.sidebar-container .sidebar-message .media-body {\n  display: block;\n  width: auto;\n}\n.sidebar-container .sidebar-message .message-avatar {\n  height: 38px;\n  width: 38px;\n  border-radius: 50%;\n}\n.sidebar-container .setings-item {\n  padding: 15px 20px;\n  border-bottom: 1px solid #e7eaec;\n}\nbody {\n  font-family: \"open sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  background-color: #2f4050;\n  font-size: 13px;\n  color: #676a6c;\n  overflow-x: hidden;\n}\nhtml,\nbody {\n  height: 100%;\n}\nbody.full-height-layout #wrapper,\nbody.full-height-layout #page-wrapper {\n  height: 100%;\n}\n#page-wrapper {\n  min-height: 100vh;\n}\nbody.boxed-layout {\n  background: url('patterns/shattered.png');\n}\nbody.boxed-layout #wrapper {\n  background-color: #2f4050;\n  max-width: 1200px;\n  margin: 0 auto;\n  -webkit-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);\n  -moz-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);\n  box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);\n}\n.top-navigation.boxed-layout #wrapper,\n.boxed-layout #wrapper.top-navigation {\n  max-width: 1300px !important;\n}\n.block {\n  display: block;\n}\n.clear {\n  display: block;\n  overflow: hidden;\n}\na {\n  cursor: pointer;\n}\na:hover,\na:focus {\n  text-decoration: none;\n}\n.border-bottom {\n  border-bottom: 1px solid #e7eaec !important;\n}\n.font-bold {\n  font-weight: 600;\n}\n.font-normal {\n  font-weight: 400;\n}\n.text-uppercase {\n  text-transform: uppercase;\n}\n.font-italic {\n  font-style: italic;\n}\n.b-r {\n  border-right: 1px solid #e7eaec;\n}\n.hr-line-dashed {\n  border-top: 1px dashed #e7eaec;\n  color: #ffffff;\n  background-color: #ffffff;\n  height: 1px;\n  margin: 20px 0;\n}\n.hr-line-solid {\n  border-bottom: 1px solid #e7eaec;\n  background-color: rgba(0, 0, 0, 0);\n  border-style: solid !important;\n  margin-top: 15px;\n  margin-bottom: 15px;\n}\nvideo {\n  width: 100% !important;\n  height: auto !important;\n}\n/* GALLERY */\n.gallery > .row > div {\n  margin-bottom: 15px;\n}\n.fancybox img {\n  margin-bottom: 5px;\n  /* Only for demo */\n  width: 24%;\n}\n/* Summernote text editor  */\n.note-editor {\n  height: auto !important;\n}\n.note-editor.fullscreen {\n  z-index: 2050;\n}\n.note-editor.note-frame.fullscreen {\n  z-index: 2020;\n}\n.note-editor.note-frame .note-editing-area .note-editable {\n  color: #676a6c;\n  padding: 15px;\n}\n.note-editor.note-frame {\n  border: none;\n}\n.note-editor.panel {\n  margin-bottom: 0;\n}\n/* MODAL */\n.modal-content {\n  background-clip: padding-box;\n  background-color: #FFFFFF;\n  border: 1px solid rgba(0, 0, 0, 0);\n  border-radius: 4px;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n  outline: 0 none;\n  position: relative;\n}\n.modal-dialog {\n  z-index: 2200;\n}\n.modal-body {\n  padding: 20px 30px 30px 30px;\n}\n.inmodal .modal-body {\n  background: #f8fafb;\n}\n.inmodal .modal-header {\n  padding: 30px 15px;\n  text-align: center;\n  display: block;\n}\n.animated.modal.fade .modal-dialog {\n  -webkit-transform: none;\n  -ms-transform: none;\n  -o-transform: none;\n  transform: none;\n}\n.inmodal .modal-title {\n  font-size: 26px;\n}\n.inmodal .modal-icon {\n  font-size: 84px;\n  color: #e2e3e3;\n}\n.modal-footer {\n  margin-top: 0;\n}\n/* WRAPPERS */\n#wrapper {\n  width: 100%;\n  overflow-x: hidden;\n  display: -ms-flex;\n  display: -webkit-flex;\n  display: flex;\n}\n.wrapper {\n  padding: 0 20px;\n}\n.wrapper-content {\n  padding: 20px 10px 40px;\n}\n#page-wrapper {\n  padding: 0 15px;\n  position: relative !important;\n  flex-shrink: 1;\n  width: calc(100% - 220px);\n}\n@media (min-width: 768px) {\n  #page-wrapper {\n    position: inherit;\n  }\n}\n.title-action {\n  text-align: right;\n  padding-top: 30px;\n}\n.ibox-content h1,\n.ibox-content h2,\n.ibox-content h3,\n.ibox-content h4,\n.ibox-content h5,\n.ibox-title h1,\n.ibox-title h2,\n.ibox-title h3,\n.ibox-title h4,\n.ibox-title h5 {\n  margin-top: 5px;\n}\nul.unstyled,\nol.unstyled {\n  list-style: none outside none;\n  margin-left: 0;\n}\n.big-icon {\n  font-size: 160px !important;\n  color: #e5e6e7;\n}\n/* FOOTER */\n.footer {\n  background: none repeat scroll 0 0 white;\n  border-top: 1px solid #e7eaec;\n  bottom: 0;\n  left: 0;\n  padding: 10px 20px;\n  position: absolute;\n  right: 0;\n}\n.footer.fixed_full {\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  z-index: 1000;\n  padding: 10px 20px;\n  background: white;\n  border-top: 1px solid #e7eaec;\n}\n.footer.fixed {\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  z-index: 1000;\n  padding: 10px 20px;\n  background: white;\n  border-top: 1px solid #e7eaec;\n  margin-left: 220px;\n}\nbody.mini-navbar .footer.fixed,\nbody.body-small.mini-navbar .footer.fixed {\n  margin: 0 0 0 70px;\n}\nbody.mini-navbar.fixed-sidebar .footer.fixed {\n  margin: 0;\n}\nbody.mini-navbar.canvas-menu .footer.fixed,\nbody.canvas-menu .footer.fixed {\n  margin: 0 !important;\n}\nbody.fixed-sidebar.body-small.mini-navbar .footer.fixed {\n  margin: 0 0 0 220px;\n}\nbody.body-small .footer.fixed {\n  margin-left: 0;\n}\n/* PANELS */\n.panel-title > .small,\n.panel-title > .small > a,\n.panel-title > a,\n.panel-title > small,\n.panel-title > small > a {\n  color: inherit;\n}\n.page-heading {\n  border-top: 0;\n  padding: 0 10px 20px 10px;\n}\n.panel-heading h1,\n.panel-heading h2 {\n  margin-bottom: 5px;\n}\n.panel-body {\n  padding: 15px;\n}\n/* Bootstrap 3.3.x panels */\n.panel {\n  margin-bottom: 20px;\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.panel-heading {\n  color: white;\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-footer {\n  padding: 10px 15px;\n  border-top: 1px solid #e7eaec;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel-default > .panel-heading {\n  color: #333;\n  background-color: #f5f5f5;\n  border-color: #e7eaec;\n}\n.panel-default {\n  border-color: #e7eaec;\n}\n.panel-group .panel + .panel {\n  margin-top: 5px;\n}\n.panel-group .panel {\n  margin-bottom: 0;\n  border-radius: 4px;\n}\n/* TABLES */\n.table > caption + thead > tr:first-child > td,\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > td,\n.table > thead:first-child > tr:first-child > th {\n  border-top: 0;\n}\n.table-bordered {\n  border: 1px solid #EBEBEB;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n  background-color: #F5F5F6;\n  border-bottom-width: 1px;\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 #e7e7e7;\n}\n.table > thead > tr > th {\n  border-bottom: 1px solid #DDDDDD;\n  vertical-align: bottom;\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  border-top: 1px solid #e7eaec;\n  line-height: 1.42857;\n  padding: 8px;\n  vertical-align: top;\n}\n/* PANELS */\n.panel.blank-panel {\n  background: none;\n  margin: 0;\n}\n.blank-panel .panel-heading {\n  padding-bottom: 0;\n}\n.nav-tabs > li > a {\n  color: #A7B1C2;\n  font-weight: 600;\n  padding: 10px 20px 10px 25px;\n}\n.nav-tabs > li > a:hover,\n.nav-tabs > li > a:focus {\n  color: #676a6c;\n}\n.ui-tab .tab-content {\n  padding: 20px 0;\n}\n/* GLOBAL  */\n.no-padding {\n  padding: 0 !important;\n}\n.no-borders {\n  border: none !important;\n}\n.no-margins {\n  margin: 0 !important;\n}\n.no-top-border {\n  border-top: 0 !important;\n}\n.ibox-content.text-box {\n  padding-bottom: 0;\n  padding-top: 15px;\n}\n.border-left-right {\n  border-left: 1px solid #e7eaec;\n  border-right: 1px solid #e7eaec;\n}\n.border-top-bottom {\n  border-top: 1px solid #e7eaec;\n  border-bottom: 1px solid #e7eaec;\n}\n.border-left {\n  border-left: 1px solid #e7eaec;\n}\n.border-right {\n  border-right: 1px solid #e7eaec;\n}\n.border-top {\n  border-top: 1px solid #e7eaec;\n}\n.border-bottom {\n  border-bottom: 1px solid #e7eaec;\n}\n.border-size-sm {\n  border-width: 3px;\n}\n.border-size-md {\n  border-width: 6px;\n}\n.border-size-lg {\n  border-width: 9px;\n}\n.border-size-xl {\n  border-width: 12px;\n}\n.full-width {\n  width: 100% !important;\n}\n.link-block {\n  font-size: 12px;\n  padding: 10px;\n}\n.nav.navbar-top-links .link-block a {\n  font-size: 12px;\n}\n.navbar-top-links {\n  text-align: right;\n}\n.link-block a {\n  font-size: 10px;\n  color: inherit;\n}\nbody.mini-navbar .branding {\n  display: none;\n}\nimg.circle-border {\n  border: 6px solid #FFFFFF;\n  border-radius: 50%;\n}\n.branding {\n  float: left;\n  color: #FFFFFF;\n  font-size: 18px;\n  font-weight: 600;\n  padding: 17px 20px;\n  text-align: center;\n  background-color: #1ab394;\n}\n.login-panel {\n  margin-top: 25%;\n}\n.icons-box h3 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.icons-box .infont a i {\n  font-size: 25px;\n  display: block;\n  color: #676a6c;\n}\n.icons-box .infont a {\n  color: #a6a8a9;\n}\n.icons-box .infont a {\n  padding: 10px;\n  margin: 1px;\n  display: block;\n}\n.ui-draggable .ibox-title {\n  cursor: move;\n}\n.breadcrumb {\n  background-color: #ffffff;\n  padding: 0;\n  margin-bottom: 0;\n}\n.breadcrumb > li a {\n  color: inherit;\n}\n.breadcrumb > .active {\n  color: inherit;\n}\ncode {\n  background-color: #F9F2F4;\n  border-radius: 4px;\n  color: #ca4440;\n  font-size: 90%;\n  padding: 2px 4px;\n  white-space: nowrap;\n}\n.ibox {\n  clear: both;\n  margin-bottom: 25px;\n  margin-top: 0;\n  padding: 0;\n}\n.ibox.collapsed .ibox-content {\n  display: none;\n}\n.ibox.collapsed .fa.fa-chevron-up:before {\n  content: \"\\f078\";\n}\n.ibox.collapsed .fa.fa-chevron-down:before {\n  content: \"\\f077\";\n}\n.ibox:after,\n.ibox:before {\n  display: table;\n}\n.ibox-title {\n  background-color: #ffffff;\n  border-color: #e7eaec;\n  border-image: none;\n  border-style: solid solid none;\n  border-width: 1px;\n  color: inherit;\n  margin-bottom: 0;\n  padding: 15px 90px 8px 15px;\n  min-height: 48px;\n  position: relative;\n  clear: both;\n  -webkit-border-radius: 3px 3px 0 0;\n  -moz-border-radius: 3px 3px 0 0;\n  border-radius: 2px 2px 0 0;\n}\n.ibox-content {\n  background-color: #ffffff;\n  color: inherit;\n  padding: 15px 20px 20px 20px;\n  border-color: #e7eaec;\n  border-image: none;\n  border-style: solid;\n  border-width: 1px;\n}\n.ibox-footer {\n  color: inherit;\n  border-top: 1px solid #e7eaec;\n  font-size: 90%;\n  background: #ffffff;\n  padding: 10px 15px;\n}\ntable.table-mail tr td {\n  padding: 12px;\n}\n.table-mail .check-mail {\n  padding-left: 20px;\n}\n.table-mail .mail-date {\n  padding-right: 20px;\n}\n.star-mail,\n.check-mail {\n  width: 40px;\n}\n.unread td a,\n.unread td {\n  font-weight: 600;\n  color: inherit;\n}\n.read td a,\n.read td {\n  font-weight: normal;\n  color: inherit;\n}\n.unread td {\n  background-color: #f9f8f8;\n}\n.ibox-content {\n  clear: both;\n}\n.ibox-heading {\n  background-color: #f3f6fb;\n  border-bottom: none;\n}\n.ibox-heading h3 {\n  font-weight: 200;\n  font-size: 24px;\n}\n.ibox-title h5 {\n  display: inline-block;\n  font-size: 14px;\n  margin: 0 0 7px;\n  padding: 0;\n  text-overflow: ellipsis;\n  float: none;\n}\n.ibox-title .label {\n  margin-left: 4px;\n}\n.ibox-title .pull-right {\n  position: absolute;\n  right: 15px;\n  top: 15px;\n}\n.ibox-tools {\n  display: block;\n  float: none;\n  margin-top: 0;\n  position: absolute;\n  top: 15px;\n  right: 15px;\n  padding: 0;\n  text-align: right;\n}\n.ibox-tools a {\n  cursor: pointer;\n  margin-left: 5px;\n  color: #c4c4c4 !important;\n}\n.ibox-tools a.btn-primary {\n  color: #fff !important;\n}\n.ibox-tools .dropdown-menu > li > a {\n  padding: 4px 10px;\n  font-size: 12px;\n  color: #676a6c !important;\n}\n.ibox .ibox-tools.open > .dropdown-menu {\n  left: auto;\n  right: 0;\n}\n.ibox-tools .dropdown-toggle::after {\n  display: none;\n}\n.dropdown-item {\n  width: auto;\n}\n.dropdown-item.active,\n.dropdown-item:active {\n  background-color: inherit;\n  color: inherit;\n}\n/* BACKGROUNDS */\n.gray-bg,\n.bg-muted {\n  background-color: #f3f3f4;\n}\n.white-bg {\n  background-color: #ffffff;\n}\n.blue-bg,\n.bg-success {\n  background-color: #1c84c6 !important;\n  color: #ffffff;\n}\n.navy-bg,\n.bg-primary {\n  background-color: #1ab394 !important;\n  color: #ffffff;\n}\n.lazur-bg,\n.bg-info {\n  background-color: #23c6c8 !important;\n  color: #ffffff;\n}\n.yellow-bg,\n.bg-warning {\n  background-color: #f8ac59 !important;\n  color: #ffffff;\n}\n.red-bg,\n.bg-danger {\n  background-color: #ed5565 !important;\n  color: #ffffff;\n}\n.black-bg {\n  background-color: #262626;\n}\n.panel-primary {\n  border-color: #1ab394;\n}\n.panel-primary > .panel-heading {\n  background-color: #1ab394;\n  border-color: #1ab394;\n}\n.panel-success {\n  border-color: #1c84c6;\n}\n.panel-success > .panel-heading {\n  background-color: #1c84c6;\n  border-color: #1c84c6;\n  color: #ffffff;\n}\n.panel-info {\n  border-color: #23c6c8;\n}\n.panel-info > .panel-heading {\n  background-color: #23c6c8;\n  border-color: #23c6c8;\n  color: #ffffff;\n}\n.panel-warning {\n  border-color: #f8ac59;\n}\n.panel-warning > .panel-heading {\n  background-color: #f8ac59;\n  border-color: #f8ac59;\n  color: #ffffff;\n}\n.panel-danger {\n  border-color: #ed5565;\n}\n.panel-danger > .panel-heading {\n  background-color: #ed5565;\n  border-color: #ed5565;\n  color: #ffffff;\n}\n.progress-bar {\n  background-color: #1ab394;\n}\n.progress-small,\n.progress-small .progress-bar {\n  height: 10px;\n}\n.progress-small,\n.progress-mini {\n  margin-top: 5px;\n}\n.progress-mini,\n.progress-mini .progress-bar {\n  height: 5px;\n  margin-bottom: 0;\n}\n.progress-bar-navy-light {\n  background-color: #3dc7ab;\n}\n.progress-bar-success {\n  background-color: #1c84c6;\n}\n.progress-bar-info {\n  background-color: #23c6c8;\n}\n.progress-bar-warning {\n  background-color: #f8ac59;\n}\n.progress-bar-danger {\n  background-color: #ed5565;\n}\n.panel-title {\n  font-size: inherit;\n}\n.jumbotron {\n  border-radius: 6px;\n  padding: 40px;\n}\n.jumbotron h1 {\n  margin-top: 0;\n}\n/* COLORS */\n.text-navy {\n  color: #1ab394 !important;\n}\n.text-primary {\n  color: inherit !important;\n}\n.text-success {\n  color: #1c84c6 !important;\n}\n.text-info {\n  color: #23c6c8 !important;\n}\n.text-warning {\n  color: #f8ac59 !important;\n}\n.text-danger {\n  color: #ed5565 !important;\n}\n.text-muted {\n  color: #888888 !important;\n}\n.text-white {\n  color: #ffffff;\n}\n.simple_tag {\n  background-color: #f3f3f4;\n  border: 1px solid #e7eaec;\n  border-radius: 2px;\n  color: inherit;\n  font-size: 10px;\n  margin-right: 5px;\n  margin-top: 5px;\n  padding: 5px 12px;\n  display: inline-block;\n}\n.img-shadow {\n  -webkit-box-shadow: 0 0 3px 0 #919191;\n  -moz-box-shadow: 0 0 3px 0 #919191;\n  box-shadow: 0 0 3px 0 #919191;\n}\n/* For handle diferent bg color in AngularJS version */\n.dashboards\\.dashboard_2 nav.navbar,\n.dashboards\\.dashboard_3 nav.navbar,\n.mailbox\\.inbox nav.navbar,\n.mailbox\\.email_view nav.navbar,\n.mailbox\\.email_compose nav.navbar,\n.dashboards\\.dashboard_4_1 nav.navbar,\n.metrics nav.navbar,\n.metrics\\.index nav.navbar,\n.dashboards\\.dashboard_5 nav.navbar {\n  background: #fff;\n}\n/* For handle diferent bg color in MVC version */\n.Dashboard_2 .navbar.navbar-static-top,\n.Dashboard_3 .navbar.navbar-static-top,\n.Dashboard_4_1 .navbar.navbar-static-top,\n.ComposeEmail .navbar.navbar-static-top,\n.EmailView .navbar.navbar-static-top,\n.Inbox .navbar.navbar-static-top,\n.Metrics .navbar.navbar-static-top,\n.Dashboard_5 .navbar.navbar-static-top {\n  background: #fff;\n}\na.close-canvas-menu {\n  position: absolute;\n  top: 10px;\n  right: 15px;\n  z-index: 1011;\n  color: #a7b1c2;\n}\na.close-canvas-menu:hover {\n  color: #fff;\n}\n.close-canvas-menu {\n  display: none;\n}\n.canvas-menu .close-canvas-menu {\n  display: block;\n}\n.light-navbar .navbar.navbar-static-top {\n  background-color: #ffffff;\n}\n/* FULL HEIGHT */\n.full-height {\n  height: 100%;\n}\n.fh-breadcrumb {\n  height: calc(100% - 196px);\n  margin: 0 -15px;\n  position: relative;\n}\n.fh-no-breadcrumb {\n  height: calc(100% - 99px);\n  margin: 0 -15px;\n  position: relative;\n}\n.fh-column {\n  background: #fff;\n  height: 100%;\n  width: 240px;\n  float: left;\n}\n.modal-backdrop {\n  z-index: 2040 !important;\n}\n.modal {\n  z-index: 2050 !important;\n}\n.spiner-example {\n  height: 200px;\n  padding-top: 70px;\n}\nlegend {\n  font-size: 1rem;\n}\n/* MARGINS & PADDINGS */\n.p-xxs {\n  padding: 5px;\n}\n.p-xs {\n  padding: 10px;\n}\n.p-sm {\n  padding: 15px;\n}\n.p-m {\n  padding: 20px;\n}\n.p-md {\n  padding: 25px;\n}\n.p-lg {\n  padding: 30px;\n}\n.p-xl {\n  padding: 40px;\n}\n.p-w-xs {\n  padding: 0 10px;\n}\n.p-w-sm {\n  padding: 0 15px;\n}\n.p-w-m {\n  padding: 0 20px;\n}\n.p-w-md {\n  padding: 0 25px;\n}\n.p-w-lg {\n  padding: 0 30px;\n}\n.p-w-xl {\n  padding: 0 40px;\n}\n.p-h-xs {\n  padding: 10px 0;\n}\n.p-h-sm {\n  padding: 15px 0;\n}\n.p-h-m {\n  padding: 20px 0;\n}\n.p-h-md {\n  padding: 25px 0;\n}\n.p-h-lg {\n  padding: 30px 0;\n}\n.p-h-xl {\n  padding: 40px 0;\n}\n.m-xxs {\n  margin: 2px 4px;\n}\n.m {\n  margin: 15px;\n}\n.m-xs {\n  margin: 5px;\n}\n.m-sm {\n  margin: 10px;\n}\n.m-md {\n  margin: 20px;\n}\n.m-lg {\n  margin: 30px;\n}\n.m-xl {\n  margin: 50px;\n}\n.m-n {\n  margin: 0 !important;\n}\n.m-l-none {\n  margin-left: 0;\n}\n.m-l-xs {\n  margin-left: 5px;\n}\n.m-l-sm {\n  margin-left: 10px;\n}\n.m-l {\n  margin-left: 15px;\n}\n.m-l-md {\n  margin-left: 20px;\n}\n.m-l-lg {\n  margin-left: 30px;\n}\n.m-l-xl {\n  margin-left: 40px;\n}\n.m-l-n-xxs {\n  margin-left: -1px;\n}\n.m-l-n-xs {\n  margin-left: -5px;\n}\n.m-l-n-sm {\n  margin-left: -10px;\n}\n.m-l-n {\n  margin-left: -15px;\n}\n.m-l-n-md {\n  margin-left: -20px;\n}\n.m-l-n-lg {\n  margin-left: -30px;\n}\n.m-l-n-xl {\n  margin-left: -40px;\n}\n.m-t-none {\n  margin-top: 0;\n}\n.m-t-xxs {\n  margin-top: 1px;\n}\n.m-t-xs {\n  margin-top: 5px;\n}\n.m-t-sm {\n  margin-top: 10px;\n}\n.m-t {\n  margin-top: 15px;\n}\n.m-t-md {\n  margin-top: 20px;\n}\n.m-t-lg {\n  margin-top: 30px;\n}\n.m-t-xl {\n  margin-top: 40px;\n}\n.m-t-n-xxs {\n  margin-top: -1px;\n}\n.m-t-n-xs {\n  margin-top: -5px;\n}\n.m-t-n-sm {\n  margin-top: -10px;\n}\n.m-t-n {\n  margin-top: -15px;\n}\n.m-t-n-md {\n  margin-top: -20px;\n}\n.m-t-n-lg {\n  margin-top: -30px;\n}\n.m-t-n-xl {\n  margin-top: -40px;\n}\n.m-r-none {\n  margin-right: 0;\n}\n.m-r-xxs {\n  margin-right: 1px;\n}\n.m-r-xs {\n  margin-right: 5px;\n}\n.m-r-sm {\n  margin-right: 10px;\n}\n.m-r {\n  margin-right: 15px;\n}\n.m-r-md {\n  margin-right: 20px;\n}\n.m-r-lg {\n  margin-right: 30px;\n}\n.m-r-xl {\n  margin-right: 40px;\n}\n.m-r-n-xxs {\n  margin-right: -1px;\n}\n.m-r-n-xs {\n  margin-right: -5px;\n}\n.m-r-n-sm {\n  margin-right: -10px;\n}\n.m-r-n {\n  margin-right: -15px;\n}\n.m-r-n-md {\n  margin-right: -20px;\n}\n.m-r-n-lg {\n  margin-right: -30px;\n}\n.m-r-n-xl {\n  margin-right: -40px;\n}\n.m-b-none {\n  margin-bottom: 0;\n}\n.m-b-xxs {\n  margin-bottom: 1px;\n}\n.m-b-xs {\n  margin-bottom: 5px;\n}\n.m-b-sm {\n  margin-bottom: 10px;\n}\n.m-b {\n  margin-bottom: 15px;\n}\n.m-b-md {\n  margin-bottom: 20px;\n}\n.m-b-lg {\n  margin-bottom: 30px;\n}\n.m-b-xl {\n  margin-bottom: 40px;\n}\n.m-b-n-xxs {\n  margin-bottom: -1px;\n}\n.m-b-n-xs {\n  margin-bottom: -5px;\n}\n.m-b-n-sm {\n  margin-bottom: -10px;\n}\n.m-b-n {\n  margin-bottom: -15px;\n}\n.m-b-n-md {\n  margin-bottom: -20px;\n}\n.m-b-n-lg {\n  margin-bottom: -30px;\n}\n.m-b-n-xl {\n  margin-bottom: -40px;\n}\n.space-15 {\n  margin: 15px 0;\n}\n.space-20 {\n  margin: 20px 0;\n}\n.space-25 {\n  margin: 25px 0;\n}\n.space-30 {\n  margin: 30px 0;\n}\n.img-sm {\n  width: 32px;\n  height: 32px;\n}\n.img-md {\n  width: 64px;\n  height: 64px;\n}\n.img-lg {\n  width: 96px;\n  height: 96px;\n}\n.b-r-xs {\n  -webkit-border-radius: 1px;\n  -moz-border-radius: 1px;\n  border-radius: 1px;\n}\n.b-r-sm {\n  -webkit-border-radius: 3px;\n  -moz-border-radius: 3px;\n  border-radius: 3px;\n}\n.b-r-md {\n  -webkit-border-radius: 6px;\n  -moz-border-radius: 6px;\n  border-radius: 6px;\n}\n.b-r-lg {\n  -webkit-border-radius: 12px;\n  -moz-border-radius: 12px;\n  border-radius: 12px;\n}\n.b-r-xl {\n  -webkit-border-radius: 24px;\n  -moz-border-radius: 24px;\n  border-radius: 24px;\n}\n.fullscreen-ibox-mode .animated {\n  animation: none;\n}\nbody.fullscreen-ibox-mode {\n  overflow-y: hidden;\n}\n.ibox.fullscreen {\n  z-index: 2030;\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  overflow: auto;\n  margin-bottom: 0;\n}\n.ibox.fullscreen .collapse-link {\n  display: none;\n}\n.ibox.fullscreen .ibox-content {\n  min-height: calc(100% - 48px);\n}\nbody.modal-open {\n  padding-right: inherit !important;\n}\n_::-webkit-full-page-media,\n_:future,\n:root body.modal-open .wrapper-content.animated {\n  -webkit-animation: none;\n  -ms-animation-nam: none;\n  animation: none;\n}\nbody.modal-open .animated {\n  animation-fill-mode: initial;\n  z-index: inherit;\n}\n/* Show profile dropdown on fixed sidebar */\nbody.mini-navbar.fixed-sidebar .profile-element,\n.block {\n  display: block !important;\n}\nbody.mini-navbar.fixed-sidebar .nav-header {\n  padding: 33px 25px;\n}\nbody.mini-navbar.fixed-sidebar .logo-element {\n  display: none;\n}\n.fullscreen-video .animated {\n  animation: none;\n}\n.list-inline > li {\n  display: inline-block;\n}\n.custom-file-label {\n  padding: .5rem .75rem;\n}\n.custom-file-label::after {\n  padding: .5rem .75rem;\n}\n/* SEARCH PAGE */\n.search-form {\n  margin-top: 10px;\n}\n.search-result h3 {\n  margin-bottom: 0;\n  color: #1E0FBE;\n}\n.search-result .search-link {\n  color: #006621;\n}\n.search-result p {\n  font-size: 12px;\n  margin-top: 5px;\n}\n/* CONTACTS */\n.contact-box {\n  background-color: #ffffff;\n  border: 1px solid #e7eaec;\n  padding: 20px;\n  margin-bottom: 20px;\n}\n.contact-box > a {\n  color: inherit;\n}\n.contact-box.center-version {\n  border: 1px solid #e7eaec;\n  padding: 0;\n}\n.contact-box.center-version > a {\n  display: block;\n  background-color: #ffffff;\n  padding: 20px;\n  text-align: center;\n}\n.contact-box.center-version > a img {\n  width: 80px;\n  height: 80px;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.contact-box.center-version address {\n  margin-bottom: 0;\n}\n.contact-box .contact-box-footer {\n  text-align: center;\n  background-color: #ffffff;\n  border-top: 1px solid #e7eaec;\n  padding: 15px 20px;\n}\n/* INVOICE */\n.invoice-table tbody > tr > td:last-child,\n.invoice-table tbody > tr > td:nth-child(4),\n.invoice-table tbody > tr > td:nth-child(3),\n.invoice-table tbody > tr > td:nth-child(2) {\n  text-align: right;\n}\n.invoice-table thead > tr > th:last-child,\n.invoice-table thead > tr > th:nth-child(4),\n.invoice-table thead > tr > th:nth-child(3),\n.invoice-table thead > tr > th:nth-child(2) {\n  text-align: right;\n}\n.invoice-total > tbody > tr > td:first-child {\n  text-align: right;\n}\n.invoice-total > tbody > tr > td {\n  border: 0 none;\n}\n.invoice-total > tbody > tr > td:last-child {\n  border-bottom: 1px solid #DDDDDD;\n  text-align: right;\n  width: 15%;\n}\n/* ERROR & LOGIN & LOCKSCREEN*/\n.middle-box {\n  max-width: 400px;\n  z-index: 100;\n  margin: 0 auto;\n  padding-top: 40px;\n}\n.lockscreen.middle-box {\n  width: 200px;\n  padding-top: 110px;\n}\n.loginscreen.middle-box {\n  width: 300px;\n}\n.loginColumns {\n  max-width: 800px;\n  margin: 0 auto;\n}\n.passwordBox {\n  max-width: 460px;\n  margin: 0 auto;\n  padding: 100px 20px 20px 20px;\n}\n.logo-name {\n  color: #e6e6e6;\n  font-size: 180px;\n  font-weight: 800;\n  letter-spacing: -10px;\n  margin-bottom: 0;\n}\n.middle-box h1 {\n  font-size: 170px;\n}\n.wrapper .middle-box {\n  margin-top: 140px;\n}\n.lock-word {\n  z-index: 10;\n  position: absolute;\n  top: 110px;\n  left: 50%;\n  margin-left: -470px;\n}\n.lock-word span {\n  font-size: 100px;\n  font-weight: 600;\n  color: #e9e9e9;\n  display: inline-block;\n}\n.lock-word .first-word {\n  margin-right: 160px;\n}\n/* DASBOARD */\n.dashboard-header {\n  border-top: 0;\n  padding: 20px 20px 20px 20px;\n}\n.dashboard-header h2 {\n  margin-top: 10px;\n  font-size: 26px;\n}\n.fist-item {\n  border-top: none !important;\n}\n.statistic-box {\n  margin-top: 40px;\n}\n.dashboard-header .list-group-item span.label {\n  margin-right: 10px;\n}\n.list-group.clear-list .list-group-item {\n  border-top: 1px solid #e7eaec;\n  border-bottom: 0;\n  border-right: 0;\n  border-left: 0;\n  padding: 10px 0;\n}\nul.clear-list:first-child {\n  border-top: none !important;\n}\n/* Intimeline */\n.timeline-item .date i {\n  position: absolute;\n  top: 0;\n  right: 0;\n  padding: 5px;\n  width: 30px;\n  text-align: center;\n  border-top: 1px solid #e7eaec;\n  border-bottom: 1px solid #e7eaec;\n  border-left: 1px solid #e7eaec;\n  background: #f8f8f8;\n}\n.timeline-item .date {\n  text-align: right;\n  width: 110px;\n  position: relative;\n  padding-top: 30px;\n}\n.timeline-item .content {\n  border-left: 1px solid #e7eaec;\n  border-top: 1px solid #e7eaec;\n  padding-top: 10px;\n  min-height: 100px;\n}\n.timeline-item .content:hover {\n  background: #f6f6f6;\n}\n/* PIN BOARD */\nul.notes li,\nul.tag-list li {\n  list-style: none;\n}\nul.notes li h4 {\n  margin-top: 20px;\n  font-size: 16px;\n}\nul.notes li div {\n  text-decoration: none;\n  color: #000;\n  background: #ffc;\n  display: block;\n  height: 140px;\n  width: 140px;\n  padding: 1em;\n  position: relative;\n}\nul.notes li div small {\n  position: absolute;\n  top: 5px;\n  right: 5px;\n  font-size: 10px;\n}\nul.notes li div a {\n  position: absolute;\n  right: 10px;\n  bottom: 10px;\n  color: inherit;\n}\nul.notes li {\n  margin: 10px 40px 50px 0;\n  float: left;\n}\nul.notes li div p {\n  font-size: 12px;\n}\nul.notes li div {\n  text-decoration: none;\n  color: #000;\n  background: #ffc;\n  display: block;\n  height: 140px;\n  width: 140px;\n  padding: 1em;\n  /* Firefox */\n  -moz-box-shadow: 5px 5px 2px #212121;\n  /* Safari+Chrome */\n  -webkit-box-shadow: 5px 5px 2px rgba(33, 33, 33, 0.7);\n  /* Opera */\n  box-shadow: 5px 5px 2px rgba(33, 33, 33, 0.7);\n}\nul.notes li div {\n  -webkit-transform: rotate(-6deg);\n  -o-transform: rotate(-6deg);\n  -moz-transform: rotate(-6deg);\n  -ms-transform: rotate(-6deg);\n}\nul.notes li:nth-child(even) div {\n  -o-transform: rotate(4deg);\n  -webkit-transform: rotate(4deg);\n  -moz-transform: rotate(4deg);\n  -ms-transform: rotate(4deg);\n  position: relative;\n  top: 5px;\n}\nul.notes li:nth-child(3n) div {\n  -o-transform: rotate(-3deg);\n  -webkit-transform: rotate(-3deg);\n  -moz-transform: rotate(-3deg);\n  -ms-transform: rotate(-3deg);\n  position: relative;\n  top: -5px;\n}\nul.notes li:nth-child(5n) div {\n  -o-transform: rotate(5deg);\n  -webkit-transform: rotate(5deg);\n  -moz-transform: rotate(5deg);\n  -ms-transform: rotate(5deg);\n  position: relative;\n  top: -10px;\n}\nul.notes li div:hover,\nul.notes li div:focus {\n  -webkit-transform: scale(1.1);\n  -moz-transform: scale(1.1);\n  -o-transform: scale(1.1);\n  -ms-transform: scale(1.1);\n  position: relative;\n  z-index: 5;\n}\nul.notes li div {\n  text-decoration: none;\n  color: #000;\n  background: #ffc;\n  display: block;\n  height: 210px;\n  width: 210px;\n  padding: 1em;\n  -moz-box-shadow: 5px 5px 7px #212121;\n  -webkit-box-shadow: 5px 5px 7px rgba(33, 33, 33, 0.7);\n  box-shadow: 5px 5px 7px rgba(33, 33, 33, 0.7);\n  -moz-transition: -moz-transform 0.15s linear;\n  -o-transition: -o-transform 0.15s linear;\n  -webkit-transition: -webkit-transform 0.15s linear;\n}\n/* FILE MANAGER */\n.file-box {\n  float: left;\n  width: 220px;\n}\n.file-manager h5 {\n  text-transform: uppercase;\n}\n.file-manager {\n  list-style: none outside none;\n  margin: 0;\n  padding: 0;\n}\n.folder-list li a {\n  color: #666666;\n  display: block;\n  padding: 5px 0;\n}\n.folder-list li {\n  border-bottom: 1px solid #e7eaec;\n  display: block;\n}\n.folder-list li i {\n  margin-right: 8px;\n  color: #3d4d5d;\n}\n.category-list li a {\n  color: #666666;\n  display: block;\n  padding: 5px 0;\n}\n.category-list li {\n  display: block;\n}\n.category-list li i {\n  margin-right: 8px;\n  color: #3d4d5d;\n}\n.category-list li a .text-navy {\n  color: #1ab394;\n}\n.category-list li a .text-primary {\n  color: #1c84c6;\n}\n.category-list li a .text-info {\n  color: #23c6c8;\n}\n.category-list li a .text-danger {\n  color: #EF5352;\n}\n.category-list li a .text-warning {\n  color: #F8AC59;\n}\n.file-manager h5.tag-title {\n  margin-top: 20px;\n}\n.tag-list li {\n  float: left;\n}\n.tag-list li a {\n  font-size: 10px;\n  background-color: #f3f3f4;\n  padding: 5px 12px;\n  color: inherit;\n  border-radius: 2px;\n  border: 1px solid #e7eaec;\n  margin-right: 5px;\n  margin-top: 5px;\n  display: block;\n}\n.file {\n  border: 1px solid #e7eaec;\n  padding: 0;\n  background-color: #ffffff;\n  position: relative;\n  margin-bottom: 20px;\n  margin-right: 20px;\n}\n.file-manager .hr-line-dashed {\n  margin: 15px 0;\n}\n.file .icon,\n.file .image {\n  height: 100px;\n  overflow: hidden;\n}\n.file .icon {\n  padding: 15px 10px;\n  text-align: center;\n}\n.file-control {\n  color: inherit;\n  font-size: 11px;\n  margin-right: 10px;\n}\n.file-control.active {\n  text-decoration: underline;\n}\n.file .icon i {\n  font-size: 70px;\n  color: #dadada;\n}\n.file .file-name {\n  padding: 10px;\n  background-color: #f8f8f8;\n  border-top: 1px solid #e7eaec;\n}\n.file-name small {\n  color: #676a6c;\n}\n.corner {\n  position: absolute;\n  display: inline-block;\n  width: 0;\n  height: 0;\n  line-height: 0;\n  border: 0.6em solid transparent;\n  border-right: 0.6em solid #f1f1f1;\n  border-bottom: 0.6em solid #f1f1f1;\n  right: 0em;\n  bottom: 0em;\n}\na.compose-mail {\n  padding: 8px 10px;\n}\n.mail-search {\n  max-width: 300px;\n}\n/* PROFILE */\n.profile-content {\n  border-top: none !important;\n}\n.profile-stats {\n  margin-right: 10px;\n}\n.profile-image {\n  width: 120px;\n  float: left;\n}\n.profile-image img {\n  width: 96px;\n  height: 96px;\n}\n.profile-info {\n  margin-left: 120px;\n}\n.feed-activity-list .feed-element {\n  border-bottom: 1px solid #e7eaec;\n}\n.feed-element:first-child {\n  margin-top: 0;\n}\n.feed-element {\n  padding-bottom: 15px;\n}\n.feed-element,\n.feed-element .media {\n  margin-top: 15px;\n}\n.feed-element,\n.media-body {\n  overflow: hidden;\n}\n.feed-element > a img {\n  margin-right: 10px;\n}\n.feed-element img.rounded-circle,\n.dropdown-messages-box img.rounded-circle {\n  width: 38px;\n  height: 38px;\n}\n.feed-element .well {\n  border: 1px solid #e7eaec;\n  box-shadow: none;\n  margin-top: 10px;\n  margin-bottom: 5px;\n  padding: 10px 20px;\n  font-size: 11px;\n  line-height: 16px;\n}\n.feed-element .actions {\n  margin-top: 10px;\n}\n.feed-element .photos {\n  margin: 10px 0;\n}\n.dropdown-messages-box .dropdown-item:focus,\n.dropdown-messages-box .dropdown-item:hover {\n  background-color: inherit;\n}\n.feed-photo {\n  max-height: 180px;\n  border-radius: 4px;\n  overflow: hidden;\n  margin-right: 10px;\n  margin-bottom: 10px;\n}\n.file-list li {\n  padding: 5px 10px;\n  font-size: 11px;\n  border-radius: 2px;\n  border: 1px solid #e7eaec;\n  margin-bottom: 5px;\n}\n.file-list li a {\n  color: inherit;\n}\n.file-list li a:hover {\n  color: #1ab394;\n}\n.user-friends img {\n  width: 42px;\n  height: 42px;\n  margin-bottom: 5px;\n  margin-right: 5px;\n}\n/* MAILBOX */\n.mail-box {\n  background-color: #ffffff;\n  border: 1px solid #e7eaec;\n  border-top: 0;\n  padding: 0;\n  margin-bottom: 20px;\n}\n.mail-box-header {\n  background-color: #ffffff;\n  border: 1px solid #e7eaec;\n  border-bottom: 0;\n  padding: 30px 20px 20px 20px;\n}\n.mail-box-header h2 {\n  margin-top: 0;\n}\n.mailbox-content .tag-list li a {\n  background: #ffffff;\n}\n.mail-body {\n  border-top: 1px solid #e7eaec;\n  padding: 20px;\n}\n.mail-text {\n  border-top: 1px solid #e7eaec;\n}\n.mail-text .note-toolbar {\n  padding: 10px 15px;\n}\n.mail-body .form-group {\n  margin-bottom: 5px;\n}\n.mail-text .note-editor .note-toolbar {\n  background-color: #F9F8F8;\n}\n.mail-attachment {\n  border-top: 1px solid #e7eaec;\n  padding: 20px;\n  font-size: 12px;\n}\n.mailbox-content {\n  background: none;\n  border: none;\n  padding: 10px;\n}\n.mail-ontact {\n  width: 23%;\n}\n/* PROJECTS */\n.project-people,\n.project-actions {\n  text-align: right;\n  vertical-align: middle;\n}\ndd.project-people {\n  text-align: left;\n  margin-top: 5px;\n}\n.project-people img {\n  width: 32px;\n  height: 32px;\n}\n.project-title a {\n  font-size: 14px;\n  color: #676a6c;\n  font-weight: 600;\n}\n.project-list table tr td {\n  border-top: none;\n  border-bottom: 1px solid #e7eaec;\n  padding: 15px 10px;\n  vertical-align: middle;\n}\n.project-manager .tag-list li a {\n  font-size: 10px;\n  background-color: white;\n  padding: 5px 12px;\n  color: inherit;\n  border-radius: 2px;\n  border: 1px solid #e7eaec;\n  margin-right: 5px;\n  margin-top: 5px;\n  display: block;\n}\n.project-files li a {\n  font-size: 11px;\n  color: #676a6c;\n  margin-left: 10px;\n  line-height: 22px;\n}\n/* FAQ */\n.faq-item {\n  padding: 20px;\n  margin-bottom: 2px;\n  background: #fff;\n}\n.faq-question {\n  font-size: 18px;\n  font-weight: 600;\n  color: #1ab394;\n  display: block;\n}\n.faq-question:hover {\n  color: #179d82;\n}\n.faq-answer {\n  margin-top: 10px;\n  background: #f3f3f4;\n  border: 1px solid #e7eaec;\n  border-radius: 3px;\n  padding: 15px;\n}\n.faq-item .tag-item {\n  background: #f3f3f4;\n  padding: 2px 6px;\n  font-size: 10px;\n  text-transform: uppercase;\n}\n/* Chat view */\n.message-input {\n  height: 90px !important;\n}\n.chat-avatar {\n  width: 36px;\n  height: 36px;\n  float: left;\n  margin-right: 10px;\n}\n.chat-user-name {\n  padding: 10px;\n}\n.chat-user {\n  padding: 8px 10px;\n  border-bottom: 1px solid #e7eaec;\n}\n.chat-user a {\n  color: inherit;\n}\n.chat-view {\n  z-index: 20012;\n}\n.chat-users,\n.chat-statistic {\n  margin-left: -30px;\n}\n@media (max-width: 992px) {\n  .chat-users,\n  .chat-statistic {\n    margin-left: 0;\n  }\n}\n.chat-view .ibox-content {\n  padding: 0;\n}\n.chat-message {\n  padding: 10px 20px;\n}\n.message-avatar {\n  height: 48px;\n  width: 48px;\n  border: 1px solid #e7eaec;\n  border-radius: 4px;\n  margin-top: 1px;\n}\n.chat-discussion .chat-message.left .message-avatar {\n  float: left;\n  margin-right: 10px;\n}\n.chat-discussion .chat-message.right .message-avatar {\n  float: right;\n  margin-left: 10px;\n}\n.message {\n  background-color: #fff;\n  border: 1px solid #e7eaec;\n  text-align: left;\n  display: block;\n  padding: 10px 20px;\n  position: relative;\n  border-radius: 4px;\n}\n.chat-discussion .chat-message.left .message-date {\n  float: right;\n}\n.chat-discussion .chat-message.right .message-date {\n  float: left;\n}\n.chat-discussion .chat-message.left .message {\n  text-align: left;\n  margin-left: 55px;\n}\n.chat-discussion .chat-message.right .message {\n  text-align: right;\n  margin-right: 55px;\n}\n.message-date {\n  font-size: 10px;\n  color: #888888;\n}\n.message-content {\n  display: block;\n}\n.chat-discussion {\n  background: #eee;\n  padding: 15px;\n  height: 400px;\n  overflow-y: auto;\n}\n.chat-users {\n  overflow-y: auto;\n  height: 400px;\n}\n.chat-message-form .form-group {\n  margin-bottom: 0;\n}\n/* jsTree */\n.jstree-open > .jstree-anchor > .fa-folder:before {\n  content: \"\\f07c\";\n}\n.jstree-default .jstree-icon.none {\n  width: 0;\n}\n/* CLIENTS */\n.clients-list {\n  margin-top: 20px;\n}\n.clients-list .tab-pane {\n  position: relative;\n  height: 600px;\n}\n.client-detail {\n  position: relative;\n  height: 620px;\n}\n.clients-list table tr td {\n  height: 46px;\n  vertical-align: middle;\n  border: none;\n}\n.client-link {\n  font-weight: 600;\n  color: inherit;\n}\n.client-link:hover {\n  color: inherit;\n}\n.client-avatar {\n  width: 42px;\n}\n.client-avatar img {\n  width: 28px;\n  height: 28px;\n  border-radius: 50%;\n}\n.contact-type {\n  width: 20px;\n  color: #c1c3c4;\n}\n.client-status {\n  text-align: left;\n}\n.client-detail .vertical-timeline-content p {\n  margin: 0;\n}\n.client-detail .vertical-timeline-icon.gray-bg {\n  color: #a7aaab;\n}\n.clients-list .nav-tabs > li.active > a,\n.clients-list .nav-tabs > li.active > a:hover,\n.clients-list .nav-tabs > li.active > a:focus {\n  border-bottom: 1px solid #fff;\n}\n/* BLOG ARTICLE */\n.blog h2 {\n  font-weight: 700;\n}\n.blog h5 {\n  margin: 0 0 5px 0;\n}\n.blog .btn {\n  margin: 0 0 5px 0;\n}\n.article h1 {\n  font-size: 48px;\n  font-weight: 700;\n  color: #2f4050;\n}\n.article p {\n  font-size: 15px;\n  line-height: 26px;\n}\n.article-title {\n  text-align: center;\n  margin: 40px 0 100px 0;\n}\n.article .ibox-content {\n  padding: 40px;\n}\n/* ISSUE TRACKER */\n.issue-tracker .btn-link {\n  color: #1ab394;\n}\ntable.issue-tracker tbody tr td {\n  vertical-align: middle;\n  height: 50px;\n}\n.issue-info {\n  width: 50%;\n}\n.issue-info a {\n  font-weight: 600;\n  color: #676a6c;\n}\n.issue-info small {\n  display: block;\n}\n/* TEAMS */\n.team-members {\n  margin: 10px 0;\n}\n.team-members img.rounded-circle {\n  width: 42px;\n  height: 42px;\n  margin-bottom: 5px;\n}\n/* AGILE BOARD */\n.sortable-list {\n  padding: 10px 0;\n}\n.agile-list {\n  list-style: none;\n  margin: 0;\n}\n.agile-list li {\n  background: #FAFAFB;\n  border: 1px solid #e7eaec;\n  margin: 0 0 10px 0;\n  padding: 10px;\n  border-radius: 2px;\n}\n.agile-list li:hover {\n  cursor: pointer;\n  background: #fff;\n}\n.agile-list li.warning-element {\n  border-left: 3px solid #f8ac59;\n}\n.agile-list li.danger-element {\n  border-left: 3px solid #ed5565;\n}\n.agile-list li.info-element {\n  border-left: 3px solid #1c84c6;\n}\n.agile-list li.success-element {\n  border-left: 3px solid #1ab394;\n}\n.agile-detail {\n  margin-top: 5px;\n  font-size: 12px;\n}\n/* DIFF */\nins {\n  background-color: #c6ffc6;\n  text-decoration: none;\n}\ndel {\n  background-color: #ffc6c6;\n}\n/* E-commerce */\n.product-box {\n  padding: 0;\n  border: 1px solid #e7eaec;\n}\n.product-box:hover,\n.product-box.active {\n  border: 1px solid transparent;\n  -webkit-box-shadow: 0 3px 7px 0 #a8a8a8;\n  -moz-box-shadow: 0 3px 7px 0 #a8a8a8;\n  box-shadow: 0 3px 7px 0 #a8a8a8;\n}\n.product-imitation {\n  text-align: center;\n  padding: 90px 0;\n  background-color: #f8f8f9;\n  color: #bebec3;\n  font-weight: 600;\n}\n.cart-product-imitation {\n  text-align: center;\n  padding-top: 30px;\n  height: 80px;\n  width: 80px;\n  background-color: #f8f8f9;\n}\n.product-imitation.xl {\n  padding: 120px 0;\n}\n.product-desc {\n  padding: 20px;\n  position: relative;\n}\n.ecommerce .tag-list {\n  padding: 0;\n}\n.ecommerce .fa-star {\n  color: #d1dade;\n}\n.ecommerce .fa-star.active {\n  color: #f8ac59;\n}\n.ecommerce .note-editor {\n  border: 1px solid #e7eaec;\n}\ntable.shoping-cart-table {\n  margin-bottom: 0;\n}\ntable.shoping-cart-table tr td {\n  border: none;\n  text-align: right;\n}\ntable.shoping-cart-table tr td.desc,\ntable.shoping-cart-table tr td:first-child {\n  text-align: left;\n}\ntable.shoping-cart-table tr td:last-child {\n  width: 80px;\n}\n.product-name {\n  font-size: 16px;\n  font-weight: 600;\n  color: #676a6c;\n  display: block;\n  margin: 2px 0 5px 0;\n}\n.product-name:hover,\n.product-name:focus {\n  color: #1ab394;\n}\n.product-price {\n  font-size: 14px;\n  font-weight: 600;\n  color: #ffffff;\n  background-color: #1ab394;\n  padding: 6px 12px;\n  position: absolute;\n  top: -32px;\n  right: 0;\n}\n.product-detail .ibox-content {\n  padding: 30px 30px 50px 30px;\n}\n.image-imitation {\n  background-color: #f8f8f9;\n  text-align: center;\n  padding: 200px 0;\n}\n.product-main-price small {\n  font-size: 10px;\n}\n.product-images {\n  margin: 0 20px;\n}\n/* Social feed */\n.social-feed-separated .social-feed-box {\n  margin-left: 62px;\n}\n.social-feed-separated .social-avatar {\n  float: left;\n  padding: 0;\n}\n.social-feed-separated .social-avatar img {\n  width: 52px;\n  height: 52px;\n  border: 1px solid #e7eaec;\n}\n.social-feed-separated .social-feed-box .social-avatar {\n  padding: 15px 15px 0 15px;\n  float: none;\n}\n.social-feed-box {\n  /*padding: 15px;*/\n  border: 1px solid #e7eaec;\n  background: #fff;\n  margin-bottom: 15px;\n}\n.article .social-feed-box {\n  margin-bottom: 0;\n  border-bottom: none;\n}\n.article .social-feed-box:last-child {\n  margin-bottom: 0;\n  border-bottom: 1px solid #e7eaec;\n}\n.article .social-feed-box p {\n  font-size: 13px;\n  line-height: 18px;\n}\n.social-action {\n  margin: 15px;\n}\n.social-action .dropdown-toggle::after {\n  margin-left: auto;\n}\n.social-avatar {\n  padding: 15px 15px 0 15px;\n}\n.social-comment .social-comment {\n  margin-left: 45px;\n}\n.social-avatar img {\n  height: 40px;\n  width: 40px;\n  margin-right: 10px;\n}\n.social-avatar .media-body a {\n  font-size: 14px;\n  display: block;\n}\n.social-body {\n  padding: 15px;\n}\n.social-body img {\n  margin-bottom: 10px;\n}\n.social-footer {\n  border-top: 1px solid #e7eaec;\n  padding: 10px 15px;\n  background: #f9f9f9;\n}\n.social-footer .social-comment img {\n  width: 32px;\n  margin-right: 10px;\n}\n.social-comment:first-child {\n  margin-top: 0;\n}\n.social-comment {\n  margin-top: 15px;\n}\n.social-comment textarea {\n  font-size: 12px;\n}\n/* Vote list */\n.vote-item {\n  padding: 20px 25px;\n  background: #ffffff;\n  border-top: 1px solid #e7eaec;\n}\n.vote-item:last-child {\n  border-bottom: 1px solid #e7eaec;\n}\n.vote-item:hover {\n  background: #fbfbfb;\n}\n.vote-actions {\n  float: left;\n  width: 30px;\n  margin-right: 15px;\n  text-align: center;\n}\n.vote-actions a {\n  color: #1ab394;\n  font-weight: 600;\n}\n.vote-actions {\n  font-weight: 600;\n}\n.vote-title {\n  display: block;\n  color: inherit;\n  font-size: 18px;\n  font-weight: 600;\n  margin-top: 5px;\n  margin-bottom: 2px;\n}\n.vote-title:hover,\n.vote-title:focus {\n  color: inherit;\n}\n.vote-info,\n.vote-title {\n  margin-left: 45px;\n}\n.vote-info,\n.vote-info a {\n  color: #b4b6b8;\n  font-size: 12px;\n}\n.vote-info a {\n  margin-right: 10px;\n}\n.vote-info a:hover {\n  color: #1ab394;\n}\n.vote-icon {\n  text-align: right;\n  font-size: 38px;\n  display: block;\n  color: #e8e9ea;\n}\n.vote-icon.active {\n  color: #1ab394;\n}\nbody.body-small .vote-icon {\n  display: none;\n}\n.lightBoxGallery {\n  text-align: center;\n}\n.lightBoxGallery img {\n  margin: 5px;\n}\n#small-chat {\n  position: fixed;\n  bottom: 20px;\n  right: 20px;\n  z-index: 1000;\n}\n#small-chat .badge {\n  position: absolute;\n  top: -3px;\n  right: -4px;\n}\n.open-small-chat {\n  height: 38px;\n  width: 38px;\n  display: block;\n  background: #1ab394;\n  padding: 9px 8px;\n  text-align: center;\n  color: #fff;\n  border-radius: 50%;\n}\n.open-small-chat:hover {\n  color: white;\n  background: #1ab394;\n}\n.small-chat-box {\n  display: none;\n  position: fixed;\n  bottom: 20px;\n  right: 75px;\n  background: #fff;\n  border: 1px solid #e7eaec;\n  width: 230px;\n  height: 320px;\n  border-radius: 4px;\n}\n.small-chat-box.ng-small-chat {\n  display: block;\n}\n.body-small .small-chat-box {\n  bottom: 70px;\n  right: 20px;\n}\n.small-chat-box.active {\n  display: block;\n}\n.small-chat-box {\n  z-index: 1001;\n}\n.small-chat-box .heading {\n  background: #2f4050;\n  padding: 8px 15px;\n  font-weight: bold;\n  color: #fff;\n}\n.small-chat-box .chat-date {\n  opacity: 0.6;\n  font-size: 10px;\n  font-weight: normal;\n}\n.small-chat-box .content {\n  padding: 15px 15px;\n}\n.small-chat-box .content .author-name {\n  font-weight: bold;\n  margin-bottom: 3px;\n  font-size: 11px;\n}\n.small-chat-box .content > div {\n  padding-bottom: 20px;\n}\n.small-chat-box .content .chat-message {\n  padding: 5px 10px;\n  border-radius: 6px;\n  font-size: 11px;\n  line-height: 14px;\n  max-width: 80%;\n  background: #f3f3f4;\n  margin-bottom: 10px;\n}\n.small-chat-box .content .chat-message.active {\n  background: #1ab394;\n  color: #fff;\n}\n.small-chat-box .content .left {\n  text-align: left;\n  clear: both;\n}\n.small-chat-box .content .left .chat-message {\n  float: left;\n}\n.small-chat-box .content .right {\n  text-align: right;\n  clear: both;\n}\n.small-chat-box .content .right .chat-message {\n  float: right;\n}\n.small-chat-box .form-chat {\n  padding: 10px 10px;\n}\n/*\n * metismenu - v2.0.2\n * A jQuery menu plugin\n * https://github.com/onokumus/metisMenu\n *\n * Made by Osman Nuri Okumus\n * Under MIT License\n */\n.metismenu .plus-minus,\n.metismenu .plus-times {\n  float: right;\n}\n.metismenu .arrow {\n  float: right;\n  line-height: 1.42857;\n}\n.metismenu .glyphicon.arrow:before {\n  content: \"\\e079\";\n}\n.metismenu .active > a > .glyphicon.arrow:before {\n  content: \"\\e114\";\n}\n.metismenu .fa.arrow:before {\n  content: \"\\f104\";\n}\n.metismenu .active > a > .fa.arrow:before {\n  content: \"\\f107\";\n}\n.metismenu .ion.arrow:before {\n  content: \"\\f3d2\";\n}\n.metismenu .active > a > .ion.arrow:before {\n  content: \"\\f3d0\";\n}\n.metismenu .fa.plus-minus:before,\n.metismenu .fa.plus-times:before {\n  content: \"\\f067\";\n}\n.metismenu .active > a > .fa.plus-times {\n  -webkit-transform: rotate(45deg);\n  -ms-transform: rotate(45deg);\n  transform: rotate(45deg);\n}\n.metismenu .active > a > .fa.plus-minus:before {\n  content: \"\\f068\";\n}\n.metismenu .collapse {\n  display: none;\n}\n.metismenu .collapse.in {\n  display: block;\n}\n.metismenu .collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  -webkit-transition-timing-function: ease;\n  transition-timing-function: ease;\n  -webkit-transition-duration: .35s;\n  transition-duration: .35s;\n  -webkit-transition-property: height, visibility;\n  transition-property: height, visibility;\n}\n.mini-navbar .metismenu .collapse {\n  opacity: 0;\n}\n.mini-navbar .metismenu .collapse.in {\n  opacity: 1;\n}\n.mini-navbar .metismenu .collapse a {\n  display: none;\n}\n.mini-navbar .metismenu .collapse.in a {\n  display: block;\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-rotating-plane\"></div>\n *\n */\n.sk-spinner-rotating-plane.sk-spinner {\n  width: 30px;\n  height: 30px;\n  background-color: #1ab394;\n  margin: 0 auto;\n  -webkit-animation: sk-rotatePlane 1.2s infinite ease-in-out;\n  animation: sk-rotatePlane 1.2s infinite ease-in-out;\n}\n@-webkit-keyframes sk-rotatePlane {\n  0% {\n    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);\n    transform: perspective(120px) rotateX(0deg) rotateY(0deg);\n  }\n  50% {\n    -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);\n    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);\n  }\n  100% {\n    -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);\n    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);\n  }\n}\n@keyframes sk-rotatePlane {\n  0% {\n    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);\n    transform: perspective(120px) rotateX(0deg) rotateY(0deg);\n  }\n  50% {\n    -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);\n    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);\n  }\n  100% {\n    -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);\n    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-double-bounce\">\n *      <div class=\"sk-double-bounce1\"></div>\n *      <div class=\"sk-double-bounce2\"></div>\n *    </div>\n *\n */\n.sk-spinner-double-bounce.sk-spinner {\n  width: 40px;\n  height: 40px;\n  position: relative;\n  margin: 0 auto;\n}\n.sk-spinner-double-bounce .sk-double-bounce1,\n.sk-spinner-double-bounce .sk-double-bounce2 {\n  width: 100%;\n  height: 100%;\n  border-radius: 50%;\n  background-color: #1ab394;\n  opacity: 0.6;\n  position: absolute;\n  top: 0;\n  left: 0;\n  -webkit-animation: sk-doubleBounce 2s infinite ease-in-out;\n  animation: sk-doubleBounce 2s infinite ease-in-out;\n}\n.sk-spinner-double-bounce .sk-double-bounce2 {\n  -webkit-animation-delay: -1s;\n  animation-delay: -1s;\n}\n@-webkit-keyframes sk-doubleBounce {\n  0%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  50% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n@keyframes sk-doubleBounce {\n  0%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  50% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-wave\">\n *      <div class=\"sk-rect1\"></div>\n *      <div class=\"sk-rect2\"></div>\n *      <div class=\"sk-rect3\"></div>\n *      <div class=\"sk-rect4\"></div>\n *      <div class=\"sk-rect5\"></div>\n *    </div>\n *\n */\n.sk-spinner-wave.sk-spinner {\n  margin: 0 auto;\n  width: 50px;\n  height: 30px;\n  text-align: center;\n  font-size: 10px;\n}\n.sk-spinner-wave div {\n  background-color: #1ab394;\n  height: 100%;\n  width: 6px;\n  display: inline-block;\n  -webkit-animation: sk-waveStretchDelay 1.2s infinite ease-in-out;\n  animation: sk-waveStretchDelay 1.2s infinite ease-in-out;\n}\n.sk-spinner-wave .sk-rect2 {\n  -webkit-animation-delay: -1.1s;\n  animation-delay: -1.1s;\n}\n.sk-spinner-wave .sk-rect3 {\n  -webkit-animation-delay: -1s;\n  animation-delay: -1s;\n}\n.sk-spinner-wave .sk-rect4 {\n  -webkit-animation-delay: -0.9s;\n  animation-delay: -0.9s;\n}\n.sk-spinner-wave .sk-rect5 {\n  -webkit-animation-delay: -0.8s;\n  animation-delay: -0.8s;\n}\n@-webkit-keyframes sk-waveStretchDelay {\n  0%,\n  40%,\n  100% {\n    -webkit-transform: scaleY(0.4);\n    transform: scaleY(0.4);\n  }\n  20% {\n    -webkit-transform: scaleY(1);\n    transform: scaleY(1);\n  }\n}\n@keyframes sk-waveStretchDelay {\n  0%,\n  40%,\n  100% {\n    -webkit-transform: scaleY(0.4);\n    transform: scaleY(0.4);\n  }\n  20% {\n    -webkit-transform: scaleY(1);\n    transform: scaleY(1);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-wandering-cubes\">\n *      <div class=\"sk-cube1\"></div>\n *      <div class=\"sk-cube2\"></div>\n *    </div>\n *\n */\n.sk-spinner-wandering-cubes.sk-spinner {\n  margin: 0 auto;\n  width: 32px;\n  height: 32px;\n  position: relative;\n}\n.sk-spinner-wandering-cubes .sk-cube1,\n.sk-spinner-wandering-cubes .sk-cube2 {\n  background-color: #1ab394;\n  width: 10px;\n  height: 10px;\n  position: absolute;\n  top: 0;\n  left: 0;\n  -webkit-animation: sk-wanderingCubeMove 1.8s infinite ease-in-out;\n  animation: sk-wanderingCubeMove 1.8s infinite ease-in-out;\n}\n.sk-spinner-wandering-cubes .sk-cube2 {\n  -webkit-animation-delay: -0.9s;\n  animation-delay: -0.9s;\n}\n@-webkit-keyframes sk-wanderingCubeMove {\n  25% {\n    -webkit-transform: translateX(42px) rotate(-90deg) scale(0.5);\n    transform: translateX(42px) rotate(-90deg) scale(0.5);\n  }\n  50% {\n    /* Hack to make FF rotate in the right direction */\n    -webkit-transform: translateX(42px) translateY(42px) rotate(-179deg);\n    transform: translateX(42px) translateY(42px) rotate(-179deg);\n  }\n  50.1% {\n    -webkit-transform: translateX(42px) translateY(42px) rotate(-180deg);\n    transform: translateX(42px) translateY(42px) rotate(-180deg);\n  }\n  75% {\n    -webkit-transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5);\n    transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5);\n  }\n  100% {\n    -webkit-transform: rotate(-360deg);\n    transform: rotate(-360deg);\n  }\n}\n@keyframes sk-wanderingCubeMove {\n  25% {\n    -webkit-transform: translateX(42px) rotate(-90deg) scale(0.5);\n    transform: translateX(42px) rotate(-90deg) scale(0.5);\n  }\n  50% {\n    /* Hack to make FF rotate in the right direction */\n    -webkit-transform: translateX(42px) translateY(42px) rotate(-179deg);\n    transform: translateX(42px) translateY(42px) rotate(-179deg);\n  }\n  50.1% {\n    -webkit-transform: translateX(42px) translateY(42px) rotate(-180deg);\n    transform: translateX(42px) translateY(42px) rotate(-180deg);\n  }\n  75% {\n    -webkit-transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5);\n    transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5);\n  }\n  100% {\n    -webkit-transform: rotate(-360deg);\n    transform: rotate(-360deg);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-pulse\"></div>\n *\n */\n.sk-spinner-pulse.sk-spinner {\n  width: 40px;\n  height: 40px;\n  margin: 0 auto;\n  background-color: #1ab394;\n  border-radius: 100%;\n  -webkit-animation: sk-pulseScaleOut 1s infinite ease-in-out;\n  animation: sk-pulseScaleOut 1s infinite ease-in-out;\n}\n@-webkit-keyframes sk-pulseScaleOut {\n  0% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  100% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n    opacity: 0;\n  }\n}\n@keyframes sk-pulseScaleOut {\n  0% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  100% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n    opacity: 0;\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-chasing-dots\">\n *      <div class=\"sk-dot1\"></div>\n *      <div class=\"sk-dot2\"></div>\n *    </div>\n *\n */\n.sk-spinner-chasing-dots.sk-spinner {\n  margin: 0 auto;\n  width: 40px;\n  height: 40px;\n  position: relative;\n  text-align: center;\n  -webkit-animation: sk-chasingDotsRotate 2s infinite linear;\n  animation: sk-chasingDotsRotate 2s infinite linear;\n}\n.sk-spinner-chasing-dots .sk-dot1,\n.sk-spinner-chasing-dots .sk-dot2 {\n  width: 60%;\n  height: 60%;\n  display: inline-block;\n  position: absolute;\n  top: 0;\n  background-color: #1ab394;\n  border-radius: 100%;\n  -webkit-animation: sk-chasingDotsBounce 2s infinite ease-in-out;\n  animation: sk-chasingDotsBounce 2s infinite ease-in-out;\n}\n.sk-spinner-chasing-dots .sk-dot2 {\n  top: auto;\n  bottom: 0;\n  -webkit-animation-delay: -1s;\n  animation-delay: -1s;\n}\n@-webkit-keyframes sk-chasingDotsRotate {\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n@keyframes sk-chasingDotsRotate {\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n@-webkit-keyframes sk-chasingDotsBounce {\n  0%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  50% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n@keyframes sk-chasingDotsBounce {\n  0%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  50% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-three-bounce\">\n *      <div class=\"sk-bounce1\"></div>\n *      <div class=\"sk-bounce2\"></div>\n *      <div class=\"sk-bounce3\"></div>\n *    </div>\n *\n */\n.sk-spinner-three-bounce.sk-spinner {\n  margin: 0 auto;\n  width: 70px;\n  text-align: center;\n}\n.sk-spinner-three-bounce div {\n  width: 18px;\n  height: 18px;\n  background-color: #1ab394;\n  border-radius: 100%;\n  display: inline-block;\n  -webkit-animation: sk-threeBounceDelay 1.4s infinite ease-in-out;\n  animation: sk-threeBounceDelay 1.4s infinite ease-in-out;\n  /* Prevent first frame from flickering when animation starts */\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n}\n.sk-spinner-three-bounce .sk-bounce1 {\n  -webkit-animation-delay: -0.32s;\n  animation-delay: -0.32s;\n}\n.sk-spinner-three-bounce .sk-bounce2 {\n  -webkit-animation-delay: -0.16s;\n  animation-delay: -0.16s;\n}\n@-webkit-keyframes sk-threeBounceDelay {\n  0%,\n  80%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  40% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n@keyframes sk-threeBounceDelay {\n  0%,\n  80%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  40% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-circle\">\n *      <div class=\"sk-circle1 sk-circle\"></div>\n *      <div class=\"sk-circle2 sk-circle\"></div>\n *      <div class=\"sk-circle3 sk-circle\"></div>\n *      <div class=\"sk-circle4 sk-circle\"></div>\n *      <div class=\"sk-circle5 sk-circle\"></div>\n *      <div class=\"sk-circle6 sk-circle\"></div>\n *      <div class=\"sk-circle7 sk-circle\"></div>\n *      <div class=\"sk-circle8 sk-circle\"></div>\n *      <div class=\"sk-circle9 sk-circle\"></div>\n *      <div class=\"sk-circle10 sk-circle\"></div>\n *      <div class=\"sk-circle11 sk-circle\"></div>\n *      <div class=\"sk-circle12 sk-circle\"></div>\n *    </div>\n *\n */\n.sk-spinner-circle.sk-spinner {\n  margin: 0 auto;\n  width: 22px;\n  height: 22px;\n  position: relative;\n}\n.sk-spinner-circle .sk-circle {\n  width: 100%;\n  height: 100%;\n  position: absolute;\n  left: 0;\n  top: 0;\n}\n.sk-spinner-circle .sk-circle:before {\n  content: '';\n  display: block;\n  margin: 0 auto;\n  width: 20%;\n  height: 20%;\n  background-color: #1ab394;\n  border-radius: 100%;\n  -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out;\n  animation: sk-circleBounceDelay 1.2s infinite ease-in-out;\n  /* Prevent first frame from flickering when animation starts */\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n}\n.sk-spinner-circle .sk-circle2 {\n  -webkit-transform: rotate(30deg);\n  -ms-transform: rotate(30deg);\n  transform: rotate(30deg);\n}\n.sk-spinner-circle .sk-circle3 {\n  -webkit-transform: rotate(60deg);\n  -ms-transform: rotate(60deg);\n  transform: rotate(60deg);\n}\n.sk-spinner-circle .sk-circle4 {\n  -webkit-transform: rotate(90deg);\n  -ms-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n.sk-spinner-circle .sk-circle5 {\n  -webkit-transform: rotate(120deg);\n  -ms-transform: rotate(120deg);\n  transform: rotate(120deg);\n}\n.sk-spinner-circle .sk-circle6 {\n  -webkit-transform: rotate(150deg);\n  -ms-transform: rotate(150deg);\n  transform: rotate(150deg);\n}\n.sk-spinner-circle .sk-circle7 {\n  -webkit-transform: rotate(180deg);\n  -ms-transform: rotate(180deg);\n  transform: rotate(180deg);\n}\n.sk-spinner-circle .sk-circle8 {\n  -webkit-transform: rotate(210deg);\n  -ms-transform: rotate(210deg);\n  transform: rotate(210deg);\n}\n.sk-spinner-circle .sk-circle9 {\n  -webkit-transform: rotate(240deg);\n  -ms-transform: rotate(240deg);\n  transform: rotate(240deg);\n}\n.sk-spinner-circle .sk-circle10 {\n  -webkit-transform: rotate(270deg);\n  -ms-transform: rotate(270deg);\n  transform: rotate(270deg);\n}\n.sk-spinner-circle .sk-circle11 {\n  -webkit-transform: rotate(300deg);\n  -ms-transform: rotate(300deg);\n  transform: rotate(300deg);\n}\n.sk-spinner-circle .sk-circle12 {\n  -webkit-transform: rotate(330deg);\n  -ms-transform: rotate(330deg);\n  transform: rotate(330deg);\n}\n.sk-spinner-circle .sk-circle2:before {\n  -webkit-animation-delay: -1.1s;\n  animation-delay: -1.1s;\n}\n.sk-spinner-circle .sk-circle3:before {\n  -webkit-animation-delay: -1s;\n  animation-delay: -1s;\n}\n.sk-spinner-circle .sk-circle4:before {\n  -webkit-animation-delay: -0.9s;\n  animation-delay: -0.9s;\n}\n.sk-spinner-circle .sk-circle5:before {\n  -webkit-animation-delay: -0.8s;\n  animation-delay: -0.8s;\n}\n.sk-spinner-circle .sk-circle6:before {\n  -webkit-animation-delay: -0.7s;\n  animation-delay: -0.7s;\n}\n.sk-spinner-circle .sk-circle7:before {\n  -webkit-animation-delay: -0.6s;\n  animation-delay: -0.6s;\n}\n.sk-spinner-circle .sk-circle8:before {\n  -webkit-animation-delay: -0.5s;\n  animation-delay: -0.5s;\n}\n.sk-spinner-circle .sk-circle9:before {\n  -webkit-animation-delay: -0.4s;\n  animation-delay: -0.4s;\n}\n.sk-spinner-circle .sk-circle10:before {\n  -webkit-animation-delay: -0.3s;\n  animation-delay: -0.3s;\n}\n.sk-spinner-circle .sk-circle11:before {\n  -webkit-animation-delay: -0.2s;\n  animation-delay: -0.2s;\n}\n.sk-spinner-circle .sk-circle12:before {\n  -webkit-animation-delay: -0.1s;\n  animation-delay: -0.1s;\n}\n@-webkit-keyframes sk-circleBounceDelay {\n  0%,\n  80%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  40% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n@keyframes sk-circleBounceDelay {\n  0%,\n  80%,\n  100% {\n    -webkit-transform: scale(0);\n    transform: scale(0);\n  }\n  40% {\n    -webkit-transform: scale(1);\n    transform: scale(1);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-cube-grid\">\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *      <div class=\"sk-cube\"></div>\n *    </div>\n *\n */\n.sk-spinner-cube-grid {\n  /*\n   * Spinner positions\n   * 1 2 3\n   * 4 5 6\n   * 7 8 9\n   */\n}\n.sk-spinner-cube-grid.sk-spinner {\n  width: 30px;\n  height: 30px;\n  margin: 0 auto;\n}\n.sk-spinner-cube-grid .sk-cube {\n  width: 33%;\n  height: 33%;\n  background-color: #1ab394;\n  float: left;\n  -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;\n  animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(1) {\n  -webkit-animation-delay: 0.2s;\n  animation-delay: 0.2s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(2) {\n  -webkit-animation-delay: 0.3s;\n  animation-delay: 0.3s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(3) {\n  -webkit-animation-delay: 0.4s;\n  animation-delay: 0.4s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(4) {\n  -webkit-animation-delay: 0.1s;\n  animation-delay: 0.1s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(5) {\n  -webkit-animation-delay: 0.2s;\n  animation-delay: 0.2s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(6) {\n  -webkit-animation-delay: 0.3s;\n  animation-delay: 0.3s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(7) {\n  -webkit-animation-delay: 0s;\n  animation-delay: 0s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(8) {\n  -webkit-animation-delay: 0.1s;\n  animation-delay: 0.1s;\n}\n.sk-spinner-cube-grid .sk-cube:nth-child(9) {\n  -webkit-animation-delay: 0.2s;\n  animation-delay: 0.2s;\n}\n@-webkit-keyframes sk-cubeGridScaleDelay {\n  0%,\n  70%,\n  100% {\n    -webkit-transform: scale3D(1, 1, 1);\n    transform: scale3D(1, 1, 1);\n  }\n  35% {\n    -webkit-transform: scale3D(0, 0, 1);\n    transform: scale3D(0, 0, 1);\n  }\n}\n@keyframes sk-cubeGridScaleDelay {\n  0%,\n  70%,\n  100% {\n    -webkit-transform: scale3D(1, 1, 1);\n    transform: scale3D(1, 1, 1);\n  }\n  35% {\n    -webkit-transform: scale3D(0, 0, 1);\n    transform: scale3D(0, 0, 1);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-wordpress\">\n *      <span class=\"sk-inner-circle\"></span>\n *    </div>\n *\n */\n.sk-spinner-wordpress.sk-spinner {\n  background-color: #1ab394;\n  width: 30px;\n  height: 30px;\n  border-radius: 30px;\n  position: relative;\n  margin: 0 auto;\n  -webkit-animation: sk-innerCircle 1s linear infinite;\n  animation: sk-innerCircle 1s linear infinite;\n}\n.sk-spinner-wordpress .sk-inner-circle {\n  display: block;\n  background-color: #fff;\n  width: 8px;\n  height: 8px;\n  position: absolute;\n  border-radius: 8px;\n  top: 5px;\n  left: 5px;\n}\n@-webkit-keyframes sk-innerCircle {\n  0% {\n    -webkit-transform: rotate(0);\n    transform: rotate(0);\n  }\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n@keyframes sk-innerCircle {\n  0% {\n    -webkit-transform: rotate(0);\n    transform: rotate(0);\n  }\n  100% {\n    -webkit-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n/*\n *  Usage:\n *\n *    <div class=\"sk-spinner sk-spinner-fading-circle\">\n *      <div class=\"sk-circle1 sk-circle\"></div>\n *      <div class=\"sk-circle2 sk-circle\"></div>\n *      <div class=\"sk-circle3 sk-circle\"></div>\n *      <div class=\"sk-circle4 sk-circle\"></div>\n *      <div class=\"sk-circle5 sk-circle\"></div>\n *      <div class=\"sk-circle6 sk-circle\"></div>\n *      <div class=\"sk-circle7 sk-circle\"></div>\n *      <div class=\"sk-circle8 sk-circle\"></div>\n *      <div class=\"sk-circle9 sk-circle\"></div>\n *      <div class=\"sk-circle10 sk-circle\"></div>\n *      <div class=\"sk-circle11 sk-circle\"></div>\n *      <div class=\"sk-circle12 sk-circle\"></div>\n *    </div>\n *\n */\n.sk-spinner-fading-circle.sk-spinner {\n  margin: 0 auto;\n  width: 22px;\n  height: 22px;\n  position: relative;\n}\n.sk-spinner-fading-circle .sk-circle {\n  width: 100%;\n  height: 100%;\n  position: absolute;\n  left: 0;\n  top: 0;\n}\n.sk-spinner-fading-circle .sk-circle:before {\n  content: '';\n  display: block;\n  margin: 0 auto;\n  width: 18%;\n  height: 18%;\n  background-color: #1ab394;\n  border-radius: 100%;\n  -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out;\n  animation: sk-circleFadeDelay 1.2s infinite ease-in-out;\n  /* Prevent first frame from flickering when animation starts */\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n}\n.sk-spinner-fading-circle .sk-circle2 {\n  -webkit-transform: rotate(30deg);\n  -ms-transform: rotate(30deg);\n  transform: rotate(30deg);\n}\n.sk-spinner-fading-circle .sk-circle3 {\n  -webkit-transform: rotate(60deg);\n  -ms-transform: rotate(60deg);\n  transform: rotate(60deg);\n}\n.sk-spinner-fading-circle .sk-circle4 {\n  -webkit-transform: rotate(90deg);\n  -ms-transform: rotate(90deg);\n  transform: rotate(90deg);\n}\n.sk-spinner-fading-circle .sk-circle5 {\n  -webkit-transform: rotate(120deg);\n  -ms-transform: rotate(120deg);\n  transform: rotate(120deg);\n}\n.sk-spinner-fading-circle .sk-circle6 {\n  -webkit-transform: rotate(150deg);\n  -ms-transform: rotate(150deg);\n  transform: rotate(150deg);\n}\n.sk-spinner-fading-circle .sk-circle7 {\n  -webkit-transform: rotate(180deg);\n  -ms-transform: rotate(180deg);\n  transform: rotate(180deg);\n}\n.sk-spinner-fading-circle .sk-circle8 {\n  -webkit-transform: rotate(210deg);\n  -ms-transform: rotate(210deg);\n  transform: rotate(210deg);\n}\n.sk-spinner-fading-circle .sk-circle9 {\n  -webkit-transform: rotate(240deg);\n  -ms-transform: rotate(240deg);\n  transform: rotate(240deg);\n}\n.sk-spinner-fading-circle .sk-circle10 {\n  -webkit-transform: rotate(270deg);\n  -ms-transform: rotate(270deg);\n  transform: rotate(270deg);\n}\n.sk-spinner-fading-circle .sk-circle11 {\n  -webkit-transform: rotate(300deg);\n  -ms-transform: rotate(300deg);\n  transform: rotate(300deg);\n}\n.sk-spinner-fading-circle .sk-circle12 {\n  -webkit-transform: rotate(330deg);\n  -ms-transform: rotate(330deg);\n  transform: rotate(330deg);\n}\n.sk-spinner-fading-circle .sk-circle2:before {\n  -webkit-animation-delay: -1.1s;\n  animation-delay: -1.1s;\n}\n.sk-spinner-fading-circle .sk-circle3:before {\n  -webkit-animation-delay: -1s;\n  animation-delay: -1s;\n}\n.sk-spinner-fading-circle .sk-circle4:before {\n  -webkit-animation-delay: -0.9s;\n  animation-delay: -0.9s;\n}\n.sk-spinner-fading-circle .sk-circle5:before {\n  -webkit-animation-delay: -0.8s;\n  animation-delay: -0.8s;\n}\n.sk-spinner-fading-circle .sk-circle6:before {\n  -webkit-animation-delay: -0.7s;\n  animation-delay: -0.7s;\n}\n.sk-spinner-fading-circle .sk-circle7:before {\n  -webkit-animation-delay: -0.6s;\n  animation-delay: -0.6s;\n}\n.sk-spinner-fading-circle .sk-circle8:before {\n  -webkit-animation-delay: -0.5s;\n  animation-delay: -0.5s;\n}\n.sk-spinner-fading-circle .sk-circle9:before {\n  -webkit-animation-delay: -0.4s;\n  animation-delay: -0.4s;\n}\n.sk-spinner-fading-circle .sk-circle10:before {\n  -webkit-animation-delay: -0.3s;\n  animation-delay: -0.3s;\n}\n.sk-spinner-fading-circle .sk-circle11:before {\n  -webkit-animation-delay: -0.2s;\n  animation-delay: -0.2s;\n}\n.sk-spinner-fading-circle .sk-circle12:before {\n  -webkit-animation-delay: -0.1s;\n  animation-delay: -0.1s;\n}\n@-webkit-keyframes sk-circleFadeDelay {\n  0%,\n  39%,\n  100% {\n    opacity: 0;\n  }\n  40% {\n    opacity: 1;\n  }\n}\n@keyframes sk-circleFadeDelay {\n  0%,\n  39%,\n  100% {\n    opacity: 0;\n  }\n  40% {\n    opacity: 1;\n  }\n}\n.ibox-content > .sk-spinner {\n  display: none;\n}\n.ibox-content.sk-loading {\n  position: relative;\n}\n.ibox-content.sk-loading:after {\n  content: '';\n  background-color: rgba(255, 255, 255, 0.7);\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}\n.ibox-content.sk-loading > .sk-spinner {\n  display: block;\n  position: absolute;\n  top: 40%;\n  left: 0;\n  right: 0;\n  z-index: 2000;\n}\n/* PACE PLUGIN\n-------------------------------------------------- */\n.landing-page.pace .pace-progress {\n  background: #fff;\n  position: fixed;\n  z-index: 2000;\n  top: 0;\n  left: 0;\n  height: 2px;\n  -webkit-transition: width 1s;\n  -moz-transition: width 1s;\n  -o-transition: width 1s;\n  transition: width 1s;\n}\n.pace-inactive {\n  display: none;\n}\nbody.landing-page {\n  color: #676a6c;\n  font-family: 'Open Sans', helvetica, arial, sans-serif;\n  background-color: #fff;\n}\n.landing-page {\n  /* CUSTOMIZE THE NAVBAR\n  -------------------------------------------------- */\n  /* Flip around the padding for proper display in narrow viewports */\n  /* BACKGROUNDS SLIDER\n  -------------------------------------------------- */\n  /* CUSTOMIZE THE CAROUSEL\n  -------------------------------------------------- */\n  /* Carousel base class */\n  /* Since positioning the image, we need to help out the caption */\n  /* Declare heights because of positioning of img element */\n  /* Sections\n  ------------------------- */\n  /* Buttons - only primary custom button\n  ------------------------- */\n  /* RESPONSIVE CSS\n  -------------------------------------------------- */\n}\n.landing-page button:focus {\n  outline: 0;\n}\n.landing-page .container {\n  overflow: hidden;\n}\n.landing-page span.navy {\n  color: #1ab394;\n}\n.landing-page p.text-color {\n  color: #676a6c;\n}\n.landing-page a.navy-link {\n  color: #1ab394;\n  text-decoration: none;\n}\n.landing-page a.navy-link:hover {\n  color: #179d82;\n}\n.landing-page section p {\n  color: #aeaeae;\n  font-size: 13px;\n}\n.landing-page address {\n  font-size: 13px;\n}\n.landing-page h1 {\n  margin-top: 10px;\n  font-size: 30px;\n  font-weight: 200;\n}\n.landing-page .navy-line {\n  width: 60px;\n  height: 1px;\n  margin: 60px auto 0;\n  border-bottom: 2px solid #1ab394;\n}\n.landing-page .navbar {\n  padding: 0 1rem;\n}\n.landing-page .navbar-wrapper {\n  position: fixed;\n  top: 0;\n  right: 0;\n  left: 0;\n  z-index: 200;\n}\n.landing-page .navbar-wrapper > .container {\n  padding-right: 0;\n  padding-left: 0;\n}\n.landing-page .navbar-wrapper .navbar {\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.landing-page .navbar-default.navbar-scroll {\n  background-color: #fff;\n  border-color: #fff;\n  padding: 15px 0;\n}\n.landing-page .navbar-default {\n  background-color: transparent;\n  border-color: transparent;\n  transition: all 0.3s ease-in-out 0s;\n}\n.landing-page .navbar-default .nav li a {\n  color: #fff;\n  font-family: 'Open Sans', helvetica, arial, sans-serif;\n  font-weight: 700;\n  letter-spacing: 1px;\n  text-transform: uppercase;\n  font-size: 14px;\n}\n.landing-page .navbar-nav > li > a {\n  padding-top: 25px;\n  border-top: 6px solid transparent;\n}\n.landing-page .navbar-default .navbar-nav > .active > a,\n.landing-page .navbar-default .navbar-nav > .active > a:hover {\n  background: transparent;\n  color: #fff;\n  border-top: 6px solid #1ab394;\n}\n.landing-page .navbar-default .navbar-nav > li > a:hover,\n.landing-page .navbar-default .navbar-nav > li > a:focus {\n  color: #1ab394;\n  background: inherit;\n}\n.landing-page .navbar-default .navbar-nav > .active > a:focus {\n  background: transparent;\n  color: #fff;\n}\n.landing-page .navbar-default .navbar-nav > .active > a:focus {\n  background: transparent;\n  color: #ffffff;\n}\n.landing-page .navbar-default.navbar-scroll .navbar-nav > .active > a:focus {\n  background: transparent;\n  color: inherit;\n}\n.landing-page .navbar-default .navbar-brand:hover,\n.landing-page .navbar-default .navbar-brand:focus {\n  background: #179d82;\n  color: #fff;\n}\n.landing-page .navbar-default .navbar-brand {\n  color: #fff;\n  height: auto;\n  display: block;\n  font-size: 14px;\n  background: #1ab394;\n  padding: 15px 20px 15px 20px;\n  border-radius: 0 0 5px 5px;\n  font-weight: 700;\n  transition: all 0.3s ease-in-out 0s;\n  margin-top: -16px;\n}\n.landing-page .navbar-scroll.navbar-default .nav li a {\n  color: #676a6c;\n}\n.landing-page .navbar-scroll.navbar-default .nav li a:hover {\n  color: #1ab394;\n}\n.landing-page .navbar-wrapper .navbar.navbar-scroll {\n  padding-top: 0;\n  padding-bottom: 5px;\n  border-bottom: 1px solid #e7eaec;\n  border-radius: 0;\n}\n.landing-page .nav.navbar-right {\n  flex-direction: row;\n}\n.landing-page .nav > li.active {\n  border: none;\n  background: inherit;\n}\n.landing-page .nav > li > a {\n  padding: 25px 10px 15px 10px;\n}\n.landing-page .navbar-scroll .navbar-nav > li > a {\n  padding: 20px 10px;\n}\n.landing-page .navbar-default .navbar-nav > li .nav-link.active,\n.landing-page .navbar-default .navbar-nav > li .nav-link.active:hover {\n  border-top: 6px solid #1ab394;\n}\n.landing-page .navbar-fixed-top {\n  border: none !important;\n}\n.landing-page .navbar-fixed-top.navbar-scroll {\n  border-bottom: 1px solid #e7eaec !important;\n}\n.landing-page .navbar.navbar-scroll .navbar-brand {\n  margin-top: 5px;\n  border-radius: 5px;\n  font-size: 12px;\n  padding: 10px;\n  height: auto;\n}\n.landing-page .header-back {\n  height: 470px;\n  width: 100%;\n}\n.landing-page .header-back.one {\n  background: url('../img/landing/header_one.jpg') 50% 0 no-repeat;\n}\n.landing-page .header-back.two {\n  background: url('../img/landing/header_two.jpg') 50% 0 no-repeat;\n}\n.landing-page .carousel {\n  height: 470px;\n  overflow: hidden;\n}\n.landing-page .carousel-caption {\n  z-index: 10;\n}\n.landing-page .carousel .item {\n  height: 470px;\n  background-color: #777;\n}\n.landing-page .carousel-inner > .item > img {\n  position: absolute;\n  top: 0;\n  left: 0;\n  min-width: 100%;\n  height: 470px;\n}\n.landing-page .carousel-fade .carousel-inner .item {\n  opacity: 0;\n  -webkit-transition-property: opacity;\n  transition-property: opacity;\n}\n.landing-page .carousel-fade .carousel-inner .active {\n  opacity: 1;\n}\n.landing-page .carousel-fade .carousel-inner .active.left,\n.landing-page .carousel-fade .carousel-inner .active.right {\n  left: 0;\n  opacity: 0;\n  z-index: 1;\n}\n.landing-page .carousel-fade .carousel-inner .next.left,\n.landing-page .carousel-fade .carousel-inner .prev.right {\n  opacity: 1;\n}\n.landing-page .carousel-fade .carousel-control {\n  z-index: 2;\n}\n.landing-page .carousel-control.left,\n.landing-page .carousel-control.right {\n  background: none;\n}\n.landing-page .carousel-control {\n  width: 6%;\n}\n.landing-page .carousel-inner .container {\n  position: relative;\n  overflow: visible;\n}\n.landing-page .carousel-inner {\n  overflow: visible;\n}\n.landing-page .carousel-caption {\n  position: absolute;\n  top: 100px;\n  left: 0;\n  bottom: auto;\n  right: auto;\n  text-align: left;\n}\n.landing-page .carousel-caption {\n  position: absolute;\n  top: 100px;\n  left: 0;\n  bottom: auto;\n  right: auto;\n  text-align: left;\n}\n.landing-page .carousel-caption.blank {\n  top: 140px;\n}\n.landing-page .carousel-image {\n  position: absolute;\n  right: 10px;\n  top: 150px;\n}\n.landing-page .carousel-indicators {\n  padding-right: 60px;\n}\n.landing-page .carousel-caption h1 {\n  font-weight: 700;\n  font-size: 38px;\n  text-transform: uppercase;\n  text-shadow: none;\n  letter-spacing: -1.5px;\n}\n.landing-page .carousel-caption p {\n  font-weight: 700;\n  text-transform: uppercase;\n  text-shadow: none;\n}\n.landing-page .caption-link {\n  color: #fff;\n  margin-left: 10px;\n  text-transform: capitalize;\n  font-weight: 400;\n}\n.landing-page .caption-link:hover {\n  text-decoration: none;\n  color: inherit;\n}\n.landing-page .services {\n  padding-top: 60px;\n}\n.landing-page .services h2 {\n  font-size: 20px;\n  letter-spacing: -1px;\n  font-weight: 600;\n  text-transform: uppercase;\n}\n.landing-page .features-block {\n  margin-top: 40px;\n}\n.landing-page .features-text {\n  margin-top: 40px;\n}\n.landing-page .features small {\n  color: #1ab394;\n}\n.landing-page .features h2 {\n  font-size: 18px;\n  margin-top: 5px;\n}\n.landing-page .features-text-alone {\n  margin: 40px 0;\n}\n.landing-page .features-text-alone h1 {\n  font-weight: 200;\n}\n.landing-page .features-icon {\n  color: #1ab394;\n  font-size: 40px;\n}\n.landing-page .navy-section {\n  margin-top: 60px;\n  background: #1ab394;\n  color: #fff;\n  padding: 20px 0;\n}\n.landing-page .gray-section {\n  background: #f4f4f4;\n  margin-top: 60px;\n}\n.landing-page .team-member {\n  text-align: center;\n}\n.landing-page .team-member img {\n  margin: auto;\n}\n.landing-page .social-icon a {\n  background: #1ab394;\n  color: #fff;\n  padding: 4px 8px;\n  height: 28px;\n  width: 28px;\n  display: block;\n  border-radius: 50px;\n}\n.landing-page .social-icon a:hover {\n  background: #179d82;\n}\n.landing-page .img-small {\n  height: 88px;\n  width: 88px;\n}\n.landing-page .pricing-plan {\n  margin: 20px 30px 0 30px;\n  border-radius: 4px;\n}\n.landing-page .pricing-plan.selected {\n  transform: scale(1.1);\n  background: #f4f4f4;\n}\n.landing-page .pricing-plan li {\n  padding: 10px 16px;\n  border-top: 1px solid #e7eaec;\n  text-align: center;\n  color: #aeaeae;\n}\n.landing-page .pricing-plan .pricing-price span {\n  font-weight: 700;\n  color: #1ab394;\n}\n.landing-page li.pricing-desc {\n  font-size: 13px;\n  border-top: none;\n  padding: 20px 16px;\n}\n.landing-page li.pricing-title {\n  background: #1ab394;\n  color: #fff;\n  padding: 10px;\n  border-radius: 4px 4px 0 0;\n  font-size: 22px;\n  font-weight: 600;\n}\n.landing-page .testimonials {\n  padding-top: 80px;\n  padding-bottom: 90px;\n  background-color: #1ab394;\n  background-image: url('../img/landing/avatar_all.png');\n}\n.landing-page .big-icon {\n  font-size: 56px !important;\n}\n.landing-page .features .big-icon {\n  color: #1ab394 !important;\n}\n.landing-page .contact {\n  background-image: url('../img/landing/word_map.png');\n  background-position: 50% 50%;\n  background-repeat: no-repeat;\n  margin-top: 60px;\n}\n.landing-page section.timeline {\n  padding-bottom: 30px;\n}\n.landing-page section.comments {\n  padding-bottom: 80px;\n}\n.landing-page .comments-avatar {\n  margin-top: 25px;\n  margin-left: 22px;\n  margin-bottom: 25px;\n}\n.landing-page .comments-avatar .commens-name {\n  font-weight: 600;\n  font-size: 14px;\n}\n.landing-page .comments-avatar img {\n  width: 42px;\n  height: 42px;\n  border-radius: 50%;\n  margin-right: 10px;\n}\n.landing-page .bubble {\n  position: relative;\n  height: 120px;\n  padding: 20px;\n  background: #FFFFFF;\n  -webkit-border-radius: 10px;\n  -moz-border-radius: 10px;\n  border-radius: 10px;\n  font-style: italic;\n  font-size: 14px;\n}\n.landing-page .bubble:after {\n  content: '';\n  position: absolute;\n  border-style: solid;\n  border-width: 15px 14px 0;\n  border-color: #FFFFFF transparent;\n  display: block;\n  width: 0;\n  z-index: 1;\n  bottom: -15px;\n  left: 30px;\n}\n.landing-page .btn-primary.btn-outline:hover,\n.landing-page .btn-success.btn-outline:hover,\n.landing-page .btn-info.btn-outline:hover,\n.landing-page .btn-warning.btn-outline:hover,\n.landing-page .btn-danger.btn-outline:hover {\n  color: #fff;\n}\n.landing-page .btn-primary {\n  background-color: #1ab394;\n  border-color: #1ab394;\n  color: #FFFFFF;\n  font-size: 14px;\n  padding: 10px 20px;\n  font-weight: 600;\n}\n.landing-page .btn-primary:hover,\n.landing-page .btn-primary:focus,\n.landing-page .btn-primary:active,\n.landing-page .btn-primary.active,\n.landing-page .open .dropdown-toggle.btn-primary {\n  background-color: #179d82;\n  border-color: #179d82;\n  color: #FFFFFF;\n}\n.landing-page .btn-primary:active,\n.landing-page .btn-primary.active,\n.landing-page .open .dropdown-toggle.btn-primary {\n  background-image: none;\n}\n.landing-page .btn-primary.disabled,\n.landing-page .btn-primary.disabled:hover,\n.landing-page .btn-primary.disabled:focus,\n.landing-page .btn-primary.disabled:active,\n.landing-page .btn-primary.disabled.active,\n.landing-page .btn-primary[disabled],\n.landing-page .btn-primary[disabled]:hover,\n.landing-page .btn-primary[disabled]:focus,\n.landing-page .btn-primary[disabled]:active,\n.landing-page .btn-primary.active[disabled],\n.landing-page fieldset[disabled] .btn-primary,\n.landing-page fieldset[disabled] .btn-primary:hover,\n.landing-page fieldset[disabled] .btn-primary:focus,\n.landing-page fieldset[disabled] .btn-primary:active,\n.landing-page fieldset[disabled] .btn-primary.active {\n  background-color: #1dc5a3;\n  border-color: #1dc5a3;\n}\n@media (min-width: 768px) {\n  .landing-page {\n    /* Navbar positioning foo */\n    /* The navbar becomes detached from the top, so we round the corners */\n    /* Bump up size of carousel content */\n  }\n  .landing-page .navbar-wrapper .container {\n    padding-right: 15px;\n    padding-left: 15px;\n  }\n  .landing-page .navbar-wrapper .navbar {\n    padding-right: 0;\n    padding-left: 0;\n  }\n  .landing-page .navbar-wrapper .navbar {\n    border-radius: 4px;\n  }\n  .landing-page .carousel-caption p {\n    margin-bottom: 20px;\n    font-size: 14px;\n    line-height: 1.4;\n  }\n  .landing-page .featurette-heading {\n    font-size: 50px;\n  }\n}\n@media (max-width: 992px) {\n  .landing-page .carousel-image {\n    display: none;\n  }\n}\n@media (max-width: 768px) {\n  .landing-page .carousel-caption,\n  .landing-page .carousel-caption.blank {\n    left: 5%;\n    top: 80px;\n  }\n  .landing-page .carousel-caption h1 {\n    font-size: 28px;\n  }\n  .landing-page .navbar.navbar-scroll .navbar-brand {\n    margin-top: 6px;\n  }\n  .landing-page .navbar-default {\n    background-color: #fff;\n    border-color: #fff;\n    padding: 15px 0;\n  }\n  .landing-page .navbar-default .navbar-nav > .active > a:focus {\n    background: transparent;\n    color: inherit;\n  }\n  .landing-page .navbar-default .nav li a {\n    color: #676a6c;\n  }\n  .landing-page .navbar-default .nav li a:hover {\n    color: #1ab394;\n  }\n  .landing-page .navbar-wrapper .navbar {\n    padding-top: 0;\n    padding-bottom: 5px;\n    border-bottom: 1px solid #e7eaec;\n    border-radius: 0;\n  }\n  .landing-page .nav > li > a {\n    padding: 10px 10px 15px 10px;\n  }\n  .landing-page .navbar-nav > li > a {\n    padding: 20px 10px;\n  }\n  .landing-page .navbar .navbar-brand {\n    margin-top: 5px;\n    border-radius: 5px;\n    font-size: 12px;\n    padding: 10px;\n    height: auto;\n  }\n  .landing-page .navbar-wrapper .navbar {\n    padding-left: 15px;\n    padding-right: 5px;\n  }\n  .landing-page .navbar-default .navbar-nav > .active > a,\n  .landing-page .navbar-default .navbar-nav > .active > a:hover {\n    color: inherit;\n  }\n  .landing-page .carousel-control {\n    display: none;\n  }\n}\n@media (min-width: 992px) {\n  .landing-page .featurette-heading {\n    margin-top: 120px;\n  }\n}\n@media (max-width: 768px) {\n  .landing-page .navbar .navbar-header {\n    display: block;\n    float: none;\n  }\n  .landing-page .navbar .navbar-header .navbar-toggle {\n    background-color: #ffffff;\n    padding: 9px 10px;\n    border: none;\n  }\n  .landing-page .nav.navbar-right {\n    flex-direction: column;\n  }\n}\n.landing-page .navbar-toggle {\n  color: #ddd;\n  float: right;\n}\n.landing-page .navbar-toggle i {\n  font-size: 24px;\n}\nbody.rtls {\n  text-align: right !important;\n  /* Theme config */\n}\nbody.rtls .nav-second-level li a {\n  padding: 7px 35px 7px 10px;\n}\nbody.rtls .ibox-title h5 {\n  float: right;\n}\nbody.rtls .float-right {\n  float: left !important;\n}\nbody.rtls .float-left {\n  float: right !important;\n}\nbody.rtls .ibox-title {\n  padding: 15px 15px 8px 15px;\n}\nbody.rtls .ibox-title .label {\n  float: left;\n}\nbody.rtls #small-chat {\n  right: auto;\n  left: 20px;\n}\nbody.rtls .small-chat-box {\n  right: auto;\n  left: 75px;\n}\nbody.rtls .ibox-tools {\n  float: left;\n  position: static;\n}\nbody.rtls .stat-percent {\n  float: left;\n}\nbody.rtls .navbar-right {\n  float: left !important;\n}\nbody.rtls .navbar-top-links li:last-child {\n  margin-left: 40px;\n  margin-right: 0;\n}\nbody.rtls .minimalize-styl-2 {\n  float: right;\n  margin: 14px 20px 5px 5px;\n}\nbody.rtls .feed-element > .float-left {\n  margin-left: 10px;\n  margin-right: 0;\n}\nbody.rtls .timeline-item .date {\n  text-align: left;\n}\nbody.rtls .timeline-item .date i {\n  left: 0;\n  right: auto;\n}\nbody.rtls .timeline-item .content {\n  border-right: 1px solid #e7eaec;\n  border-left: none;\n}\nbody.rtls .theme-config {\n  left: 0;\n  right: auto;\n}\nbody.rtls .spin-icon {\n  border-radius: 0 20px 20px 0;\n}\nbody.rtls .toast-close-button {\n  float: left;\n}\nbody.rtls #toast-container > .toast:before {\n  margin: auto -1.5em auto 0.5em;\n}\nbody.rtls #toast-container > div {\n  padding: 15px 50px 15px 15px;\n}\nbody.rtls #toast-container > div {\n  background-position: 95% center;\n}\nbody.rtls .center-orientation .vertical-timeline-icon i {\n  margin-left: 0;\n  margin-right: -12px;\n}\nbody.rtls .vertical-timeline-icon i {\n  right: 50%;\n  left: auto;\n  margin-left: auto;\n  margin-right: -12px;\n}\nbody.rtls .file-box {\n  float: right;\n}\nbody.rtls ul.notes li {\n  float: right;\n}\nbody.rtls .chat-users,\nbody.rtls .chat-statistic {\n  margin-right: -30px;\n  margin-left: auto;\n}\nbody.rtls .dropdown-menu > li > a {\n  text-align: right;\n}\nbody.rtls .b-r {\n  border-left: 1px solid #e7eaec;\n  border-right: none;\n}\nbody.rtls .dd-list .dd-list {\n  padding-right: 30px;\n  padding-left: 0;\n}\nbody.rtls .dd-item > button {\n  float: right;\n}\nbody.rtls .theme-config-box {\n  margin-left: -220px;\n  margin-right: 0;\n}\nbody.rtls .theme-config-box.show {\n  margin-left: 0;\n  margin-right: 0;\n}\nbody.rtls .spin-icon {\n  right: 0;\n  left: auto;\n}\nbody.rtls .skin-settings {\n  margin-right: 40px;\n  margin-left: 0;\n}\nbody.rtls .skin-settings {\n  direction: ltr;\n}\nbody.rtls .footer.fixed {\n  margin-right: 220px;\n  margin-left: 0;\n}\nbody.rtls .navbar-static-top .dropdown-menu {\n  left: 0;\n  right: auto;\n}\nbody.rtls .social-footer .social-comment img,\nbody.rtls .social-avatar img {\n  margin-left: 10px;\n  margin-right: 0;\n}\nbody.rtls .sidebar-container .sidebar-message > a > .float-left {\n  margin-left: 10px;\n  margin-right: 0;\n}\nbody.rtls .setings-item .switch {\n  margin-left: 5px;\n}\nbody.rtls .nav > li > a i {\n  margin-left: 6px;\n}\n@media (max-width: 992px) {\n  body.rtls .chat-users,\n  body.rtls .chat-statistic {\n    margin-right: 0;\n  }\n}\nbody.rtls.mini-navbar .footer.fixed,\nbody.body-small.mini-navbar .footer.fixed {\n  margin: 0 70px 0 0;\n}\nbody.rtls.mini-navbar.fixed-sidebar .footer.fixed,\nbody.body-small.mini-navbar .footer.fixed {\n  margin: 0 0 0 0;\n}\nbody.rtls.top-navigation .navbar-toggle {\n  float: right;\n  margin-left: 15px;\n  margin-right: 15px;\n}\n.body-small.rtls.top-navigation .navbar-header {\n  float: none;\n}\nbody.rtls.top-navigation #page-wrapper {\n  margin: 0;\n}\nbody.rtls.mini-navbar.fixed-sidebar #page-wrapper {\n  margin: 0 0 0 0;\n}\nbody.rtls.body-small.fixed-sidebar.mini-navbar #page-wrapper {\n  margin: 0 220px 0 0;\n}\nbody.rtls.body-small.fixed-sidebar.mini-navbar .navbar-static-side {\n  width: 220px;\n}\n.body-small.rtls .navbar-fixed-top {\n  margin-right: 0;\n}\n.body-small.rtls .navbar-header {\n  float: right;\n}\nbody.rtls .navbar-top-links li:last-child {\n  margin-left: 20px;\n}\nbody.rtls .top-navigation #page-wrapper,\nbody.rtls.mini-navbar .top-navigation #page-wrapper,\nbody.rtls.mini-navbar.top-navigation #page-wrapper {\n  margin: 0;\n}\nbody.rtls .top-navigation .footer.fixed,\nbody.rtls.top-navigation .footer.fixed {\n  margin: 0;\n}\n@media (max-width: 768px) {\n  body.rtls .navbar-top-links li:last-child {\n    margin-left: 10px;\n  }\n  .navbar-top-links li a {\n    padding: 20px 5px;\n  }\n  .body-small.rtls #page-wrapper {\n    position: inherit;\n    margin: 0 0 0 0;\n    min-height: 1000px;\n  }\n  .rtls.fixed-sidebar.body-small .navbar-static-side {\n    display: none;\n    z-index: 2001;\n    position: fixed;\n    width: 220px;\n  }\n  .rtls.fixed-sidebar.body-small.mini-navbar .navbar-static-side {\n    display: block;\n  }\n}\n.rtls .ltr-support {\n  direction: ltr;\n}\n.rtls.mini-navbar .nav-second-level,\n.rtls.mini-navbar li.active .nav-second-level {\n  left: auto;\n  right: 70px;\n}\n.rtls #right-sidebar {\n  left: -260px;\n  right: auto;\n}\n.rtls #right-sidebar.sidebar-open {\n  left: 0;\n}\n/*\n *\n *   This is style for skin config\n *   Use only in demo theme\n *\n*/\n.theme-config {\n  position: absolute;\n  top: 90px;\n  right: 0;\n  overflow: hidden;\n}\n.theme-config-box {\n  margin-right: -220px;\n  position: relative;\n  z-index: 2100;\n  transition-duration: 0.8s;\n}\n.theme-config-box.show {\n  margin-right: 0;\n}\n.spin-icon {\n  background: #1ab394;\n  position: absolute;\n  padding: 7px 10px 7px 13px;\n  border-radius: 20px 0 0 20px;\n  font-size: 16px;\n  top: 0;\n  left: 0;\n  width: 40px;\n  color: #fff;\n  cursor: pointer;\n}\n.skin-settings {\n  width: 220px;\n  margin-left: 40px;\n  background: #f3f3f4;\n}\n.skin-settings .title {\n  background: #efefef;\n  text-align: center;\n  text-transform: uppercase;\n  font-weight: 600;\n  display: block;\n  padding: 10px 15px;\n  font-size: 12px;\n}\n.setings-item {\n  padding: 10px 30px;\n}\n.setings-item.skin {\n  text-align: center;\n}\n.setings-item .switch {\n  float: right;\n}\n.skin-name a {\n  text-transform: uppercase;\n}\n.setings-item a {\n  color: #fff;\n}\n.default-skin,\n.blue-skin,\n.ultra-skin,\n.yellow-skin {\n  text-align: center;\n}\n.default-skin {\n  font-weight: 600;\n  background: #283A49;\n}\n.default-skin:hover {\n  background: #1e2e3d;\n}\n.blue-skin {\n  font-weight: 600;\n  background: url(\"patterns/header-profile-skin-1.png\") repeat scroll 0 0;\n}\n.blue-skin:hover {\n  background: #0d8ddb;\n}\n.yellow-skin {\n  font-weight: 600;\n  background: url(\"patterns/header-profile-skin-3.png\") repeat scroll 0 100%;\n}\n.yellow-skin:hover {\n  background: #ce8735;\n}\n.ultra-skin {\n  padding: 20px 10px;\n  font-weight: 600;\n  background: url(\"patterns/3.png\") repeat scroll 0 0;\n}\n.ultra-skin:hover {\n  background: url(\"patterns/4.png\") repeat scroll 0 0;\n}\n/*\n *\n *   SKIN 1 - INSPINIA - Responsive Admin Theme\n *   NAME - Blue light\n *\n*/\n.skin-1 .minimalize-styl-2 {\n  margin: 14px 5px 5px 30px;\n}\n.skin-1 .navbar-top-links li:last-child {\n  margin-right: 30px;\n}\n.skin-1.fixed-nav .minimalize-styl-2 {\n  margin: 14px 5px 5px 15px;\n}\n.skin-1 .spin-icon {\n  background: #0e9aef !important;\n}\n.skin-1 .nav-header {\n  background-color: #0e9aef;\n  background-image: url('patterns/header-profile-skin-1.png');\n}\n.skin-1.mini-navbar .nav-second-level {\n  background: #3e495f;\n}\n.skin-1 .breadcrumb {\n  background: transparent;\n}\n.skin-1 .page-heading {\n  border: none;\n}\n.skin-1 .nav > li.active {\n  background: #3a4459;\n}\n.skin-1 .nav > li > a {\n  color: #9ea6b9;\n}\n.skin-1 ul.nav-second-level {\n  background-color: inherit;\n}\n.skin-1 .nav > li.active > a {\n  color: #fff;\n}\n.skin-1 .navbar-minimalize {\n  background: #0e9aef;\n  border-color: #0e9aef;\n}\nbody.skin-1 {\n  background: #3e495f;\n}\n.skin-1 .navbar-static-top {\n  background: #ffffff;\n}\n.skin-1 .dashboard-header {\n  background: transparent;\n  border-bottom: none !important;\n  border-top: none;\n  padding: 20px 30px 10px 30px;\n}\n.fixed-nav.skin-1 .navbar-fixed-top {\n  background: #fff;\n}\n.skin-1 .wrapper-content {\n  padding: 30px 15px;\n}\n.skin-1 #page-wrapper {\n  background: #f4f6fa;\n}\n.skin-1 .ibox-title,\n.skin-1 .ibox-content {\n  border-width: 1px;\n}\n.skin-1 .ibox-content:last-child {\n  border-style: solid solid solid solid;\n}\n.skin-1 .nav > li.active {\n  border: none;\n}\n.skin-1 .nav-header {\n  padding: 35px 25px 25px 25px;\n}\n.skin-1 .nav-header a.dropdown-toggle {\n  color: #fff;\n  margin-top: 10px;\n}\n.skin-1 .nav-header a.dropdown-toggle .text-muted {\n  color: #fff;\n  opacity: 0.8;\n}\n.skin-1 .profile-element {\n  text-align: center;\n}\n.skin-1 .rounded-circle {\n  border-radius: 5px;\n}\n.skin-1 .navbar-default .nav > li > a:hover,\n.skin-1 .navbar-default .nav > li > a:focus {\n  background: #3a4459;\n  color: #fff;\n}\n.skin-1 .nav.nav-tabs > li.active > a {\n  color: #555;\n}\n.skin-1 .nav.nav-tabs > li.active {\n  background: transparent;\n}\n/*\n *\n *   SKIN 2 - INSPINIA - Responsive Admin Theme\n *   NAME - Inspinia Ultra\n *\n*/\nbody.skin-2 {\n  color: #565758 !important;\n}\n.skin-2 .minimalize-styl-2 {\n  margin: 14px 5px 5px 25px;\n}\n.skin-2 .navbar-top-links li:last-child {\n  margin-right: 30px;\n}\n.skin-2 .spin-icon {\n  background: #23c6c8 !important;\n}\n.skin-2 .nav-header {\n  background-color: #23c6c8;\n  background-image: url('patterns/header-profile-skin-2.png');\n}\n.skin-2.mini-navbar .nav-second-level {\n  background: #ededed;\n}\n.skin-2 .breadcrumb {\n  background: transparent;\n}\n.skin-2.fixed-nav .minimalize-styl-2 {\n  margin: 14px 5px 5px 15px;\n}\n.skin-2 .page-heading {\n  border: none;\n  background: rgba(255, 255, 255, 0.7);\n}\n.skin-2 ul.nav-second-level {\n  background-color: inherit;\n}\n.skin-2 .nav > li.active {\n  background: #e0e0e0;\n}\n.skin-2 .logo-element {\n  padding: 17px 0;\n}\n.skin-2 .nav > li > a,\n.skin-2 .welcome-message {\n  color: #edf6ff;\n}\n.skin-2 #top-search::-moz-placeholder {\n  color: #edf6ff;\n  opacity: 0.5;\n}\n.skin-2 #side-menu > li > a,\n.skin-2 .nav.nav-second-level > li > a {\n  color: #586b7d;\n}\n.skin-2 .nav > li.active > a {\n  color: #213a53;\n}\n.skin-2.mini-navbar .nav-header {\n  background: #213a53;\n}\n.skin-2 .navbar-minimalize {\n  background: #23c6c8;\n  border-color: #23c6c8;\n}\n.skin-2 .border-bottom {\n  border-bottom: none !important;\n}\n.skin-2 #top-search {\n  color: #fff;\n}\nbody.skin-2 #wrapper {\n  background-color: #ededed;\n}\n.skin-2 .navbar-static-top {\n  background: #213a53;\n}\n.fixed-nav.skin-2 .navbar-fixed-top {\n  background: #213a53;\n  border-bottom: none !important;\n}\n.skin-2 .nav-header {\n  padding: 30px 25px 30px 25px;\n}\n.skin-2 .dashboard-header {\n  background: rgba(255, 255, 255, 0.4);\n  border-bottom: none !important;\n  border-top: none;\n  padding: 20px 30px 20px 30px;\n}\n.skin-2 .wrapper-content {\n  padding: 30px 15px;\n}\n.skin-2 .dashoard-1 .wrapper-content {\n  padding: 0 30px 25px 30px;\n}\n.skin-2 .ibox-title {\n  background: rgba(255, 255, 255, 0.7);\n  border: none;\n  margin-bottom: 1px;\n}\n.skin-2 .ibox-content {\n  background: rgba(255, 255, 255, 0.4);\n  border: none !important;\n}\n.skin-2 #page-wrapper {\n  background: #f6f6f6;\n  background: -webkit-radial-gradient(center, ellipse cover, #f6f6f6 20%, #d5d5d5 100%);\n  background: -o-radial-gradient(center, ellipse cover, #f6f6f6 20%, #d5d5d5 100%);\n  background: -ms-radial-gradient(center, ellipse cover, #f6f6f6 20%, #d5d5d5 100%);\n  background: radial-gradient(ellipse at center, #f6f6f6 20%, #d5d5d5 100%);\n  -ms-filter: \"progid:DXImageTransform.Microsoft.gradient(startColorstr=#f6f6f6, endColorstr=#d5d5d5)\";\n}\n.skin-2 .ibox-title,\n.skin-2 .ibox-content {\n  border-width: 1px;\n}\n.skin-2 .ibox-content:last-child {\n  border-style: solid solid solid solid;\n}\n.skin-2 .nav > li.active {\n  border: none;\n}\n.skin-2 .nav-header a.dropdown-toggle {\n  color: #edf6ff;\n  margin-top: 10px;\n}\n.skin-2 .nav-header a.dropdown-toggle .text-muted {\n  color: #edf6ff;\n  opacity: 0.8;\n}\n.skin-2 .rounded-circle {\n  border-radius: 10px;\n}\n.skin-2 .nav.navbar-top-links > li > a:hover,\n.skin-2 .nav.navbar-top-links > li > a:focus {\n  background: #1a2d41;\n}\n.skin-2 .navbar-default .nav > li > a:hover,\n.skin-2 .navbar-default .nav > li > a:focus {\n  background: #e0e0e0;\n  color: #213a53;\n}\n.skin-2 .nav.nav-tabs > li.active > a {\n  color: #555;\n}\n.skin-2 .nav.nav-tabs > li.active {\n  background: transparent;\n}\n/*\n *\n *   SKIN 3 - INSPINIA - Responsive Admin Theme\n *   NAME - Yellow/purple\n *\n*/\n.skin-3 .minimalize-styl-2 {\n  margin: 14px 5px 5px 30px;\n}\n.skin-3 .navbar-top-links li:last-child {\n  margin-right: 30px;\n}\n.skin-3.fixed-nav .minimalize-styl-2 {\n  margin: 14px 5px 5px 15px;\n}\n.skin-3 .spin-icon {\n  background: #ecba52 !important;\n}\nbody.boxed-layout.skin-3 #wrapper {\n  background: #3e2c42;\n}\n.skin-3 .nav-header {\n  background-color: #ecba52;\n  background-image: url('patterns/header-profile-skin-3.png');\n}\n.skin-3.mini-navbar .nav-second-level {\n  background: #3e2c42;\n}\n.skin-3 .breadcrumb {\n  background: transparent;\n}\n.skin-3 .page-heading {\n  border: none;\n}\n.skin-3 ul.nav-second-level {\n  background-color: inherit;\n}\n.skin-3 .nav > li.active {\n  background: #38283c;\n}\n.fixed-nav.skin-3 .navbar-fixed-top {\n  background: #fff;\n}\n.skin-3 .nav > li > a {\n  color: #948b96;\n}\n.skin-3 .nav > li.active > a {\n  color: #fff;\n}\n.skin-3 .navbar-minimalize {\n  background: #ecba52;\n  border-color: #ecba52;\n}\nbody.skin-3 {\n  background: #3e2c42;\n}\n.skin-3 .navbar-static-top {\n  background: #ffffff;\n}\n.skin-3 .dashboard-header {\n  background: transparent;\n  border-bottom: none !important;\n  border-top: none;\n  padding: 20px 30px 10px 30px;\n}\n.skin-3 .wrapper-content {\n  padding: 30px 15px;\n}\n.skin-3 #page-wrapper {\n  background: #f4f6fa;\n}\n.skin-3 .ibox-title,\n.skin-3 .ibox-content {\n  border-width: 1px;\n}\n.skin-3 .ibox-content:last-child {\n  border-style: solid solid solid solid;\n}\n.skin-3 .nav > li.active {\n  border: none;\n}\n.skin-3 .nav-header {\n  padding: 35px 25px 25px 25px;\n}\n.skin-3 .nav-header a.dropdown-toggle {\n  color: #fff;\n  margin-top: 10px;\n}\n.skin-3 .nav-header a.dropdown-toggle .text-muted {\n  color: #fff;\n  opacity: 0.8;\n}\n.skin-3 .profile-element {\n  text-align: center;\n}\n.skin-3 .rounded-circle {\n  border-radius: 5px;\n}\n.skin-3 .navbar-default .nav > li > a:hover,\n.skin-3 .navbar-default .nav > li > a:focus {\n  background: #38283c;\n  color: #fff;\n}\n.skin-3 .nav.nav-tabs > li.active > a {\n  color: #555;\n}\n.skin-3 .nav.nav-tabs > li.active {\n  background: transparent;\n}\n/*\n *\n *   SKIN 4 - INSPINIA - Responsive Admin Theme\n *   NAME - Light-Skin\n *\n*/\nbody.light-skin {\n  background-color: #f9f9f9;\n  color: #3e5476;\n}\n.light-skin {\n  /* For handle diferent bg color in MVC version */\n}\n.light-skin .select2-container--default .select2-selection--single,\n.light-skin .select2-container--default .select2-selection--multiple,\n.light-skin .select2-container--default .select2-selection--single .select2-selection__arrow {\n  height: 2.05rem;\n}\n.light-skin .select2-container--default .select2-selection--single .select2-selection__rendered {\n  line-height: 32px;\n}\n.light-skin .navbar-fixed-top,\n.light-skin .navbar-static-top {\n  background-color: transparent;\n}\n.light-skin .Dashboard_2 .navbar.navbar-static-top,\n.light-skin .Dashboard_3 .navbar.navbar-static-top,\n.light-skin .Dashboard_4_1 .navbar.navbar-static-top,\n.light-skin .ComposeEmail .navbar.navbar-static-top,\n.light-skin .EmailView .navbar.navbar-static-top,\n.light-skin .Inbox .navbar.navbar-static-top,\n.light-skin .Metrics .navbar.navbar-static-top,\n.light-skin .Dashboard_5 .navbar.navbar-static-top {\n  background: transparent;\n}\n.light-skin.fixed-nav .navbar-fixed-top {\n  background-color: #FFFFFF;\n}\n.light-skin.mini-navbar .nav .nav-second-level {\n  background-color: #f9f9f9;\n  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.light-skin.fixed-sidebar.mini-navbar .nav .nav-second-level,\n.light-skin.canvas-menu.mini-navbar .nav .nav-second-level {\n  box-shadow: none;\n}\n.light-skin.canvas-menu nav.navbar-static-side {\n  background-color: #f9f9f9;\n}\n.light-skin.canvas-menu.mini-navbar nav.navbar-static-side {\n  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.light-skin.mini-navbar .nav-header {\n  background-color: #f9f9f9;\n}\n.light-skin #page-wrapper.gray-bg,\n.light-skin #page-wrapper.bg-muted {\n  background-color: #f9f9f9;\n}\n.light-skin .logo-element {\n  color: #7c899a;\n}\n.light-skin nav > .sidebar-collapse > ul > li a {\n  color: #7c899a;\n  padding-top: 9px;\n  padding-bottom: 9px;\n}\n.light-skin nav > .sidebar-collapse .nav-second-level li a {\n  padding-top: 7px;\n  padding-bottom: 7px;\n}\n.light-skin .nav > li.active > a {\n  color: #384d6c;\n  font-weight: 700;\n}\n.light-skin .nav-header a {\n  color: #384d6c;\n}\n.light-skin .navbar-default .nav > li > a:hover,\n.light-skin .navbar-default .nav > li > a:focus {\n  background-color: inherit;\n  color: #384d6c;\n  font-weight: 700;\n}\n.light-skin .nav-header .font-bold {\n  font-size: 12px;\n  font-weight: 700;\n  color: #384d6c;\n}\n.light-skin .nav-header .text-muted {\n  color: #8291a3 !important;\n  font-size: 12px;\n}\n.light-skin .nav-header {\n  background-color: #f9f9f9;\n  background-image: none;\n  padding: 20px 25px 20px 25px;\n}\n.light-skin .profile-element img {\n  border-radius: 6px !important;\n}\n.light-skin .nav > li.active {\n  background: #f9f9f9;\n}\n.light-skin ul.nav-second-level {\n  background: #f9f9f9;\n}\n.light-skin .dashboard-header {\n  border-bottom: none !important;\n  border-top: 0;\n  border-radius: 4px;\n  padding: 20px 20px 20px 20px;\n  margin: 10px 10px 0 10px;\n  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.light-skin .page-heading {\n  padding-bottom: 10px;\n}\n.light-skin .ibox-title {\n  background-color: transparent;\n  border: none;\n  padding-left: 5px;\n}\n.light-skin .ibox-title h5 {\n  font-size: 12px;\n}\n.light-skin .ibox-tools {\n  right: 5px;\n}\n.light-skin .ibox-tools a {\n  color: #7c899a !important;\n}\n.light-skin .ibox-heading {\n  background-color: #fff;\n  margin-bottom: 20px;\n}\n.light-skin .ibox-content {\n  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);\n  border-radius: 4px;\n  border: none;\n  background-color: #FFFFFF;\n}\n.light-skin .breadcrumb {\n  background-color: transparent;\n}\n.light-skin .minimalize-styl-2 {\n  margin: 14px 5px 5px 30px;\n}\n.light-skin .footer {\n  border: none;\n  background: none;\n}\n.light-skin .sidebar-panel {\n  background-color: #fff;\n}\n.light-skin #page-wrapper > .border-bottom,\n.light-skin .ibox.border-bottom {\n  border-bottom: transparent !important;\n}\n.light-skin .fh-breadcrumb {\n  height: calc(100% - 176px);\n}\n.light-skin.top-navigation #page-wrapper > .border-bottom {\n  border-bottom: 1px solid #e7eaec !important;\n}\n.light-skin .wrapper.white-bg {\n  background-color: transparent;\n}\n.light-skin .ibox-tools a.btn-primary {\n  color: #FFFFFF !important;\n}\n.light-skin .chat-discussion {\n  background-color: #FFFFFF;\n}\n.light-skin .ibox-footer {\n  margin-top: 4px;\n  border: none;\n  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);\n  border-radius: 4px;\n}\n.light-skin .contact-box,\n.light-skin .social-feed-box,\n.light-skin .vertical-timeline-content {\n  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1);\n  border: none;\n}\n.light-skin .navbar-default .landing_link a,\n.light-skin .navbar-default .special_link a {\n  color: #FFFFFF;\n}\nbody.md-skin {\n  font-family: \"Roboto\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  background-color: #ffffff;\n}\n.md-skin .nav-header {\n  background: url(\"patterns/4.png\") no-repeat;\n}\n.md-skin .label,\n.md-skin .badge {\n  font-family: 'Roboto';\n}\n.md-skin ul.nav-second-level {\n  background-color: inherit;\n}\n.md-skin .font-bold {\n  font-weight: 500;\n}\n.md-skin .wrapper-content {\n  padding: 30px 20px 40px;\n}\n@media (max-width: 768px) {\n  .md-skin .wrapper-content {\n    padding: 30px 0 40px;\n  }\n}\n.md-skin .page-heading {\n  border-bottom: none !important;\n  border-top: 0;\n  padding: 0 10px 20px 10px;\n  box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.34), 0 0 6px 0 rgba(0, 0, 0, 0.14);\n}\n.md-skin .full-height-layout .page-heading {\n  border-bottom: 1px solid #e7eaec !important;\n}\n.md-skin .ibox {\n  clear: both;\n  margin-bottom: 25px;\n  margin-top: 0;\n  padding: 0;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin .ibox.border-bottom {\n  border-bottom: none !important;\n}\n.md-skin .ibox-title,\n.md-skin .ibox-content {\n  border-style: none;\n}\n.md-skin .ibox-title h5 {\n  font-size: 16px;\n  font-weight: 400;\n}\n.md-skin a.close-canvas-menu {\n  color: #ffffff;\n}\n.md-skin .welcome-message {\n  color: #ffffff !important;\n  font-weight: 300;\n}\n.md-skin #top-search::-moz-placeholder {\n  color: #ffffff;\n}\n.md-skin #top-search::-webkit-input-placeholder {\n  color: #ffffff;\n}\n.md-skin #nestable-output,\n.md-skin #nestable2-output {\n  font-family: 'Roboto', lucida grande, lucida sans unicode, helvetica, arial, sans-serif;\n}\n.md-skin .landing-page {\n  font-family: 'Roboto', helvetica, arial, sans-serif;\n}\n.md-skin .landing-page.navbar-default.navbar-scroll {\n  background-color: #fff !important;\n}\n.md-skin .landing-page.navbar-default {\n  background-color: transparent !important;\n  box-shadow: none;\n}\n.md-skin .landing-page.navbar-default .nav li a {\n  font-family: 'Roboto', helvetica, arial, sans-serif;\n}\n.md-skin .nav > li > a {\n  color: #676a6c;\n  padding: 14px 20px 14px 25px;\n}\n.md-skin .nav.navbar-right > li > a {\n  color: #ffffff;\n}\n.md-skin .nav > li.active > a {\n  color: #5b5d5f;\n  font-weight: 700;\n}\n.md-skin .navbar-default .nav > li > a:hover,\n.md-skin .navbar-default .nav > li > a:focus {\n  font-weight: 700;\n  color: #5b5d5f;\n}\n.md-skin .nav .open > a,\n.md-skin .nav .open > a:hover,\n.md-skin .nav .open > a:focus {\n  background: #1ab394;\n}\n.md-skin .navbar-top-links li {\n  display: inline-table;\n}\n.md-skin .navbar-top-links .dropdown-menu li {\n  display: block;\n}\n.md-skin .pace-done .nav-header {\n  transition: all 0.4s;\n}\n.md-skin .nav > li.active {\n  background: #f8f8f9;\n}\n.md-skin .nav-second-level li a {\n  padding: 7px 10px 7px 52px;\n}\n.md-skin .nav-third-level li a {\n  padding-left: 62px;\n}\n.md-skin .navbar-top-links li a {\n  padding: 20px 10px;\n  min-height: 50px;\n}\n.md-skin .nav > li > a {\n  font-weight: 400;\n}\n.md-skin .navbar-static-side .nav > li > a:focus,\n.md-skin .navbar-static-side .nav > li > a:hover {\n  background-color: inherit;\n}\n.md-skin .navbar-top-links .dropdown-menu li a {\n  padding: 3px 20px;\n  min-height: inherit;\n}\n.md-skin .nav-header .navbar-fixed-top a {\n  color: #ffffff;\n}\n.md-skin .nav-header .text-muted {\n  color: #ffffff !important;\n}\n.md-skin .navbar-form-custom .form-control {\n  font-weight: 300;\n}\n.md-skin .mini-navbar .nav-second-level {\n  background-color: inherit;\n}\n.md-skin .mini-navbar li.active .nav-second-level {\n  left: 65px;\n}\n.md-skin .canvas-menu.mini-navbar .nav-second-level {\n  background: inherit;\n}\n.md-skin .pace-done .navbar-static-side,\n.md-skin .pace-done .nav-header,\n.md-skin .pace-done li.active,\n.md-skin .pace-done #page-wrapper,\n.md-skin .pace-done .footer {\n  -webkit-transition: all 0.4s;\n  -moz-transition: all 0.4s;\n  -o-transition: all 0.4s;\n  transition: all 0.4s;\n}\n.md-skin .navbar-fixed-top {\n  background: #fff;\n  transition-duration: 0.4s;\n  z-index: 2030;\n  border-bottom: none !important;\n}\n.md-skin .navbar-fixed-top,\n.md-skin .navbar-static-top {\n  background-color: #1ab394 !important;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin .navbar-static-side {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin #right-sidebar {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n  border: none;\n  z-index: 900;\n}\n.md-skin .white-bg .navbar-fixed-top,\n.md-skin .white-bg .navbar-static-top {\n  background: #fff !important;\n}\n.md-skin .contact-box {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n  border: none;\n}\n.md-skin .dashboard-header {\n  border-bottom: none !important;\n  border-top: 0;\n  padding: 20px 20px 20px 20px;\n  margin: 30px 20px 0 20px;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n@media (max-width: 768px) {\n  .md-skin .dashboard-header {\n    margin: 20px 0 0 0;\n  }\n}\n.md-skin ul.notes li div {\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin .file {\n  border: none;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin .mail-box {\n  background-color: #ffffff;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n  padding: 0;\n  margin-bottom: 20px;\n  border: none;\n}\n.md-skin .mail-box-header {\n  border: none;\n  background-color: #ffffff;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n  padding: 30px 20px 20px 20px;\n}\n.md-skin .mailbox-content {\n  border: none;\n  padding: 20px;\n  background: #ffffff;\n}\n.md-skin .social-feed-box {\n  border: none;\n  background: #fff;\n  margin-bottom: 15px;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin.landing-page .navbar-default {\n  background-color: transparent !important;\n  border-color: transparent;\n  transition: all 0.3s ease-in-out 0s;\n  box-shadow: none;\n}\n.md-skin.landing-page .navbar-default.navbar-scroll,\n.md-skin.landing-page.body-small .navbar-default {\n  background-color: #ffffff !important;\n}\n.md-skin.landing-page .nav > li.active {\n  background: inherit;\n}\n.md-skin.landing-page .navbar-scroll .navbar-nav > li > a {\n  padding: 20px 10px;\n}\n.md-skin.landing-page .navbar-default .nav li a {\n  font-family: 'Roboto', helvetica, arial, sans-serif;\n}\n.md-skin.landing-page .nav > li > a {\n  padding: 25px 10px 15px 10px;\n}\n.md-skin.landing-page .navbar-default .navbar-nav > li > a:hover,\n.md-skin.landing-page .navbar-default .navbar-nav > li > a:focus {\n  background: inherit;\n  color: #1ab394;\n}\n.md-skin.landing-page.body-small .nav.navbar-right > li > a {\n  color: #676a6c;\n}\n.md-skin .landing_link a,\n.md-skin .special_link a {\n  color: #ffffff !important;\n}\n.md-skin.canvas-menu.mini-navbar .nav-second-level {\n  background: #f8f8f9;\n}\n.md-skin.mini-navbar .nav-second-level {\n  background-color: #ffffff;\n  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n.md-skin.mini-navbar .nav-second-level li a {\n  padding-left: 0;\n}\n.md-skin.mini-navbar.fixed-sidebar .nav-second-level li a {\n  padding-left: 52px;\n}\n.md-skin.top-navigation .nav.navbar-right > li > a {\n  padding: 15px 20px;\n  color: #676a6c;\n}\n.md-skin.top-navigation .nav > li a:hover,\n.md-skin .top-navigation .nav > li a:focus,\n.md-skin.top-navigation .nav .open > a,\n.md-skin.top-navigation .nav .open > a:hover,\n.md-skin.top-navigation .nav .open > a:focus {\n  color: #1ab394;\n  background: #ffffff;\n}\n.md-skin.top-navigation .nav > li.active a {\n  color: #1ab394;\n  background: #ffffff;\n}\n.md-skin.fixed-nav #side-menu {\n  background-color: #fff;\n}\n.md-skin.fixed-nav #wrapper.top-navigation #page-wrapper {\n  margin-top: 0;\n}\n.md-skin.fixed-sidebar.mini-navbar #page-wrapper {\n  margin: 0 0 0 0;\n}\n.md-skin.body-small.fixed-sidebar.mini-navbar .navbar-static-side {\n  width: 220px;\n  background-color: #ffffff;\n}\n.md-skin.boxed-layout #wrapper {\n  background-color: #ffffff;\n}\n.md-skin.canvas-menu nav.navbar-static-side {\n  z-index: 2001;\n  background: #ffffff;\n  height: 100%;\n  position: fixed;\n  display: none;\n}\n@media (min-width: 768px) {\n  #page-wrapper {\n    position: inherit;\n    min-height: 100vh;\n  }\n  .navbar-static-side {\n    z-index: 2001;\n    width: 220px;\n  }\n  .navbar-top-links .dropdown-messages,\n  .navbar-top-links .dropdown-tasks,\n  .navbar-top-links .dropdown-alerts {\n    margin-left: auto;\n  }\n}\n@media (max-width: 768px) {\n  #page-wrapper {\n    position: inherit;\n    margin: 0 0 0 0;\n    min-height: 100vh;\n    width: 100%;\n  }\n  .body-small .navbar-static-side {\n    display: block;\n    z-index: 2001;\n    width: 0;\n    overflow: hidden;\n  }\n  .body-small.mini-navbar .navbar-static-side {\n    display: block;\n    overflow: visible;\n  }\n  .lock-word {\n    display: none;\n  }\n  .navbar-form-custom {\n    display: none;\n  }\n  .navbar-header {\n    display: inline;\n    float: left;\n  }\n  .sidebar-panel {\n    z-index: 2;\n    position: relative;\n    width: auto;\n    min-height: 100% !important;\n  }\n  .sidebar-content .wrapper {\n    padding-right: 0;\n    z-index: 1;\n  }\n  .fixed-sidebar.body-small .navbar-static-side {\n    display: none;\n    z-index: 2001;\n    position: fixed;\n    width: 220px;\n  }\n  .fixed-sidebar.body-small.mini-navbar .navbar-static-side {\n    display: block;\n  }\n  .ibox-tools {\n    float: none;\n    text-align: right;\n    display: block;\n  }\n  .navbar-static-side {\n    display: none;\n  }\n  body:not(.mini-navbar) {\n    -webkit-transition: background-color 500ms linear;\n    -moz-transition: background-color 500ms linear;\n    -o-transition: background-color 500ms linear;\n    -ms-transition: background-color 500ms linear;\n    transition: background-color 500ms linear;\n    background-color: #f3f3f4;\n  }\n}\n@media (max-width: 350px) {\n  .timeline-item .date {\n    text-align: left;\n    width: 110px;\n    position: relative;\n    padding-top: 30px;\n  }\n  .timeline-item .date i {\n    position: absolute;\n    top: 0;\n    left: 15px;\n    padding: 5px;\n    width: 30px;\n    text-align: center;\n    border: 1px solid #e7eaec;\n    background: #f8f8f8;\n  }\n  .timeline-item .content {\n    border-left: none;\n    border-top: 1px solid #e7eaec;\n    padding-top: 10px;\n    min-height: 100px;\n  }\n  .nav.navbar-top-links li.dropdown {\n    display: none;\n  }\n  .ibox-tools {\n    float: none;\n    text-align: left;\n    display: inline-block;\n  }\n}\n/* Only demo */\n@media (max-width: 1000px) {\n  .welcome-message {\n    display: none;\n  }\n}\n@media print {\n  nav.navbar-static-side {\n    display: none;\n  }\n  body {\n    overflow: visible !important;\n  }\n  #page-wrapper {\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "web/static/js/inspinia.js",
    "content": "/*\n *\n *   INSPINIA - Responsive Admin Theme\n *   version 2.9.3\n *\n */\n\n\n$(document).ready(function () {\n\n    // Fast fix bor position issue with Propper.js\n    // Will be fixed in Bootstrap 4.1 - https://github.com/twbs/bootstrap/pull/24092\n    //Popper.Defaults.modifiers.computeStyle.gpuAcceleration = false;\n\n\n    // Add body-small class if window less than 768px\n    if (window.innerWidth < 769) {\n        $('body').addClass('body-small')\n    } else {\n        $('body').removeClass('body-small')\n    }\n\n    // MetisMenu\n    //var sideMenu = $('#side-menu').metisMenu();\n\n    // Collapse ibox function\n    $('.collapse-link').on('click', function (e) {\n        e.preventDefault();\n        var ibox = $(this).closest('div.ibox');\n        var button = $(this).find('i');\n        var content = ibox.children('.ibox-content');\n        content.slideToggle(200);\n        button.toggleClass('fa-chevron-up').toggleClass('fa-chevron-down');\n        ibox.toggleClass('').toggleClass('border-bottom');\n        setTimeout(function () {\n            ibox.resize();\n            ibox.find('[id^=map-]').resize();\n        }, 50);\n    });\n\n    // Close ibox function\n    $('.close-link').on('click', function (e) {\n        e.preventDefault();\n        var content = $(this).closest('div.ibox');\n        content.remove();\n    });\n\n    // Fullscreen ibox function\n    $('.fullscreen-link').on('click', function (e) {\n        e.preventDefault();\n        var ibox = $(this).closest('div.ibox');\n        var button = $(this).find('i');\n        $('body').toggleClass('fullscreen-ibox-mode');\n        button.toggleClass('fa-expand').toggleClass('fa-compress');\n        ibox.toggleClass('fullscreen');\n        setTimeout(function () {\n            $(window).trigger('resize');\n        }, 100);\n    });\n\n    // Close menu in canvas mode\n    $('.close-canvas-menu').on('click', function (e) {\n        e.preventDefault();\n        $(\"body\").toggleClass(\"mini-navbar\");\n        SmoothlyMenu();\n    });\n\n    // Run menu of canvas\n    //$('body.canvas-menu .sidebar-collapse').slimScroll({\n    //    height: '100%',\n    //    railOpacity: 0.9\n    //});\n\n    // Open close right sidebar\n    $('.right-sidebar-toggle').on('click', function (e) {\n        e.preventDefault();\n        $('#right-sidebar').toggleClass('sidebar-open');\n    });\n\n    // Initialize slimscroll for right sidebar\n    //$('.sidebar-container').slimScroll({\n    //    height: '100%',\n    //    railOpacity: 0.4,\n    //    wheelStep: 10\n    //});\n\n    // Open close small chat\n    $('.open-small-chat').on('click', function (e) {\n        e.preventDefault();\n        $(this).children().toggleClass('fa-comments').toggleClass('fa-times');\n        $('.small-chat-box').toggleClass('active');\n    });\n\n    // Initialize slimscroll for small chat\n    //$('.small-chat-box .content').slimScroll({\n    //    height: '234px',\n    //    railOpacity: 0.4\n    //});\n\n    // Small todo handler\n    $('.check-link').on('click', function () {\n        var button = $(this).find('i');\n        var label = $(this).next('span');\n        button.toggleClass('fa-check-square').toggleClass('fa-square-o');\n        label.toggleClass('todo-completed');\n        return false;\n    });\n\n    // Append config box / Only for demo purpose\n    // Uncomment on server mode to enable XHR calls\n    //$.get(\"skin-config2.html\", function (data) {\n    //   if (!$('body').hasClass('no-skin-config'))\n    //       $('body').append(data);\n    //});\n\n    // Minimalize menu\n    $('.navbar-minimalize').on('click', function (event) {\n        event.preventDefault();\n        $(\"body\").toggleClass(\"mini-navbar\");\n        SmoothlyMenu();\n\n    });\n\n    // Tooltips demo\n    //$('.tooltip-demo').tooltip({\n    //    selector: \"[data-toggle=tooltip]\",\n    //    container: \"body\"\n    //});\n\n\n    // Move right sidebar top after scroll\n    $(window).scroll(function () {\n        if ($(window).scrollTop() > 0 && !$('body').hasClass('fixed-nav')) {\n            $('#right-sidebar').addClass('sidebar-top');\n        } else {\n            $('#right-sidebar').removeClass('sidebar-top');\n        }\n    });\n\n    //$(\"[data-toggle=popover]\")\n    //    .popover();\n\n    // Add slimscroll to element\n    //$('.full-height-scroll').slimscroll({\n    //    height: '100%'\n    //})\n});\n\n// Minimalize menu when screen is less than 768px\n$(window).bind(\"resize\", function () {\n    if (window.innerWidth  < 769) {\n        $('body').addClass('body-small')\n    } else {\n        $('body').removeClass('body-small')\n    }\n});\n\n// Fixed Sidebar\n$(window).bind(\"load\", function () {\n    if ($(\"body\").hasClass('fixed-sidebar')) {\n        $('.sidebar-collapse').slimScroll({\n            height: '100%',\n            railOpacity: 0.9\n        });\n    }\n});\n\n\n// check if browser support HTML5 local storage\nfunction localStorageSupport() {\n    return (('localStorage' in window) && window['localStorage'] !== null)\n}\n\n// Local Storage functions\n// Set proper body class and plugins based on user configuration\n$(document).ready(function () {\n    if (localStorageSupport()) {\n\n        var collapse = localStorage.getItem(\"collapse_menu\");\n        var fixedsidebar = localStorage.getItem(\"fixedsidebar\");\n        var fixednavbar = localStorage.getItem(\"fixednavbar\");\n        var boxedlayout = localStorage.getItem(\"boxedlayout\");\n        var fixedfooter = localStorage.getItem(\"fixedfooter\");\n\n        var body = $('body');\n\n        if (fixedsidebar == 'on') {\n            body.addClass('fixed-sidebar');\n            $('.sidebar-collapse').slimScroll({\n                height: '100%',\n                railOpacity: 0.9\n            });\n        }\n\n        if (collapse == 'on') {\n            if (body.hasClass('fixed-sidebar')) {\n                if (!body.hasClass('body-small')) {\n                    body.addClass('mini-navbar');\n                }\n            } else {\n                if (!body.hasClass('body-small')) {\n                    body.addClass('mini-navbar');\n                }\n\n            }\n        }\n\n        if (fixednavbar == 'on') {\n            $(\".navbar-static-top\").removeClass('navbar-static-top').addClass('navbar-fixed-top');\n            body.addClass('fixed-nav');\n        }\n\n        if (boxedlayout == 'on') {\n            body.addClass('boxed-layout');\n        }\n\n        if (fixedfooter == 'on') {\n            $(\".footer\").addClass('fixed');\n        }\n    }\n});\n\n// For demo purpose - animation css script\n//function animationHover(element, animation) {\n//    element = $(element);\n//    element.hover(\n//        function () {\n//            element.addClass('animated ' + animation);\n//        },\n//        function () {\n//            //wait for animation to finish before removing classes\n//            window.setTimeout(function () {\n//                element.removeClass('animated ' + animation);\n//            }, 2000);\n//        });\n//}\n\nfunction SmoothlyMenu() {\n    if (!$('body').hasClass('mini-navbar') || $('body').hasClass('body-small')) {\n        // Hide menu in order to smoothly turn on when maximize menu\n        $('#side-menu').hide();\n        // For smoothly turn on menu\n        setTimeout(\n            function () {\n                $('#side-menu').fadeIn(400);\n            }, 200);\n    } else if ($('body').hasClass('fixed-sidebar')) {\n        $('#side-menu').hide();\n        setTimeout(\n            function () {\n                $('#side-menu').fadeIn(400);\n            }, 100);\n    } else {\n        // Remove all inline style from jquery fadeIn function to reset menu state\n        $('#side-menu').removeAttr('style');\n    }\n}\n\n// Dragable panels\nfunction WinMove() {\n    var element = \"[class*=col]\";\n    var handle = \".ibox-title\";\n    var connect = \"[class*=col]\";\n    $(element).sortable(\n        {\n            handle: handle,\n            connectWith: connect,\n            tolerance: 'pointer',\n            forcePlaceholderSize: true,\n            opacity: 0.8\n        })\n        .disableSelection();\n}\n\n\n"
  },
  {
    "path": "web/static/js/language.js",
    "content": "(function ($) {\n\n\tfunction xml2json(Xml) {\n\t\tvar tempvalue, tempJson = {};\n\t\t$(Xml).each(function() {\n\t\t\tvar tagName = ($(this).attr('id') || this.tagName);\n\t\t\ttempvalue = (this.childElementCount == 0) ? this.textContent : xml2json($(this).children());\n\t\t\tswitch ($.type(tempJson[tagName])) {\n\t\t\t\tcase 'undefined':\n\t\t\t\t\ttempJson[tagName] = tempvalue;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'object':\n\t\t\t\t\ttempJson[tagName] = Array(tempJson[tagName]);\n\t\t\t\tcase 'array':\n\t\t\t\t\ttempJson[tagName].push(tempvalue);\n\t\t\t}\n\t\t});\n\t\treturn tempJson;\n\t}\n\n\tfunction setCookie (c_name, value, expiredays) {\n\t\tvar exdate = new Date();\n\t\texdate.setDate(exdate.getDate() + expiredays);\n\t\tdocument.cookie = c_name + '=' + escape(value) + ((expiredays == null) ? '' : ';expires=' + exdate.toGMTString())+ '; path='+window.nps.web_base_url+'/;';\n\t}\n\n\tfunction getCookie (c_name) {\n\t\tif (document.cookie.length > 0) {\n\t\t\tc_start = document.cookie.indexOf(c_name + '=');\n\t\t\tif (c_start != -1) {\n\t\t\t\tc_start = c_start + c_name.length + 1;\n\t\t\t\tc_end = document.cookie.indexOf(';', c_start);\n\t\t\t\tif (c_end == -1) c_end = document.cookie.length;\n\t\t\t\treturn unescape(document.cookie.substring(c_start, c_end));\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tfunction setchartlang (langobj,chartobj) {\n\t\tif ( $.type (langobj) == 'string' ) return langobj;\n\t\tif ( $.type (langobj) == 'chartobj' ) return false;\n\t\tvar flag = true;\n\t\tfor (key in langobj) {\n\t\t\tvar item = key;\n\t\t\tchildren = (chartobj.hasOwnProperty(item)) ? setchartlang (langobj[item],chartobj[item]) : setchartlang (langobj[item],undefined);\n\t\t\tswitch ($.type(children)) {\n\t\t\t\tcase 'string':\n\t\t\t\t\tif ($.type(chartobj[item]) != 'string' ) continue;\n\t\t\t\tcase 'object':\n\t\t\t\t\tchartobj[item] = (children['value'] || children);\n\t\t\t\tdefault:\n\t\t\t\t\tflag = false;\n\t\t\t}\n\t\t}\n\t\tif (flag) { return {'value':(langobj[languages['current']] || langobj[languages['default']] || 'N/A')}}\n\t}\n\n\t$.fn.cloudLang = function () {\n\t\t$.ajax({\n\t\t\ttype: 'GET',\n\t\t\turl: window.nps.web_base_url + '/static/page/languages.xml',\n\t\t\tdataType: 'xml',\n\t\t\tsuccess: function (xml) {\n\t\t\t\tlanguages['content'] = xml2json($(xml).children())['content'];\n\t\t\t\tlanguages['menu'] = languages['content']['languages'];\n\t\t\t\tlanguages['default'] = languages['content']['default'];\n\t\t\t\tlanguages['navigator'] = (getCookie ('lang') || navigator.language || navigator.browserLanguage);\n\t\t\t\tfor(var key in languages['menu']){\n\t\t\t\t\t$('#languagemenu').next().append('<li lang=\"' + key + '\"><a><img src=\"' + window.nps.web_base_url + '/static/img/flag/' + key + '.png\"> ' + languages['menu'][key] +'</a></li>');\n\t\t\t\t\tif ( key == languages['navigator'] ) languages['current'] = key;\n\t\t\t\t}\n\t\t\t\t$('#languagemenu').attr('lang',(languages['current'] || languages['default']));\n\t\t\t\t$('body').setLang ('');\n\t\t\t}\n\t\t});\n\t};\n\n\t$.fn.setLang = function (dom) {\n\t\tlanguages['current'] = $('#languagemenu').attr('lang');\n\t\tif ( dom == '' ) {\n\t\t\t$('#languagemenu span').text(' ' + languages['menu'][languages['current']]);\n\t\t\tif (languages['current'] != getCookie('lang')) setCookie('lang', languages['current']);\n\t\t\tif($(\"#table\").length>0) $('#table').bootstrapTable('refreshOptions', { 'locale': languages['current']});\n\t\t}\n\t\t$.each($(dom + ' [langtag]'), function (i, item) {\n\t\t\tvar index = $(item).attr('langtag');\n\t\t\tstring = languages['content'][index.toLowerCase()];\n\t\t\tswitch ($.type(string)) {\n\t\t\t\tcase 'string':\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'array':\n\t\t\t\t\tstring = string[Math.floor((Math.random()*string.length))];\n\t\t\t\tcase 'object':\n\t\t\t\t\tstring = (string[languages['current']] || string[languages['default']] || null);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tstring = 'Missing language string \"' + index + '\"';\n\t\t\t\t\t$(item).css('background-color','#ffeeba');\n\t\t\t}\n\t\t\tif($.type($(item).attr('placeholder')) == 'undefined') {\n\t\t\t\t$(item).text(string);\n\t\t\t} else {\n\t\t\t\t$(item).attr('placeholder', string);\n\t\t\t}\n\t\t});\n\n\t\tif ( !$.isEmptyObject(chartdatas) ) {\n\t\t\tsetchartlang(languages['content']['charts'],chartdatas);\n\t\t\tfor(var key in chartdatas){\n\t\t\t\tif ($('#'+key).length == 0) continue;\n\t\t\t\tif($.type(chartdatas[key]) == 'object')\n\t\t\t\tcharts[key] = echarts.init(document.getElementById(key));\n\t\t\t\tcharts[key].setOption(chartdatas[key], true);\n\t\t\t}\n\t\t}\n\t}\n\n})(jQuery);\n\n$(document).ready(function () {\n\t$('body').cloudLang();\n\t$('body').on('click','li[lang]',function(){\n\t\t$('#languagemenu').attr('lang',$(this).attr('lang'));\n\t\t$('body').setLang ('');\n\t});\n});\n\nvar languages = {};\nvar charts = {};\nvar chartdatas = {};\nvar postsubmit;\n\nfunction langreply(langstr) {\n    var langobj = languages['content']['reply'][langstr.replace(/[\\s,\\.\\?]*/g,\"\").toLowerCase()];\n    if ($.type(langobj) == 'undefined') return langstr\n    langobj = (langobj[languages['current']] || langobj[languages['default']] || langstr);\n    return langobj\n}\n\nfunction submitform(action, url, postdata) {\n    postsubmit = false;\n    switch (action) {\n        case 'start':\n        case 'stop':\n        case 'delete':\n            var langobj = languages['content']['confirm'][action];\n            action = (langobj[languages['current']] || langobj[languages['default']] || 'Are you sure you want to ' + action + ' it?');\n            if (! confirm(action)) return;\n            postsubmit = true;\n        case 'add':\n        case 'edit':\n            $.ajax({\n                type: \"POST\",\n                url: url,\n                data: postdata,\n                success: function (res) {\n                    alert(langreply(res.msg));\n                    if (res.status) {\n                        if (postsubmit) {document.location.reload();}else{history.back(-1);}\n                    }\n                }\n            });\n    }\n}\n\nfunction changeunit(limit) {\n    var size = \"\";\n    if (limit < 0.1 * 1024) {\n        size = limit.toFixed(2) + \"B\";\n    } else if (limit < 0.1 * 1024 * 1024) {\n        size = (limit / 1024).toFixed(2) + \"KB\";\n    } else if (limit < 0.1 * 1024 * 1024 * 1024) {\n        size = (limit / (1024 * 1024)).toFixed(2) + \"MB\";\n    } else {\n        size = (limit / (1024 * 1024 * 1024)).toFixed(2) + \"GB\";\n    }\n\n    var sizeStr = size + \"\";\n    var index = sizeStr.indexOf(\".\");\n    var dou = sizeStr.substr(index + 1, 2);\n    if (dou == \"00\") {\n        return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2);\n    }\n    return size;\n}"
  },
  {
    "path": "web/static/page/error.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>nps error</title>\n</head>\n<body>\n404 not found,power by <a href=\"//ehang.io/nps\">nps</a>\n</body>\n</html>"
  },
  {
    "path": "web/static/page/languages.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<content>\n\n\t<application>NPS</application>\n\n\t<default>en-US</default>\n\n\t<languages>\n\t\t<zh-CN>简体中文</zh-CN>\n\t\t<en-US>English</en-US>\n\t</languages>\n\n\t<lang id=\"title-admin\">\n\t\t<zh-CN>NPS - 管理</zh-CN>\n\t\t<en-US>NPS Admin</en-US>\n\t</lang>\n\t<lang id=\"title-login\">\n\t\t<zh-CN>NPS - 登录</zh-CN>\n\t\t<en-US>NPS login</en-US>\n\t</lang>\n\t<lang id=\"title-register\">\n\t\t<zh-CN>NPS - 注册</zh-CN>\n\t\t<en-US>NPS Register</en-US>\n\t</lang>\n\n\t<lang id=\"scheme-host\">\n\t\t<zh-CN>域名解析</zh-CN>\n\t\t<en-US>Host</en-US>\n\t</lang>\n\t<lang id=\"scheme-tcp\">\n\t\t<zh-CN>TCP 隧道</zh-CN>\n\t\t<en-US>TCP</en-US>\n\t</lang>\n\t<lang id=\"scheme-udp\">\n\t\t<zh-CN>UDP 隧道</zh-CN>\n\t\t<en-US>UDP</en-US>\n\t</lang>\n\t<lang id=\"scheme-httpproxy\">\n\t\t<zh-CN>HTTP 代理</zh-CN>\n\t\t<en-US>HTTP proxy</en-US>\n\t</lang>\n\t<lang id=\"scheme-socks5\">\n\t\t<zh-CN>SOCKS 代理</zh-CN>\n\t\t<en-US>SOCKS 5</en-US>\n\t</lang>\n\t<lang id=\"scheme-secret\">\n\t\t<zh-CN>私密代理</zh-CN>\n\t\t<en-US>Secret</en-US>\n\t</lang>\n\t<lang id=\"scheme-p2p\">\n\t\t<zh-CN>P2P 连接</zh-CN>\n\t\t<en-US>P2P</en-US>\n\t</lang>\n\t<lang id=\"scheme-file\">\n\t\t<zh-CN>文件访问</zh-CN>\n\t\t<en-US>File server</en-US>\n\t</lang>\n\n\t<lang id=\"page-clientlist\">\n\t\t<zh-CN>客户端列表</zh-CN>\n\t\t<en-US>Client list</en-US>\n\t</lang>\n\t<lang id=\"page-clientadd\">\n\t\t<zh-CN>新增客户端</zh-CN>\n\t\t<en-US>Add client</en-US>\n\t</lang>\n\t<lang id=\"page-clientedit\">\n\t\t<zh-CN>编辑客户端</zh-CN>\n\t\t<en-US>Edit client</en-US>\n\t</lang>\n\t<lang id=\"page-hostlist\">\n\t\t<zh-CN>主机列表</zh-CN>\n\t\t<en-US>Host list</en-US>\n\t</lang>\n\t<lang id=\"page-hostadd\">\n\t\t<zh-CN>新增主机</zh-CN>\n\t\t<en-US>Add host</en-US>\n\t</lang>\n\t<lang id=\"page-hostedit\">\n\t\t<zh-CN>编辑主机</zh-CN>\n\t\t<en-US>Edit host</en-US>\n\t</lang>\n\t<lang id=\"page-listclientid\">\n\t\t<zh-CN>隧道列表 - 客户端 ID: </zh-CN>\n\t\t<en-US>Tunnels list - Client ID: </en-US>\n\t</lang>\n\t<lang id=\"page-listtcp\">\n\t\t<zh-CN>TCP 隧道列表</zh-CN>\n\t\t<en-US>TCP list</en-US>\n\t</lang>\n\t<lang id=\"page-listudp\">\n\t\t<zh-CN>UDP 隧道列表</zh-CN>\n\t\t<en-US>UDP list</en-US>\n\t</lang>\n\t<lang id=\"page-listhttpproxy\">\n\t\t<zh-CN>HTTP 代理列表</zh-CN>\n\t\t<en-US>HTTP proxy list</en-US>\n\t</lang>\n\t<lang id=\"page-listsocks5\">\n\t\t<zh-CN>SOCKS 代理列表</zh-CN>\n\t\t<en-US>SOCKS 5 list</en-US>\n\t</lang>\n\t<lang id=\"page-listsecret\">\n\t\t<zh-CN>私密代理列表</zh-CN>\n\t\t<en-US>Secret list</en-US>\n\t</lang>\n\t<lang id=\"page-listp2p\">\n\t\t<zh-CN>P2P 连接列表</zh-CN>\n\t\t<en-US>P2P list</en-US>\n\t</lang>\n\t<lang id=\"page-listfileserver\">\n\t\t<zh-CN>文件代理列表</zh-CN>\n\t\t<en-US>File server list</en-US>\n\t</lang>\n\t<lang id=\"page-add\">\n\t\t<zh-CN>新增</zh-CN>\n\t\t<en-US>Add</en-US>\n\t</lang>\n\t<lang id=\"page-edit\">\n\t\t<zh-CN>编辑</zh-CN>\n\t\t<en-US>Edit</en-US>\n\t</lang>\n\n\t<lang id=\"word-address\">\n\t\t<zh-CN>客户端地址</zh-CN>\n\t\t<en-US>Client address</en-US>\n\t</lang>\n\t<lang id=\"word-add\">\n\t\t<zh-CN>新增</zh-CN>\n\t\t<en-US>Add</en-US>\n\t</lang>\n\t<lang id=\"word-admin\">\n\t\t<zh-CN>管理员</zh-CN>\n\t\t<en-US>Admin</en-US>\n\t</lang>\n\t<lang id=\"word-all\">\n\t\t<zh-CN>所有</zh-CN>\n\t\t<en-US>All</en-US>\n\t</lang>\n\t<lang id=\"word-bandwidth\">\n\t\t<zh-CN>带宽</zh-CN>\n\t\t<en-US>Bandwidth</en-US>\n\t</lang>\n\t<lang id=\"word-basicpassword\">\n\t\t<zh-CN>Basic 认证密码</zh-CN>\n\t\t<en-US>Basic authentication password</en-US>\n\t</lang>\n\t<lang id=\"word-basicusername\">\n\t\t<zh-CN>Basic 认证用户名</zh-CN>\n\t\t<en-US>Basic authentication username</en-US>\n\t</lang>\n\t<lang id=\"word-bridgingmode\">\n\t\t<zh-CN>桥接模式</zh-CN>\n\t\t<en-US>Bridging mode</en-US>\n\t</lang>\n\t<lang id=\"word-clientid\">\n\t\t<zh-CN>客户端 ID</zh-CN>\n\t\t<en-US>Client ID</en-US>\n\t</lang>\n\t<lang id=\"word-clientstatus\">\n\t\t<zh-CN>客户端状态</zh-CN>\n\t\t<en-US>Client status</en-US>\n\t</lang>\n\t<lang id=\"word-client\">\n\t\t<zh-CN>客户端</zh-CN>\n\t\t<en-US>Client</en-US>\n\t</lang>\n\t<lang id=\"word-close\">\n\t\t<zh-CN>关闭</zh-CN>\n\t\t<en-US>Close</en-US>\n\t</lang>\n\t<lang id=\"word-commandaccessp2ps\">\n\t\t<zh-CN>访问端命令 (SOCKS 5)</zh-CN>\n\t\t<en-US>Access command (SOCKS 5)</en-US>\n\t</lang>\n\t<lang id=\"word-commandaccessp2pt\">\n\t\t<zh-CN>访问端命令 (透明代理)</zh-CN>\n\t\t<en-US>Access command (Transparent proxy)</en-US>\n\t</lang>\n\t<lang id=\"word-commandaccessp2p\">\n\t\t<zh-CN>访问端命令 (TCP)</zh-CN>\n\t\t<en-US>Access command (TCP)</en-US>\n\t</lang>\n\t<lang id=\"word-commandaccess\">\n\t\t<zh-CN>访问端命令</zh-CN>\n\t\t<en-US>Access command</en-US>\n\t</lang>\n\t<lang id=\"word-commandclient\">\n\t\t<zh-CN>客户端命令</zh-CN>\n\t\t<en-US>Command</en-US>\n\t</lang>\n\t<lang id=\"word-compress\">\n\t\t<zh-CN>压缩</zh-CN>\n\t\t<en-US>Compress</en-US>\n\t</lang>\n\t<lang id=\"word-configurationinformation\">\n\t\t<zh-CN>配置信息</zh-CN>\n\t\t<en-US>Configuration information</en-US>\n\t</lang>\n\t<lang id=\"word-connectbyconfig\">\n\t\t<zh-CN>允许客户端通过配置文件连接</zh-CN>\n\t\t<en-US>Allow client connect by config file</en-US>\n\t</lang>\n\t<lang id=\"word-connectionport\">\n\t\t<zh-CN>客户端连接端口</zh-CN>\n\t\t<en-US>Client connection port</en-US>\n\t</lang>\n\t<lang id=\"word-connections_established\">\n\t\t<zh-CN>连接数 (已建立)</zh-CN>\n\t\t<en-US>Connections (establish)</en-US>\n\t</lang>\n\t<lang id=\"word-connect\">\n\t\t<zh-CN>连接</zh-CN>\n\t\t<en-US>Connect</en-US>\n\t</lang>\n\t<lang id=\"word-copyright\">\n\t\t<zh-CN>版权所有</zh-CN>\n\t\t<en-US>Copyright</en-US>\n\t</lang>\n\t<lang id=\"word-cpu\">\n\t\t<zh-CN>处理器</zh-CN>\n\t\t<en-US>CPU</en-US>\n\t</lang>\n\t<lang id=\"word-crypt\">\n\t\t<zh-CN>加密</zh-CN>\n\t\t<en-US>Crypt</en-US>\n\t</lang>\n\t<lang id=\"word-curconnections\">\n\t\t<zh-CN>当前连接数</zh-CN>\n\t\t<en-US>Current connections</en-US>\n\t</lang>\n\t<lang id=\"word-dashboard\">\n\t\t<zh-CN>仪表盘</zh-CN>\n\t\t<en-US>Dashboard</en-US>\n\t</lang>\n\t<lang id=\"word-exportflow\">\n\t\t<zh-CN>出口流量</zh-CN>\n\t\t<en-US>Export Flow</en-US>\n\t</lang>\n\t<lang id=\"word-false\">\n\t\t<zh-CN>否</zh-CN>\n\t\t<en-US>Flase</en-US>\n\t</lang>\n\t<lang id=\"word-flowlimit\">\n\t\t<zh-CN>流量限制</zh-CN>\n\t\t<en-US>Flow limit</en-US>\n\t</lang>\n\t<lang id=\"word-go\">\n\t\t<zh-CN>进入</zh-CN>\n\t\t<en-US>go</en-US>\n\t</lang>\n\t<lang id=\"word-help\">\n\t\t<zh-CN>使用说明</zh-CN>\n\t\t<en-US>Manual</en-US>\n\t</lang>\n\t<lang id=\"word-host\">\n\t\t<zh-CN>主机</zh-CN>\n\t\t<en-US>Host</en-US>\n\t</lang>\n\t<lang id=\"word-http\">HTTP\n\t</lang>\n\t<lang id=\"word-httpport\">\n\t\t<zh-CN>HTTP 端口</zh-CN>\n\t\t<en-US>HTTP port</en-US>\n\t</lang>\n\t<lang id=\"word-httpscert\">\n\t\t<zh-CN>HTTPS 证书</zh-CN>\n\t\t<en-US>HTTPS cert</en-US>\n\t</lang>\n\t<lang id=\"word-https\">HTTPS\n\t</lang>\n\t<lang id=\"word-httpskey\">\n\t\t<zh-CN>HTTPS 密钥</zh-CN>\n\t\t<en-US>HTTPS key</en-US>\n\t</lang>\n\t<lang id=\"word-httpsport\">\n\t\t<zh-CN>HTTPS 端口</zh-CN>\n\t\t<en-US>HTTPS port</en-US>\n\t</lang>\n\t<lang id=\"word-identificationkey\">\n\t\t<zh-CN>唯一标识密钥</zh-CN>\n\t\t<en-US>Unique identification  Key</en-US>\n\t</lang>\n\t<lang id=\"word-id\">ID\n\t</lang>\n\t<lang id=\"word-inbandwidth\">\n\t\t<zh-CN>流入带宽</zh-CN>\n\t\t<en-US>In</en-US>\n\t</lang>\n\t<lang id=\"word-inletflow\">\n\t\t<zh-CN>入口流量</zh-CN>\n\t\t<en-US>Inlet Flow</en-US>\n\t</lang>\n\t<lang id=\"word-iprestriction\">\n\t\t<zh-CN>IP 限制</zh-CN>\n\t\t<en-US>IP restriction</en-US>\n\t</lang>\n\t<lang id=\"word-load\">\n\t\t<zh-CN>负载</zh-CN>\n\t\t<en-US>Load</en-US>\n\t</lang>\n\t<lang id=\"word-localpath\">\n\t\t<zh-CN>本地路径</zh-CN>\n\t\t<en-US>Local path</en-US>\n\t</lang>\n\t<lang id=\"word-location\">\n\t\t<zh-CN>位置</zh-CN>\n\t\t<en-US>Location</en-US>\n\t</lang>\n\t<lang id=\"word-login\">\n\t\t<zh-CN>登录</zh-CN>\n\t\t<en-US>Login</en-US>\n\t</lang>\n\t<lang id=\"word-loglevel\">\n\t\t<zh-CN>日志级别</zh-CN>\n\t\t<en-US>Log level</en-US>\n\t</lang>\n\t<lang id=\"word-logout\">\n\t\t<zh-CN>退出</zh-CN>\n\t\t<en-US>Logout</en-US>\n\t</lang>\n\t<lang id=\"word-maxconnections\">\n\t\t<zh-CN>最大连接数</zh-CN>\n\t\t<en-US>Maximum connections</en-US>\n\t</lang>\n\t<lang id=\"word-maxtunnels\">\n\t\t<zh-CN>最大隧道数</zh-CN>\n\t\t<en-US>Maximum tunnels</en-US>\n\t</lang>\n\t<lang id=\"word-memory\">\n\t\t<zh-CN>内存</zh-CN>\n\t\t<en-US>Memory</en-US>\n\t</lang>\n\t<lang id=\"word-no\">\n\t\t<zh-CN>否</zh-CN>\n\t\t<en-US>No</en-US>\n\t</lang>\n\t<lang id=\"word-offline\">\n\t\t<zh-CN>离线</zh-CN>\n\t\t<en-US>Offline</en-US>\n\t</lang>\n\t<lang id=\"word-onlineclients\">\n\t\t<zh-CN>在线客户端</zh-CN>\n\t\t<en-US>Online clients</en-US>\n\t</lang>\n\t<lang id=\"word-online\">\n\t\t<zh-CN>在线</zh-CN>\n\t\t<en-US>Online</en-US>\n\t</lang>\n\t<lang id=\"word-open\">\n\t\t<zh-CN>开放</zh-CN>\n\t\t<en-US>Open</en-US>\n\t</lang>\n\t<lang id=\"word-option\">\n\t\t<zh-CN>选项</zh-CN>\n\t\t<en-US>option</en-US>\n\t</lang>\n\t<lang id=\"word-outbandwidth\">\n\t\t<zh-CN>流出带宽</zh-CN>\n\t\t<en-US>Out</en-US>\n\t</lang>\n\t<lang id=\"word-p2pport\">\n\t\t<zh-CN>P2P 端口</zh-CN>\n\t\t<en-US>P2P port</en-US>\n\t</lang>\n\t<lang id=\"word-password\">\n\t\t<zh-CN>密码</zh-CN>\n\t\t<en-US>Password</en-US>\n\t</lang>\n\t<lang id=\"word-port\">\n\t\t<zh-CN>端口</zh-CN>\n\t\t<en-US>Port</en-US>\n\t</lang>\n\t<lang id=\"word-proxytolocal\">\n\t\t<zh-CN>代理到服务器本地</zh-CN>\n\t\t<en-US>Proxy to server local</en-US>\n\t</lang>\n\t<lang id=\"word-publicvkey\">\n\t\t<zh-CN>公钥</zh-CN>\n\t\t<en-US>Public vkey</en-US>\n\t</lang>\n\t<lang id=\"word-ratelimit\">\n\t\t<zh-CN>带宽限制</zh-CN>\n\t\t<en-US>Rate limit</en-US>\n\t</lang>\n\t<lang id=\"word-readmore\">\n\t\t<zh-CN>更多说明</zh-CN>\n\t\t<en-US>Read more</en-US>\n\t</lang>\n\t<lang id=\"word-register\">\n\t\t<zh-CN>注册</zh-CN>\n\t\t<en-US>Register</en-US>\n\t</lang>\n\t<lang id=\"word-remark\">\n\t\t<zh-CN>备注</zh-CN>\n\t\t<en-US>Remark</en-US>\n\t</lang>\n\t<lang id=\"word-requestheader\">\n\t\t<zh-CN>请求头部信息修改</zh-CN>\n\t\t<en-US>Header modify</en-US>\n\t</lang>\n\t<lang id=\"word-requesthost\">\n\t\t<zh-CN>请求主机信息修改</zh-CN>\n\t\t<en-US>Host modify</en-US>\n\t</lang>\n\t<lang id=\"word-runstatus\">\n\t\t<zh-CN>运行状态</zh-CN>\n\t\t<en-US>Run status</en-US>\n\t</lang>\n\t<lang id=\"word-save\">\n\t\t<zh-CN>保存</zh-CN>\n\t\t<en-US>Save</en-US>\n\t</lang>\n\t<lang id=\"word-scheme\">\n\t\t<zh-CN>模式</zh-CN>\n\t\t<en-US>Scheme</en-US>\n\t</lang>\n\t<lang id=\"word-serverip\">\n\t\t<zh-CN>服务端 IP</zh-CN>\n\t\t<en-US>Server IP</en-US>\n\t</lang>\n\t<lang id=\"word-serveriP\">\n\t\t<zh-CN>服务端 IP</zh-CN>\n\t\t<en-US>Server IP</en-US>\n\t</lang>\n\t<lang id=\"word-serverport\">\n\t\t<zh-CN>服务端端口</zh-CN>\n\t\t<en-US>Server Port</en-US>\n\t</lang>\n\t<lang id=\"word-serverversion\">\n\t\t<zh-CN>服务端版本</zh-CN>\n\t\t<en-US>Server version</en-US>\n\t</lang>\n\t<lang id=\"word-show\">\n\t\t<zh-CN>查看</zh-CN>\n\t\t<en-US>Show</en-US>\n\t</lang>\n\t<lang id=\"word-speed\">\n\t\t<zh-CN>网速</zh-CN>\n\t\t<en-US>Speed</en-US>\n\t</lang>\n\t<lang id=\"word-status\">\n\t\t<zh-CN>状态</zh-CN>\n\t\t<en-US>Status</en-US>\n\t</lang>\n\t<lang id=\"word-stripprefix\">\n\t\t<zh-CN>访问前缀</zh-CN>\n\t\t<en-US>Strip prefix</en-US>\n\t</lang>\n\t<lang id=\"word-swapmemory\">\n\t\t<zh-CN>交换空间</zh-CN>\n\t\t<en-US>Swap memory</en-US>\n\t</lang>\n\t<lang id=\"word-systeminformation\">\n\t\t<zh-CN>系统信息</zh-CN>\n\t\t<en-US>System information</en-US>\n\t</lang>\n\t<lang id=\"word-system\">\n\t\t<zh-CN>系统</zh-CN>\n\t\t<en-US>System</en-US>\n\t</lang>\n\t<lang id=\"word-target\">\n\t\t<zh-CN>目标 (IP:端口)</zh-CN>\n\t\t<en-US>Target (IP:Port)</en-US>\n\t</lang>\n\t<lang id=\"word-tcpconnections_established\">\n\t\t<zh-CN>TCP 连接 (已建立)</zh-CN>\n\t\t<en-US>TCP connections (establish)</en-US>\n\t</lang>\n\t<lang id=\"word-tcpconnections\">\n\t\t<zh-CN>当前TCP连接数</zh-CN>\n\t\t<en-US>TCP connections</en-US>\n\t</lang>\n\t<lang id=\"word-totalclients\">\n\t\t<zh-CN>客户端总数</zh-CN>\n\t\t<en-US>Total clients</en-US>\n\t</lang>\n\t<lang id=\"word-trafficdatapersistence\">\n\t\t<zh-CN>流量数据持久化</zh-CN>\n\t\t<en-US>Traffic data persistence</en-US>\n\t</lang>\n\t<lang id=\"word-trafficstatistics\">\n\t\t<zh-CN>流量统计</zh-CN>\n\t\t<en-US>Traffic statistics</en-US>\n\t</lang>\n\t<lang id=\"word-true\">\n\t\t<zh-CN>是</zh-CN>\n\t\t<en-US>True</en-US>\n\t</lang>\n\t<lang id=\"word-tunnel\">\n\t\t<zh-CN>隧道</zh-CN>\n\t\t<en-US>Tunnel</en-US>\n\t</lang>\n\t<lang id=\"word-type\">\n\t\t<zh-CN>连接类型</zh-CN>\n\t\t<en-US>Connect type</en-US>\n\t</lang>\n\t<lang id=\"word-udpconnections_established\">\n\t\t<zh-CN>UDP 连接 (已建立)</zh-CN>\n\t\t<en-US>UDP connections (establish)</en-US>\n\t</lang>\n\t<lang id=\"word-unit\">\n\t\t<zh-CN>单位</zh-CN>\n\t\t<en-US>Flow limit</en-US>\n\t</lang>\n\t<lang id=\"word-urlroute\">\n\t\t<zh-CN>URL 路由</zh-CN>\n\t\t<en-US>Url router</en-US>\n\t</lang>\n\t<lang id=\"word-usecase\">\n\t\t<zh-CN>使用场景</zh-CN>\n\t\t<en-US>Use Case</en-US>\n\t</lang>\n\t<lang id=\"word-username\">\n\t\t<zh-CN>用户名</zh-CN>\n\t\t<en-US>Username</en-US>\n\t</lang>\n\t<lang id=\"word-user\">\n\t\t<zh-CN>用户</zh-CN>\n\t\t<en-US>User</en-US>\n\t</lang>\n\t<lang id=\"word-verifykey\">\n\t\t<zh-CN>唯一验证密钥</zh-CN>\n\t\t<en-US>Unique verify Key</en-US>\n\t</lang>\n\t<lang id=\"word-version\">\n\t\t<zh-CN>版本</zh-CN>\n\t\t<en-US>Version</en-US>\n\t</lang>\n\t<lang id=\"word-virtualmemory\">\n\t\t<zh-CN>虚拟内存</zh-CN>\n\t\t<en-US>Virtual memory</en-US>\n\t</lang>\n\t<lang id=\"word-webpassword\">\n\t\t<zh-CN>Web登陆密码</zh-CN>\n\t\t<en-US>Password of Web login</en-US>\n\t</lang>\n\t<lang id=\"word-webusername\">\n\t\t<zh-CN>Web登陆用户名</zh-CN>\n\t\t<en-US>Username of Web login</en-US>\n\t</lang>\n\t<lang id=\"word-welcome\">\n\t\t<zh-CN>欢迎使用</zh-CN>\n\t\t<en-US>Welcome to use</en-US>\n\t</lang>\n\t<lang id=\"word-yes\">\n\t\t<zh-CN>是</zh-CN>\n\t\t<en-US>Yes</en-US>\n\t</lang>\n\t<lang id=\"word-\">\n\t</lang>\n\n\t<lang id=\"info-autogenerated\">\n\t\t<zh-CN>唯一值，不填将自动生成</zh-CN>\n\t\t<en-US>Unique, non-filling will be generated automatically</en-US>\n\t</lang>\n\t<lang id=\"info-casefile\">\n\t\t<zh-CN>通提供一个公网可访问的本地文件服务，此模式仅客户端使用配置文件模式方可启动。</zh-CN>\n\t\t<en-US>Provide a local file service accessible to the public network, which can only be started by the client using the profile mode.</en-US>\n\t</lang>\n\t<lang id=\"info-casehttpproxy\">\n\t\t<zh-CN>将公网服务器1.1.1.1的8004端口作为HTTP代理，访问内网网站。</zh-CN>\n\t\t<en-US>Use port 8004 of public server 1.1.1.1 as HTTP proxy to visit intranet site.</en-US>\n\t</lang>\n\t<lang id=\"info-casep2p\">\n\t\t<zh-CN>流量不经过公网服务器，受nat类型影响较大，不能保证100%成功，支持大部分nat类型。</zh-CN>\n\t\t<en-US>The traffic does not pass through the public network server, which is greatly affected by the NAT type, and cannot guarantee 100% success. Most NAT types are supported. A client is also required to provide a port for access to the access side.</en-US>\n\t</lang>\n\t<lang id=\"info-casesecret\">\n\t\t<zh-CN>无需新增端口,实现访问内网服务器10.1.50.2的22端口,可防止其他人连接。还需要一个客户端作为访问端提供一个端口进行访问。</zh-CN>\n\t\t<en-US>There is no need to add a new port to access port 22 of intranet server 10.1.50.2, which can prevent other people from connecting. A client is also required to provide a port for access to the access side.</en-US>\n\t</lang>\n\t<lang id=\"info-casesocks5\">\n\t\t<zh-CN>将公网服务器1.1.1.1的8003端口作为SOCKS5代理，访问内网任意设备或者资源。</zh-CN>\n\t\t<en-US>Use port 8003 of public server 1.1.1.1 as Socks5 proxy to access any device or resource in the Intranet.</en-US>\n\t</lang>\n\t<lang id=\"info-casetcp\">\n\t\t<zh-CN>通过公网服务器1.1.1.1的8001端口，连接内网机器10.1.50.101的22端口，实现SSH连接。</zh-CN>\n\t\t<en-US>Connect port 8001 of public server 1.1.1.1 to port 22 of Intranet machine 10.1.50.101 to realize SSH connection.</en-US>\n\t</lang>\n\t<lang id=\"info-caseudp\">\n\t\t<zh-CN>通过公网服务器1.1.1.1的53端口，访问内网机器10.1.50.101的53端口，使用DNS服务。</zh-CN>\n\t\t<en-US>Through port 53 of public server 1.1.1.1, access port 53 of Intranet machine 10.1.50.101, and use DNS service.</en-US>\n\t</lang>\n\t<lang id=\"info-createaccount\">\n\t\t<zh-CN>创建账号以进行管理</zh-CN>\n\t\t<en-US>Create account to see it in action.</en-US>\n\t</lang>\n\t<lang id=\"info-haveaccount\">\n\t\t<zh-CN>已经有帐号了？</zh-CN>\n\t\t<en-US>Already have an account?</en-US>\n\t</lang>\n\t<lang id=\"info-header\">\n\t\t<zh-CN>冒号分割，多个头部请填写多行</zh-CN>\n\t\t<en-US>Colon separated, multiple lines please fill in</en-US>\n\t</lang>\n\t<lang id=\"info-identificationkey\">\n\t\t<zh-CN>P2P连接和私密代理模式需要</zh-CN>\n\t\t<en-US>When P2P or Secret</en-US>\n\t</lang>\n\t<lang id=\"info-feature1\">\n\t\t<zh-CN>协议支持全面，兼容几乎所有常用协议，例如tcp、udp、http(s)、socks5、p2p、http代理...</zh-CN>\n\t\t<en-US>Comprehensive protocol support, compatible with almost all commonly used protocols, such as tcp, udp, http(s), socks5, p2p, http proxy ...</en-US>\n\t</lang>\n\t<lang id=\"info-feature2\">\n\t\t<zh-CN>全平台兼容(linux、windows、macos、群辉等)，支持一键安装为系统服务</zh-CN>\n\t\t<en-US>Full platform compatibility (linux, windows, macos, Qunhui, etc.), support installation as a system service simply.</en-US>\n\t</lang>\n\t<lang id=\"info-feature3\">\n\t\t<zh-CN>控制全面，同时支持服务端和客户端控制</zh-CN>\n\t\t<en-US>Comprehensive control, both client and server control are allowed.</en-US>\n\t</lang>\n\t<lang id=\"info-feature4\">\n\t\t<zh-CN>https集成，支持将后端代理和web服务转成https，同时支持多证书</zh-CN>\n\t\t<en-US>Https integration, support to convert backend proxy and web services to https, and support multiple certificates.</en-US>\n\t</lang>\n\t<lang id=\"info-feature5\">\n\t\t<zh-CN>操作简单，只需简单的配置即可在web ui上完成其余操作</zh-CN>\n\t\t<en-US>Just simple configuration on web ui can complete most requirements.</en-US>\n\t</lang>\n\t<lang id=\"info-feature6\">\n\t\t<zh-CN>展示信息全面，流量、系统信息、即时带宽、客户端版本等</zh-CN>\n\t\t<en-US>Complete information display, such as traffic, system information, real-time bandwidth, client version, etc.</en-US>\n\t</lang>\n\t<lang id=\"info-feature7\">\n\t\t<zh-CN>扩展功能强大，该有的都有了（缓存、压缩、加密、流量限制、带宽限制、端口复用等等）</zh-CN>\n\t\t<en-US>Powerful extension functions, everything is available (cache, compression, encryption, traffic limit, bandwidth limit, port reuse, etc.)</en-US>\n\t</lang>\n\t<lang id=\"info-feature8\">\n\t\t<zh-CN>域名解析具备自定义header、404页面配置、host修改、站点保护、URL路由、泛解析等功能</zh-CN>\n\t\t<en-US>Domain name resolution has functions such as custom headers, 404 page configuration, host modification, site protection, URL routing, and pan-resolution.</en-US>\n\t</lang>\n\t<lang id=\"info-feature9\">\n\t\t<zh-CN>服务端支持多用户和用户注册功能</zh-CN>\n\t\t<en-US>Multi-user and user registration support on server.</en-US>\n\t</lang>\n\t<lang id=\"info-noaccount\">\n\t\t<zh-CN>还没有有帐号？</zh-CN>\n\t\t<en-US>Do not have an account?</en-US>\n\t</lang>\n\t<lang id=\"info-onlyproxy\">\n\t\t<zh-CN>仅限Socks5、Web、HTTP转发代理</zh-CN>\n\t\t<en-US>Only socks5 , web, HTTP forward proxy</en-US>\n\t</lang>\n\t<lang id=\"info-register\">\n\t\t<zh-CN>注册到 NPS</zh-CN>\n\t\t<en-US>Register to NPS</en-US>\n\t</lang>\n\t<lang id=\"info-suchashost\">\n\t\t<zh-CN>例如 a.proxy.com</zh-CN>\n\t\t<en-US>such as a.proxy.com</en-US>\n\t</lang>\n\t<lang id=\"info-suchasip\">\n\t\t<zh-CN>例如 0.0.0.0</zh-CN>\n\t\t<en-US>such as 0.0.0.0</en-US>\n\t</lang>\n\t<lang id=\"info-suchasiplist\">\n\t\t<zh-CN>例如&#10;10.1.50.203:80&#10;10.1.50.202:80</zh-CN>\n\t\t<en-US>such as&#10;10.1.50.203:80&#10;10.1.50.202:80</en-US>\n\t</lang>\n\t<lang id=\"info-suchaslocalpath\">\n\t\t<zh-CN>例如 /tmp</zh-CN>\n\t\t<en-US>such as /tmp</en-US>\n\t</lang>\n\t<lang id=\"info-suchasport\">\n\t\t<zh-CN>例如 8024</zh-CN>\n\t\t<en-US>such as 8024</en-US>\n\t</lang>\n\t<lang id=\"info-suchasstripprefix\">\n\t\t<zh-CN>例如 static</zh-CN>\n\t\t<en-US>such as static</en-US>\n\t</lang>\n\t<lang id=\"info-tagline\">\n\t\t<zh-CN>一款轻量级、高性能、功能强大的内网穿透代理服务器</zh-CN>\n\t\t<en-US>A lightweight, high-performance, powerful intranet reverse proxy server</en-US>\n\t</lang>\n\t<lang id=\"info-targethost\">\n\t\t<zh-CN>分行填写多个目标可实现负载均衡</zh-CN>\n\t\t<en-US>Line break if load balancing</en-US>\n\t</lang>\n\t<lang id=\"info-targettunnel\">\n\t\t<zh-CN>代理到本地可以只填写端口号，只有TCP模式支持负载均衡</zh-CN>\n\t\t<en-US>Can only fill in ports if it is local machine proxy, only tcp supports load balancing</en-US>\n\t</lang>\n\t<lang id=\"info-unrestricted\">\n\t\t<zh-CN>留空表示不受限制</zh-CN>\n\t\t<en-US>Empty means to be unrestricted</en-US>\n\t</lang>\n\n\t<confirm>\n\t\t<lang id=\"delete\">\n\t\t\t<zh-CN>你确定你要删除它吗？</zh-CN>\n\t\t\t<en-US>Are you sure you want to delete it?</en-US>\n\t\t</lang>\n\t\t<lang id=\"start\">\n\t\t\t<zh-CN>你确定你要启动它吗？</zh-CN>\n\t\t\t<en-US>Are you sure you want to start it?</en-US>\n\t\t</lang>\n\t\t<lang id=\"stop\">\n\t\t\t<zh-CN>你确定你要停止它吗？</zh-CN>\n\t\t\t<en-US>Are you sure you want to stop it?</en-US>\n\t\t</lang>\n\t</confirm>\n\n\t<reply>\n\t\t<lang id=\"adderrortheclientcannotbefound\">\n\t\t\t<zh-CN>添加错误，找不到客户端</zh-CN>\n\t\t\t<en-US>Add error, the client can not be found</en-US>\n\t\t</lang>\n\t\t<lang id=\"addfailhosthasexist\">\n\t\t\t<zh-CN>添加失败，主机已存在</zh-CN>\n\t\t\t<en-US>Add fail, host has exist</en-US>\n\t\t</lang>\n\t\t<lang id=\"addsuccess\">\n\t\t\t<zh-CN>添加成功</zh-CN>\n\t\t\t<en-US>Add success</en-US>\n\t\t</lang>\n\t\t<lang id=\"deleteerror\">\n\t\t\t<zh-CN>删除出错</zh-CN>\n\t\t\t<en-US>Delete error</en-US>\n\t\t</lang>\n\t\t<lang id=\"deletesuccess\">\n\t\t\t<zh-CN>删除成功</zh-CN>\n\t\t\t<en-US>Delete success</en-US>\n\t\t</lang>\n\t\t<lang id=\"hosthasexist\">\n\t\t\t<zh-CN>主机已存在</zh-CN>\n\t\t\t<en-US>Host has exist</en-US>\n\t\t</lang>\n\t\t<lang id=\"modifiederrortheclientisnotexist\">\n\t\t\t<zh-CN>修改出错，客户端不存在</zh-CN>\n\t\t\t<en-US>Modified error, the client is not exist</en-US>\n\t\t</lang>\n\t\t<lang id=\"modifiedfail\">\n\t\t\t<zh-CN>修改失败</zh-CN>\n\t\t\t<en-US>Modified fail</en-US>\n\t\t</lang>\n\t\t<lang id=\"modifiedsuccess\">\n\t\t\t<zh-CN>修改成功</zh-CN>\n\t\t\t<en-US>Modified success</en-US>\n\t\t</lang>\n\t\t<lang id=\"savesuccess\">\n\t\t\t<zh-CN>保存成功</zh-CN>\n\t\t\t<en-US>Save success</en-US>\n\t\t</lang>\n\t\t<lang id=\"starterror\">\n\t\t\t<zh-CN>启动出错</zh-CN>\n\t\t\t<en-US>Start error</en-US>\n\t\t</lang>\n\t\t<lang id=\"startsuccess\">\n\t\t\t<zh-CN>启动成功</zh-CN>\n\t\t\t<en-US>Start success</en-US>\n\t\t</lang>\n\t\t<lang id=\"stoperror\">\n\t\t\t<zh-CN>停止出错</zh-CN>\n\t\t\t<en-US>Stop error</en-US>\n\t\t</lang>\n\t\t<lang id=\"stopsuccess\">\n\t\t\t<zh-CN>停止成功</zh-CN>\n\t\t\t<en-US>Stop success</en-US>\n\t\t</lang>\n\t\t<lang id=\"Thenumberoftunnelsexceedsthelimit\">\n\t\t\t<zh-CN>隧道数量超过限制</zh-CN>\n\t\t\t<en-US>The number of tunnels exceeds the limit</en-US>\n\t\t</lang>\n\t\t<lang id=\"theportcannotbeopenedbecauseitmayhasbeenoccupiedorisnolongerallowed\">\n\t\t\t<zh-CN>无法打开端口，因为它可能已被占用或不再被允许。</zh-CN>\n\t\t\t<en-US>The port cannot be opened because it may has been occupied or is no longer allowed.</en-US>\n\t\t</lang>\n\t\t<lang id=\"vkeyduplicatepleasereset\">\n\t\t\t<zh-CN>唯一验证密钥重复，请重新设置</zh-CN>\n\t\t\t<en-US>Unique verify key duplicate, please reset</en-US>\n\t\t</lang>\n\t\t<lang id=\"webloginusernameduplicatepleasereset\">\n\t\t\t<zh-CN>Web登陆用户名重复，请重新设置</zh-CN>\n\t\t\t<en-US>Web login username duplicate, please reset</en-US>\n\t\t</lang>\n\t</reply>\n\n\t<charts>\n\t\t<lang id=\"load\">\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>平均负载 1</zh-CN>\n\t\t\t\t\t\t<en-US>Load avg 1</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t\t<array id=\"1\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>平均负载 5</zh-CN>\n\t\t\t\t\t\t<en-US>Load avg 5</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t\t<array id=\"2\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>平均负载 15</zh-CN>\n\t\t\t\t\t\t<en-US>Load avg 15</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t\t<lang id=\"cpu\">\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>处理器</zh-CN>\n\t\t\t\t\t\t<en-US>CPU</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t\t<lang id=\"memory\">\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>虚拟内存</zh-CN>\n\t\t\t\t\t\t<en-US>Virtual memory</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t\t<array id=\"1\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>交换空间</zh-CN>\n\t\t\t\t\t\t<en-US>Swap memory</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t\t<lang id=\"connections\">\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>TCP</name>\n\t\t\t\t</array>\n\t\t\t\t<array id=\"1\">\n\t\t\t\t\t<name>UDP</name>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t\t<lang id=\"bandwidth\">\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>入口</zh-CN>\n\t\t\t\t\t\t<en-US>In</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t\t<array id=\"1\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>出口</zh-CN>\n\t\t\t\t\t\t<en-US>Out</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t\t<lang id=\"flow\">\n\t\t\t<legend>\n\t\t\t\t<data>\n\t\t\t\t\t<array id=\"0\">\n\t\t\t\t\t\t<zh-CN>出口</zh-CN>\n\t\t\t\t\t\t<en-US>Out</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"1\">\n\t\t\t\t\t\t<zh-CN>入口</zh-CN>\n\t\t\t\t\t\t<en-US>In</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t</data>\n\t\t\t</legend>\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>流量统计</zh-CN>\n\t\t\t\t\t\t<en-US>Traffic</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t\t<data>\n\t\t\t\t\t\t<array id=\"0\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>入口</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>In</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"1\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>出口</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>Out</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t</data>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t\t<lang id=\"counts\">\n\t\t\t<legend>\n\t\t\t\t<data>\n\t\t\t\t\t<array id=\"0\">\n\t\t\t\t\t\t<zh-CN>域名转发</zh-CN>\n\t\t\t\t\t\t<en-US>HOST</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"1\">\n\t\t\t\t\t\t<zh-CN>TCP 隧道</zh-CN>\n\t\t\t\t\t\t<en-US>TCP</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"2\">\n\t\t\t\t\t\t<zh-CN>UDP 隧道</zh-CN>\n\t\t\t\t\t\t<en-US>UDP</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"3\">\n\t\t\t\t\t\t<zh-CN>HTTP 代理</zh-CN>\n\t\t\t\t\t\t<en-US>HTTP</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"4\">\n\t\t\t\t\t\t<zh-CN>SOCKS 代理</zh-CN>\n\t\t\t\t\t\t<en-US>SOCKS 5</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"5\">\n\t\t\t\t\t\t<zh-CN>私密代理</zh-CN>\n\t\t\t\t\t\t<en-US>Secret</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t\t<array id=\"6\">\n\t\t\t\t\t\t<zh-CN>P2P 连接</zh-CN>\n\t\t\t\t\t\t<en-US>P2P</en-US>\n\t\t\t\t\t</array>\n\t\t\t\t</data>\n\t\t\t</legend>\n\t\t\t<series>\n\t\t\t\t<array id=\"0\">\n\t\t\t\t\t<name>\n\t\t\t\t\t\t<zh-CN>协议类型</zh-CN>\n\t\t\t\t\t\t<en-US>Tcheme type</en-US>\n\t\t\t\t\t</name>\n\t\t\t\t\t<data>\n\t\t\t\t\t\t<array id=\"0\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>域名转发</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>HOST</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"1\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>TCP 隧道</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>TCP</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"2\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>UDP 隧道</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>UDP</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"3\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>HTTP 代理</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>HTTP</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"4\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>SOCKS 代理</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>SOCKS 5</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"5\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>私密代理</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>Secret</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<array id=\"6\">\n\t\t\t\t\t\t\t<name>\n\t\t\t\t\t\t\t\t<zh-CN>P2P 连接</zh-CN>\n\t\t\t\t\t\t\t\t<en-US>P2P</en-US>\n\t\t\t\t\t\t\t</name>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t</data>\n\t\t\t\t</array>\n\t\t\t</series>\n\t\t</lang>\n\t</charts>\n</content>\n"
  },
  {
    "path": "web/views/client/add.html",
    "content": "<div class=\"row\">\n    <div class=\"col-md-12 col-md-auto\">\n        <div class=\"ibox float-e-margins\">\n            <h3 class=\"ibox-title\" langtag=\"page-clientadd\"></h3>\n            <div class=\"ibox-content\">\n                <form class=\"form-horizontal\">\n                    <div class=\"form-group\" id=\"remark\">\n                        <label class=\"control-label font-bold\" langtag=\"word-remark\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"remark\" placeholder=\"\" langtag=\"word-remark\">\n                        </div>\n                    </div>\n                {{if eq true .allow_flow_limit}}\n                    <div class=\"form-group\" id=\"flow_limit\">\n                        <label class=\"control-label font-bold\" langtag=\"word-flowlimit\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"flow_limit\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"word-unit\"></span>: M\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_rate_limit}}\n                    <div class=\"form-group\" id=\"rate_limit\">\n                        <label class=\"control-label font-bold\" langtag=\"word-ratelimit\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"rate_limit\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"word-unit\"></span>: KB/S\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_connection_num_limit}}\n                    <div class=\"form-group\" id=\"max_conn\">\n                        <label class=\"control-label font-bold\" langtag=\"word-maxconnections\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"max_conn\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_tunnel_num_limit}}\n                    <div class=\"form-group\" id=\"max_tunnel\">\n                        <label class=\"control-label font-bold\" langtag=\"word-maxtunnels\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"max_tunnel\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\" id=\"u\">\n                        <label class=\"control-label font-bold\" langtag=\"word-basicusername\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"u\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-onlyproxy\"></span>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"p\">\n                        <label class=\"control-label font-bold\" langtag=\"word-basicpassword\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"p\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-onlyproxy\"></span>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"vkey\">\n                        <label class=\"control-label font-bold\" langtag=\"word-verifyKey\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"vkey\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-autogenerated\"></span>\n                        </div>\n                    </div>\n                {{if eq true .allow_user_login}}\n                    <div class=\"form-group\" id=\"web_username\">\n                        <label class=\"control-label font-bold\" langtag=\"word-webusername\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"web_username\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"web_password\">\n                        <label class=\"control-label font-bold\" langtag=\"word-webpassword\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"web_password\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\" id=\"config_conn_allow\">\n                        <label class=\"control-label font-bold\" langtag=\"word-connectbyconfig\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"config_conn_allow\">\n                                <option value=\"1\" langtag=\"word-yes\"></option>\n                                <option value=\"0\" langtag=\"word-no\"></option>\n                            </select>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"compress\">\n                        <label class=\"control-label font-bold\" langtag=\"word-compress\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"compress\">\n                                <option value=\"0\" langtag=\"word-no\"></option>\n                                <option value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"compress\">\n                        <label class=\"control-label font-bold\" langtag=\"word-crypt\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"crypt\">\n                                <option value=\"0\" langtag=\"word-no\"></option>\n                                <option value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                    <div class=\"hr-line-dashed\"></div>\n                    <div class=\"form-group\">\n                        <div class=\"col-sm-4 col-sm-offset-2\">\n                            <button class=\"btn btn-success\" type=\"button\" onclick=\"submitform('add', '{{.web_base_url}}/client/add', $('form').serializeArray())\">\n                                <i class=\"fa fa-fw fa-lg fa-check-circle\"></i> <span langtag=\"word-add\"></span>\n                            </button>\n                        </div>\n                    </div>\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "web/views/client/edit.html",
    "content": "<div class=\"row\">\n    <div class=\"col-md-12 col-md-auto\">\n        <div class=\"ibox float-e-margins\">\n            <h3 class=\"ibox-title\" langtag=\"page-clientedit\"></h3>\n            <div class=\"ibox-content\">\n                <form class=\"form-horizontal\">\n                    <input type=\"hidden\" name=\"id\" value=\"{{.c.Id}}\">\n                    <div class=\"form-group\" id=\"remark\">\n                        <label class=\"control-label font-bold\" langtag=\"word-remark\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.Remark}}\" type=\"text\" name=\"remark\" placeholder=\"\"  langtag=\"word-remark\">\n                        </div>\n                    </div>\n                {{if eq true .isAdmin}}\n                {{if eq true .allow_flow_limit}}\n                    <div class=\"form-group\" id=\"flow_limit\">\n                        <label class=\"control-label font-bold\" langtag=\"word-flowlimit\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.Flow.FlowLimit}}\" type=\"text\" name=\"flow_limit\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"word-unit\"></span>: M\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_rate_limit}}\n\n                    <div class=\"form-group\" id=\"rate_limit\">\n                        <label class=\"control-label font-bold\" langtag=\"word-ratelimit\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.RateLimit}}\" type=\"text\" name=\"rate_limit\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"word-unit\"></span>: KB/S\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_connection_num_limit}}\n\n                    <div class=\"form-group\" id=\"max_conn\">\n                        <label class=\"control-label font-bold\" langtag=\"word-maxconnections\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.MaxConn}}\" type=\"text\" name=\"max_conn\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_tunnel_num_limit}}\n                    <div class=\"form-group\" id=\"max_tunnel\">\n                        <label class=\"control-label font-bold\" langtag=\"word-maxtunnels\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.MaxTunnelNum}}\" type=\"text\" name=\"max_tunnel\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                {{end}}\n                    <div class=\"form-group\" id=\"u\">\n                        <label class=\"control-label font-bold\" langtag=\"word-basicusername\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.Cnf.U}}\" type=\"text\" name=\"u\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-onlyproxy\"></span>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"p\">\n                        <label class=\"control-label font-bold\" langtag=\"word-basicpassword\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.Cnf.P}}\" type=\"text\" name=\"p\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-onlyproxy\"></span>\n                        </div>\n                    </div>\n                {{if eq true .isAdmin}}\n                    <div class=\"form-group\" id=\"vkey\">\n                        <label class=\"control-label font-bold\" langtag=\"word-verifyKey\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.VerifyKey}}\" type=\"text\" name=\"vkey\" placeholder=\"\" langtag=\"info-unrestricted\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-autogenerated\"></span>\n                        </div>\n                    </div>\n                {{end}}\n                {{if eq true .allow_user_login}}\n                {{if or (eq true .allow_user_change_username) (eq true .isAdmin)}}\n                    <div class=\"form-group\" id=\"web_username\">\n                        <label class=\"control-label font-bold\" langtag=\"word-webusername\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.WebUserName}}\" type=\"text\" name=\"web_username\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\" id=\"web_password\">\n                        <label class=\"control-label font-bold\" langtag=\"word-webpassword\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"{{.c.WebPassword}}\" type=\"text\" name=\"web_password\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\" id=\"config_conn_allow\">\n                        <label class=\"control-label font-bold\" langtag=\"word-connectbyconfig\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"config_conn_allow\">\n                                <option {{if eq true .c.ConfigConnAllow}}selected{{end}}  value=\"1\" langtag=\"word-yes\"></option>\n                                <option {{if eq false .c.ConfigConnAllow}}selected{{end}} value=\"0\" langtag=\"word-no\"></option>\n                            </select>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"compress\">\n                        <label class=\"control-label font-bold\" langtag=\"word-compress\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"compress\">\n                                <option {{if eq false .c.Cnf.Compress}}selected{{end}} value=\"0\" langtag=\"word-no\"></option>\n                                <option {{if eq true .c.Cnf.Compress}}selected{{end}}  value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"compress\">\n                        <label class=\"control-label font-bold\" langtag=\"word-crypt\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"crypt\">\n                                <option {{if eq false .c.Cnf.Crypt}}selected{{end}} value=\"0\" langtag=\"word-no\"></option>\n                                <option {{if eq true .c.Cnf.Crypt}}selected{{end}} value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                    <div class=\"hr-line-dashed\"></div>\n                    <div class=\"form-group\">\n                        <div class=\"col-sm-4 col-sm-offset-2\">\n                            <button class=\"btn btn-success\" type=\"button\" onclick=\"submitform('add', '{{.web_base_url}}/client/edit', $('form').serializeArray())\"> <i\n                                    class=\"fa fa-fw fa-lg fa-save\"></i><span langtag=\"word-save\"></span>\n                            </button>\n                        </div>\n                    </div>\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "web/views/client/list.html",
    "content": "<div class=\"wrapper wrapper-content animated fadeInRight\">\n\n    <div class=\"row\">\n        <div class=\"col-lg-12\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"page-clientlist\"></h5>\n\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n            <div class=\"content\">\n            {{if eq true .isAdmin}}\n\n                <div class=\"table-responsive\">\n                    <div id=\"toolbar\">\n                        <a href=\"{{.web_base_url}}/client/add\" class=\"btn btn-primary dim\">\n                        <i class=\"fa fa-fw fa-lg fa-plus\"></i> <span langtag=\"word-add\"></span></a>\n                    </div>\n                    <table id=\"taskList_table\" class=\"table-striped table-hover\" data-mobile-responsive=\"true\"></table>\n                </div>\n            </div>\n            {{end}}\n                <div class=\"ibox-content\">\n\n                    <table id=\"table\"></table>\n\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script>\n    /*bootstrap table*/\n    $('#table').bootstrapTable({\n        toolbar: \"#toolbar\",\n        method: 'post', // 服务器数据的请求方式 get or post\n        url: \"{{.web_base_url}}/client/list\", // 服务器数据的加载地址\n        contentType: \"application/x-www-form-urlencoded\",\n        striped: true, // 设置为true会有隔行变色效果\n        search: true,\n        showHeader: true,\n        showColumns: true,\n        showRefresh: true,\n        pagination: true,//分页\n        sidePagination: 'server',//服务器端分页\n        pageNumber: 1,\n        pageList: [5, 10, 20, 50],//分页步进值\n        detailView: true,\n        smartDisplay: true, // 智能显示 pagination 和 cardview 等\n        onExpandRow: function () {$('body').setLang ('.detail-view');},\n        onPostBody: function (data) { if ($(this)[0].locale != undefined ) $('body').setLang ('#table'); },\n        detailFormatter: function (index, row, element) {\n            return '<b langtag=\"word-maxconnections\"></b>: ' + row.MaxConn + '&emsp;'\n                + '<b langtag=\"word-curconnections\"></b>: ' + row.NowConn + '&emsp;'\n                + '<b langtag=\"word-flowlimit\"></b>: ' + row.Flow.FlowLimit + 'm&emsp;'\n                + '<b langtag=\"word-ratelimit\"></b>: ' + row.RateLimit + 'kb/s&emsp;'\n                + '<b langtag=\"word-maxtunnels\"></b>: ' + row.MaxTunnelNum + '&emsp;<br/><br/>'\n                + '<b langtag=\"word-webusername\"></b>: ' + row.WebUserName + '&emsp;'\n                + '<b langtag=\"word-webpassword\"></b>: ' + row.WebPassword + '&emsp;'\n                + '<b langtag=\"word-basicusername\"></b>: ' + row.Cnf.U + '&emsp;'\n                + '<b langtag=\"word-basicpassword\"></b>: ' + row.Cnf.P + '&emsp;<br/><br/>'\n                + '<b langtag=\"word-crypt\"></b>: <span langtag=\"word-' + row.Cnf.Crypt + '\"></span>&emsp;'\n                + '<b langtag=\"word-compress\"></b>: <span langtag=\"word-' + row.Cnf.Compress + '\"></span>&emsp;'\n                + '<b langtag=\"word-connectbyconfig\"></b>: <span langtag=\"word-' + row.ConfigConnAllow + '\"></span>&emsp;<br/><br/>'\n                + '<b langtag=\"word-commandclient\"></b>: ' + \"<code>./npc{{.win}} -server={{.ip}}:{{.p}} -vkey=\" + row.VerifyKey + \" -type=\" +{{.bridgeType}} +\"</code>\"\n        },\n        //表格的列\n        columns: [\n            {\n                field: 'Id',//域值\n                title: '<span langtag=\"word-id\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Remark',//域值\n                title: '<span langtag=\"word-remark\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Version',//域值\n                title: '<span langtag=\"word-version\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'VerifyKey',//域值\n                title: '<span langtag=\"word-verifykey\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    if (!row.NoStore) {\n                        return value\n                    } else {\n                        return '<span langtag=\"word-publicvkey\"></span>'\n                    }\n                }\n            },\n            {\n                field: 'Addr',//域值\n                title: '<span langtag=\"word-address\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'InletFlow',//域值\n                title: '<span langtag=\"word-inletflow\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                sortable: true,//启用排序\n                formatter: function (value, row, index) {\n                    return changeunit(row.Flow.InletFlow)\n                }\n            },\n            {\n                field: 'ExportFlow',//域值\n                title: '<span langtag=\"word-exportflow\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                sortable: true,//启用排序\n                formatter: function (value, row, index) {\n                    return changeunit(row.Flow.ExportFlow)\n                }\n            },\n            {\n                field: 'IsConnect',//域值\n                title: '<span langtag=\"word-speed\"></span>',//内容\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return changeunit(row.Rate.NowRate) + \"/S\"\n                }\n            },\n            {\n                field: 'Status',//域值\n                title: '<span langtag=\"word-status\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    if (value) {\n                        return '<span class=\"badge badge-primary\" langtag=\"word-open\"></span>'\n                    } else {\n                        return '<span class=\"badge badge-badge\" langtag=\"word-close\"></span>'\n                    }\n                }\n            },\n            {\n                field: 'IsConnect',//域值\n                title: '<span langtag=\"word-connect\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    if (value) {\n                        return '<span class=\"badge badge-primary\" langtag=\"word-online\"></span>'\n                    } else {\n                        return '<span class=\"badge badge-badge\" langtag=\"word-offline\"></span>'\n                    }\n                }\n            },\n            {\n                field: 'option',//域值\n                title: '<span langtag=\"word-option\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    btn_group = '<div class=\"btn-group\">'\n\n                    {{if eq true .isAdmin}}\n                    if (row.Status) {\n                        btn_group += '<a onclick=\"submitform(\\'stop\\', \\'{{.web_base_url}}/client/changestatus\\', {\\'id\\':' + row.Id\n                        btn_group += ', \\'status\\': 0})\" class=\"btn btn-outline btn-warning\"><i class=\"fa fa-pause\"></i></a>'\n                    } else {\n                        btn_group += '<a onclick=\"submitform(\\'start\\', \\'{{.web_base_url}}/client/changestatus\\', {\\'id\\':' + row.Id\n                        btn_group += ', \\'status\\': 1})\" class=\"btn btn-outline btn-primary\"><i class=\"fa fa-play\"></i></a>'\n                    }\n                    btn_group += '<a onclick=\"submitform(\\'delete\\', \\'{{.web_base_url}}/client/del\\', {\\'id\\':' + row.Id\n                    btn_group += '})\" class=\"btn btn-outline btn-danger\"><i class=\"fa fa-trash\"></i></a>'\n                    {{end}}\n\n                    btn_group += '<a href=\"{{.web_base_url}}/client/edit?id=' + row.Id\n                    btn_group += '\" class=\"btn btn-outline btn-success\"><i class=\"fa fa-edit\"></i></a></div>'\n                    return btn_group\n                }\n            },\n            {\n                field: 'show',//域值\n                title: '<span langtag=\"word-show\">',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return '<div class=\"btn-group\"><a href=\"{{.web_base_url}}/index/all?client_id=' + row.Id\n                        + '\" class=\"btn btn-outline btn-primary\" langtag=\"word-tunnel\"></a>'\n                        + '<a href=\"{{.web_base_url}}/index/hostlist?client_id=' + row.Id\n                        + '\" class=\"btn btn-outline btn-success\" langtag=\"word-host\"></a></div>'\n                }\n            }\n        ]\n    });\n</script>\n"
  },
  {
    "path": "web/views/index/add.html",
    "content": "<div class=\"row tile\">\n    <div class=\"col-md-12 col-md-auto\">\n        <div class=\"ibox float-e-margins\">\n            <h3 class=\"ibox-title\" langtag=\"page-add\"></h3>\n            <div class=\"ibox-content\">\n                <form class=\"form-horizontal\">\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-scheme\"></label>\n                        <div class=\"col-sm-10\">\n                            <span class=\"help-block m-b-none font-bold\" langtag=\"word-usecase\"></span>:\n                            <span id=\"usecase\">\n                                <span id=\"casetcp\" langtag=\"info-casetcp\"></span>\n                                <span id=\"caseudp\" langtag=\"info-caseudp\"></span>\n                                <span id=\"casehttpProxy\" langtag=\"info-casehttpproxy\"></span>\n                                <span id=\"casesocks5\" langtag=\"info-casesocks5\"></span>\n                                <span id=\"casesecret\" langtag=\"info-casesecret\"></span>\n                                <span id=\"casep2p\" langtag=\"info-casep2p\"></span>\n                                <span id=\"casefile\" langtag=\"info-casefile\"></span>\n                            </span>\n                            <select class=\"form-control\" name=\"type\" id=\"type\">\n                                <option value=\"tcp\" langtag=\"scheme-tcp\"></option>\n                                <option value=\"udp\" langtag=\"scheme-udp\"></option>\n                                <option value=\"httpProxy\" langtag=\"scheme-httpProxy\"></option>\n                                <option value=\"socks5\" langtag=\"scheme-socks5\"></option>\n                                <option value=\"secret\" langtag=\"scheme-secret\"></option>\n                                <option value=\"p2p\" langtag=\"scheme-p2p\"></option>\n                                {{/*<option value=\"file\" langtag=\"scheme-file\"></option>*/}}\n                            </select>\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"client_id\">\n                        <label class=\"control-label font-bold\" langtag=\"word-clientid\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.client_id}}\" class=\"form-control\" type=\"text\" name=\"client_id\"\n                                   placeholder=\"\" langtag=\"word-clientid\">\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-remark\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"remark\" placeholder=\"\"\n                                   langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                    {{if eq true .allow_multi_ip}}\n                        <div class=\"form-group\" id=\"server_ip\">\n                            <label class=\"control-label font-bold\" langtag=\"word-serverip\"></label>\n                            <div class=\"col-sm-10\">\n                                <input class=\"form-control\" type=\"text\" value=\"0.0.0.0\" name=\"server_ip\" placeholder=\"\"\n                                       langtag=\"info-suchasip\">\n                            </div>\n                        </div>\n                    {{end}}\n                    <div class=\"form-group\" id=\"port\">\n                        <label class=\"control-label font-bold\" langtag=\"word-serverport\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"port\" placeholder=\"\"\n                                   langtag=\"info-suchasport\">\n                        </div>\n                    </div>\n\n                    {{if eq true .allow_local_proxy}}\n                        <div class=\"form-group\" id=\"local_proxy\">\n                            <label class=\"control-label font-bold\" langtag=\"word-proxytolocal\"></label>\n                            <div class=\"col-sm-10\">\n                                <select class=\"form-control\" name=\"local_proxy\">\n                                    <option value=\"0\" langtag=\"word-no\"></option>\n                                    <option value=\"1\" langtag=\"word-yes\"></option>\n                                </select>\n                            </div>\n                        </div>\n                    {{end}}\n\n                    <div class=\"form-group\" id=\"target\">\n                        <label class=\"control-label font-bold\" langtag=\"word-target\"></label>\n                        <div class=\"col-sm-10\">\n                            <textarea class=\"form-control\" name=\"target\" rows=\"4\" placeholder=\"\"\n                                      langtag=\"info-suchasiplist\"></textarea>\n                            <span class=\"help-block m-b-none\" langtag=\"info-targettunnel\"></span>\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"local_path\">\n                        <label class=\"control-label font-bold\" langtag=\"word-localpath\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"local_path\" placeholder=\"\"\n                                   langtag=\"info-suchaslocalpath\">\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"strip_pre\">\n                        <label class=\"control-label font-bold\" langtag=\"word-stripprefix\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"strip_pre\" placeholder=\"\"\n                                   langtag=\"info-suchasstripprefix\">\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"password\">\n                        <label class=\"control-label font-bold\" langtag=\"word-identificationkey\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"password\" placeholder=\"\"\n                                   langtag=\"word-identificationkey\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-identificationkey\"></span>\n                        </div>\n                    </div>\n                    <div class=\"hr-line-dashed\"></div>\n                    <div class=\"form-group\">\n                        <div class=\"col-sm-4 col-sm-offset-2\">\n                            <button class=\"btn btn-success\" type=\"button\"\n                                    onclick=\"submitform('add', '{{.web_base_url}}/index/add', $('form').serializeArray())\">\n                                <i class=\"fa fa-fw fa-lg fa-check-circle\"></i> <span langtag=\"word-add\"></span>\n                            </button>\n                        </div>\n                    </div>\n\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n    var arr = []\n    arr[\"all\"] = [\"port\", \"target\", \"password\", \"local_path\", \"strip_pre\", \"local_proxy\", \"client_id\", \"server_ip\"]\n    arr[\"tcp\"] = [\"port\", \"target\", \"local_proxy\", \"client_id\", \"server_ip\"]\n    arr[\"udp\"] = [\"port\", \"target\", \"local_proxy\", \"client_id\", \"server_ip\"]\n    arr[\"socks5\"] = [\"port\", \"client_id\", \"server_ip\"]\n    arr[\"httpProxy\"] = [\"port\", \"client_id\", \"server_ip\"]\n    arr[\"secret\"] = [\"target\", \"password\", \"client_id\", \"server_ip\"]\n    arr[\"p2p\"] = [\"target\", \"password\", \"client_id\", \"server_ip\"]\n    arr[\"file\"] = [\"port\", \"local_path\", \"strip_pre\", \"client_id\", \"server_ip\"]\n\n    function resetForm() {\n        $(\".form-group[id]\").css(\"display\", \"none\");\n        $(\"#usecase span\").css(\"display\", \"none\");\n        o = $(\"#type\").val();\n        $('#case' + o).css(\"display\", \"inline\")\n        for (var i = 0; i < arr[o].length; i++) {\n            $(\"#\" + arr[o][i]).css(\"display\", \"block\")\n        }\n    }\n\n    $(function () {\n        $(\"#type\").val(('{{.type}}' == '') ? 'tcp' : '{{.type}}');\n        resetForm()\n        $(\"#type\").on(\"change\", function () {\n            resetForm()\n        })\n        $(\"#use_client\").on(\"change\", function () {\n            resetForm()\n        })\n    })\n</script>\n"
  },
  {
    "path": "web/views/index/edit.html",
    "content": "<div class=\"row tile\">\n    <div class=\"col-md-12 col-md-auto\">\n        <div class=\"ibox float-e-margins\">\n            <h3 class=\"ibox-title\" langtag=\"page-edit\"></h3>\n            <div class=\"ibox-content\">\n                <form class=\"form-horizontal\">\n                    <input type=\"hidden\" name=\"id\" value=\"{{.t.Id}}\">\n                    <div class=\"form-group\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-scheme\"></label>\n                        <div class=\"col-sm-10\">\n                            <span class=\"help-block m-b-none font-bold\" langtag=\"word-usecase\"></span>: \n                            <span id=\"usecase\">\n                                <span id=\"casetcp\" langtag=\"info-casetcp\"></span>\n                                <span id=\"caseudp\" langtag=\"info-caseudp\"></span>\n                                <span id=\"casehttpProxy\" langtag=\"info-casehttpproxy\"></span>\n                                <span id=\"casesocks5\" langtag=\"info-casesocks5\"></span>\n                                <span id=\"casesecret\" langtag=\"info-casesecret\"></span>\n                                <span id=\"casep2p\" langtag=\"info-casep2p\"></span>\n                                <span id=\"casefile\" langtag=\"info-casefile\"></span>\n                            </span>\n                            <select class=\"form-control\" name=\"type\" id=\"type\">\n                                <option value=\"tcp\" langtag=\"scheme-tcp\"></option>\n                                <option value=\"udp\" langtag=\"scheme-udp\"></option>\n                                <option value=\"httpProxy\" langtag=\"scheme-httpProxy\"></option>\n                                <option value=\"socks5\" langtag=\"scheme-socks5\"></option>\n                                <option value=\"secret\" langtag=\"scheme-secret\"></option>\n                                <option value=\"p2p\" langtag=\"scheme-p2p\"></option>\n                            {{/*<option value=\"file\" langtag=\"scheme-file\"></option>*/}}\n                            </select>\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"client_id\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-clientid\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.t.Client.Id}}\" value=\"{{.client_id}}\" class=\"form-control\" type=\"text\" name=\"client_id\" placeholder=\"\" langtag=\"word-clientid\">\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-remark\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.t.Remark}}\" class=\"form-control\" type=\"text\" name=\"remark\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{if eq true .allow_multi_ip}}\n                    <div class=\"form-group\" id=\"server_ip\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-serverip\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" value=\"{{.t.ServerIp}}\" name=\"server_ip\" placeholder=\"\" langtag=\"info-suchasip\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\" id=\"port\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-serverport\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.t.Port}}\" class=\"form-control\" type=\"text\" name=\"port\" placeholder=\"\" langtag=\"info-suchasport\">\n                        </div>\n                    </div>\n                {{if eq true .allow_local_proxy}}\n                    <div class=\"form-group\" id=\"local_proxy\">\n                        <label class=\"control-label col-sm-2 font-bold\" langtag=\"word-proxytolocal\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"local_proxy\">\n                                <option  {{if eq false .t.Target.LocalProxy}}selected{{end}} value=\"0\" langtag=\"word-no\"></option>\n                                <option  {{if eq true .t.Target.LocalProxy}}selected{{end}} value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\" id=\"target\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-target\"></label>\n                        <div class=\"col-sm-10\">\n                            <textarea class=\"form-control\" name=\"target\" rows=\"4\" placeholder=\"\" langtag=\"info-suchasiplist\">{{.t.Target.TargetStr}}</textarea>\n                            <span class=\"help-block m-b-none\" langtag=\"info-targettunnel\"></span>\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"local_path\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-localpath\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.t.LocalPath}}\" class=\"form-control\" type=\"text\" name=\"local_path\" placeholder=\"\" langtag=\"info-suchaslocalpath\">\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"strip_pre\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-stripprefix\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.t.StripPre}}\" class=\"form-control\" type=\"text\" name=\"strip_pre\" placeholder=\"\" langtag=\"info-suchasstripprefix\">\n                        </div>\n                    </div>\n\n                    <div class=\"form-group\" id=\"password\">\n                        <label class=\"col-sm-2 control-label font-bold\" langtag=\"word-identificationkey\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.t.Password}}\" class=\"form-control\" type=\"text\" name=\"password\" placeholder=\"\" langtag=\"word-identificationkey\">\n                            <span class=\"help-block m-b-none\" langtag=\"info-identificationkey\"></span>\n                        </div>\n                    </div>\n                    <div class=\"hr-line-dashed\"></div>\n                    <div class=\"form-group\">\n                        <div class=\"col-sm-4 col-sm-offset-2\">\n                            <button class=\"btn btn-success\" type=\"button\" onclick=\"submitform('edit', '{{.web_base_url}}/index/edit', $('form').serializeArray())\">\n                                <i class=\"fa fa-fw fa-lg fa-check-circle\"></i> <span langtag=\"word-save\"></span>\n                                \n                            </button>\n                        </div>\n                    </div>\n\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n    var arr = []\n    arr[\"all\"] = [\"port\", \"target\", \"password\", \"local_path\", \"strip_pre\", \"local_proxy\"]\n    arr[\"tcp\"] = [\"client_id\", \"port\", \"target\", \"local_proxy\"]\n    arr[\"udp\"] = [\"client_id\", \"port\", \"target\", \"local_proxy\"]\n    arr[\"socks5\"] = [\"client_id\", \"port\"]\n    arr[\"httpProxy\"] = [\"client_id\", \"port\"]\n    arr[\"secret\"] = [\"client_id\", \"target\", \"password\"]\n    arr[\"p2p\"] = [\"client_id\", \"target\", \"password\"]\n    arr[\"file\"] = [\"client_id\", \"port\", \"local_path\", \"strip_pre\"]\n\n    function resetForm() {\n        $(\".form-group[id]\").css(\"display\", \"none\");\n        $(\"#usecase span\").css(\"display\", \"none\");\n        o = $(\"#type\").val();\n        $('#case'+ o).css(\"display\", \"inline\")\n        for (var i = 0; i < arr[o].length; i++) {\n            $(\"#\" + arr[o][i]).css(\"display\", \"block\")\n        }\n    }\n\n    $(function () {\n        $(\"#type\").val('{{.t.Mode}}');\n        resetForm()\n        $(\"#type\").on(\"change\", function () {\n            resetForm()\n        })\n        $(\"#use_client\").on(\"change\", function () {\n            resetForm()\n        })\n    })\n</script>\n"
  },
  {
    "path": "web/views/index/hadd.html",
    "content": "<div class=\"row tile\">\n    <div class=\"col-md-12 col-md-auto\">\n        <div class=\"ibox float-e-margins\">\n            <h3 class=\"ibox-title\" langtag=\"page-hostadd\"></h3>\n            <div class=\"ibox-content\">\n                <form class=\"form-horizontal\">\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-clientid\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.client_id}}\" class=\"form-control\" type=\"text\" name=\"client_id\" placeholder=\"\" langtag=\"word-clientid\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-remark\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"remark\" placeholder=\"\" langtag=\"word-remark\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-host\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"host\" placeholder=\"\" langtag=\"info-suchashost\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"scheme\">\n                        <label class=\"control-label font-bold\" langtag=\"word-scheme\"></label>\n                        <div class=\"col-sm-10\">\n                            <select id=\"scheme_select\" class=\"form-control\" name=\"scheme\">\n                                <option value=\"all\" langtag=\"word-all\"></option>\n                                <option value=\"http\" langtag=\"word-http\"></option>\n                                <option value=\"https\" langtag=\"word-https\"></option>\n                            </select>\n                        </div>\n                    </div>\n                {{if eq false .https_just_proxy}}\n                    <div class=\"form-group\" id=\"cert_file\">\n                        <label class=\"control-label font-bold\" langtag=\"word-httpscert\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"cert_file_path\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"key_file\">\n                        <label class=\"control-label font-bold\" langtag=\"word-httpskey\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"key_file_path\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-urlroute\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" type=\"text\" name=\"location\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{if eq true .allow_local_proxy}}\n                    <div class=\"form-group\" id=\"local_proxy\">\n                        <label class=\"control-label font-bold\" langtag=\"word-proxytolocal\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"local_proxy\">\n                                <option value=\"0\" langtag=\"word-no\"></option>\n                                <option value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-target\"></label>\n                        <div class=\"col-sm-10\">\n                        <textarea class=\"form-control\" rows=\"4\" type=\"text\" name=\"target\" placeholder=\"\" langtag=\"info-suchasiplist\"></textarea>\n                            <span class=\"help-block m-b-none\" langtag=\"info-targethost\"></span>\n\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"header\">\n                        <label class=\"control-label font-bold\" langtag=\"word-requestheader\"></label>\n                        <div class=\"col-sm-10\">\n                        <textarea class=\"form-control\" rows=\"4\" type=\"text\" name=\"header\" placeholder=\"Cache-Control: no-cache\"></textarea>\n                            <span class=\"help-block m-b-none\" langtag=\"info-header\"></span>\n                        </div>\n\n                    </div>\n                    <div class=\"form-group\" id=\"hostchange\">\n                        <label class=\"control-label font-bold\" langtag=\"word-requesthost\"></label>\n                        <div class=\"col-sm-10\">\n                            <input class=\"form-control\" value=\"\" type=\"text\" name=\"hostchange\" placeholder=\"\" langtag=\"word-requesthost\">\n                        </div>\n                    </div>\n                    <div class=\"hr-line-dashed\"></div>\n                    <div class=\"form-group\">\n                        <div class=\"col-sm-4 col-sm-offset-2\">\n                            <button class=\"btn btn-success\" type=\"button\" onclick=\"submitform('add', '{{.web_base_url}}/index/addhost', $('form').serializeArray())\">\n                                <i class=\"fa fa-fw fa-lg fa-check-circle\"></i> <span langtag=\"word-add\"></span>\n                            </button>\n                        </div>\n                    </div>\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n<script>\n    $(function () {\n        $(\"#scheme_select\").on(\"change\", function () {\n            if ($(\"#scheme_select\").val() == \"all\" || $(\"#scheme_select\").val() == \"https\") {\n                $(\"#cert_file\").css(\"display\", \"block\")\n                $(\"#key_file\").css(\"display\", \"block\")\n            } else {\n                $(\"#cert_file\").css(\"display\", \"none\")\n                $(\"#key_file\").css(\"display\", \"none\")\n            }\n        })\n    })\n</script>"
  },
  {
    "path": "web/views/index/hedit.html",
    "content": "<div class=\"row tile\">\n    <div class=\"col-md-12 col-md-auto\">\n        <div class=\"ibox float-e-margins\">\n            <h3 class=\"ibox-title\" langtag=\"page-hostedit\"></h3>\n            <div class=\"ibox-content\">\n                <form class=\"form-horizontal\">\n                    <input type=\"hidden\" name=\"id\" value=\"{{.h.Id}}\">\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-clientid\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.Client.Id}}\" class=\"form-control\" type=\"text\" name=\"client_id\" placeholder=\"\" langtag=\"word-clientid\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-remark\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.Remark}}\" class=\"form-control\" type=\"text\" name=\"remark\"\n                                   placeholder=\"remark\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-host\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.Host}}\" class=\"form-control\" type=\"text\" name=\"host\" placeholder=\"\" langtag=\"info-suchashost\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"scheme\">\n                        <label class=\"control-label font-bold\" langtag=\"word-scheme\"></label>\n                        <div class=\"col-sm-10\">\n                            <select id=\"scheme_select\" class=\"form-control\" name=\"scheme\">\n                                <option {{if eq \"all\" .h.Scheme}}selected{{end}} value=\"all\" langtag=\"word-all\"></option>\n                                <option {{if eq \"http\" .h.Scheme}}selected{{end}} value=\"http\" langtag=\"word-http\"></option>\n                                <option {{if eq \"https\" .h.Scheme}}selected{{end}} value=\"https\" langtag=\"word-https\"></option>\n                            </select>\n                        </div>\n                    </div>\n                {{if eq false .https_just_proxy}}\n                    <div class=\"form-group\" id=\"cert_file\">\n                        <label class=\"control-label font-bold\" langtag=\"word-httpscert\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.CertFilePath}}\" class=\"form-control\" type=\"text\" name=\"cert_file_path\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"key_file\">\n                        <label class=\"control-label font-bold\" langtag=\"word-httpskey\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.KeyFilePath}}\" class=\"form-control\" type=\"text\" name=\"key_file_path\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-urlroute\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.Location}}\" class=\"form-control\" type=\"text\" name=\"location\" placeholder=\"\" langtag=\"info-unrestricted\">\n                        </div>\n                    </div>\n                {{if eq true .allow_local_proxy}}\n                    <div class=\"form-group\" id=\"local_proxy\">\n                        <label class=\"control-label font-bold\" langtag=\"word-proxytolocal\"></label>\n                        <div class=\"col-sm-10\">\n                            <select class=\"form-control\" name=\"local_proxy\">\n                                <option {{if eq false .h.Target.LocalProxy}}selected{{end}} value=\"0\" langtag=\"word-no\"></option>\n                                <option {{if eq true .h.Target.LocalProxy}}selected{{end}} value=\"1\" langtag=\"word-yes\"></option>\n                            </select>\n                        </div>\n                    </div>\n                {{end}}\n                    <div class=\"form-group\">\n                        <label class=\"control-label font-bold\" langtag=\"word-target\"></label>\n                        <div class=\"col-sm-10\">\n                        <textarea class=\"form-control\" rows=\"4\" type=\"text\" name=\"target\" placeholder=\"\" langtag=\"info-suchasiplist\">{{.h.Target.TargetStr}}</textarea>\n                            <span class=\"help-block m-b-none\" langtag=\"info-targethost\"></span>\n\n                        </div>\n                    </div>\n                    <div class=\"form-group\" id=\"header\">\n                        <label class=\"control-label font-bold\" langtag=\"word-requestheader\"></label>\n                        <div class=\"col-sm-10\">\n                        <textarea class=\"form-control\" rows=\"4\" type=\"text\" name=\"header\" placeholder=\"Cache-Control: no-cache\">{{.h.HeaderChange}}</textarea>\n                            <span class=\"help-block m-b-none\" langtag=\"info-header\"></span>\n                        </div>\n\n                    </div>\n                    <div class=\"form-group\" id=\"hostchange\">\n                        <label class=\"control-label font-bold\" langtag=\"word-requesthost\"></label>\n                        <div class=\"col-sm-10\">\n                            <input value=\"{{.h.HostChange}}\" class=\"form-control\" value=\"\" type=\"text\" name=\"hostchange\" placeholder=\"\" langtag=\"word-requesthost\">\n                        </div>\n                    </div>\n                    <div class=\"hr-line-dashed\"></div>\n                    <div class=\"form-group\">\n                        <div class=\"col-sm-4 col-sm-offset-2\">\n                            <button class=\"btn btn-success\" type=\"button\" onclick=\"submitform('edit', '{{.web_base_url}}/index/edithost', $('form').serializeArray())\">\n                                <i class=\"fa fa-fw fa-lg fa-save\"></i> <span langtag=\"word-save\"></span>\n                            </button>\n                        </div>\n                    </div>\n                </form>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script>\n    $(function () {\n        $(\"#scheme_select\").on(\"change\", function () {\n            if ($(\"#scheme_select\").val() == \"all\" || $(\"#scheme_select\").val() == \"https\") {\n                $(\"#cert_file\").css(\"display\", \"block\")\n                $(\"#key_file\").css(\"display\", \"block\")\n            } else {\n                $(\"#cert_file\").css(\"display\", \"none\")\n                $(\"#key_file\").css(\"display\", \"none\")\n            }\n        })\n    })\n\n</script>"
  },
  {
    "path": "web/views/index/help.html",
    "content": "<div class=\"row\">\n    <div class=\"col-md-12\">\n        <div class=\"tile\">\n            <iframe src=\"https://ghbtns.com/github-btn.html?user=cnlh&repo=nps&type=star&count=true&size=large\"\n                    frameborder=\"0\" scrolling=\"0\" width=\"160px\" height=\"30px\"></iframe>\n            <iframe src=\"https://ghbtns.com/github-btn.html?user=cnlh&repo=nps&type=watch&count=true&size=large&v=2\"\n                    frameborder=\"0\" scrolling=\"0\" width=\"160px\" height=\"30px\"></iframe>\n            <iframe src=\"https://ghbtns.com/github-btn.html?user=cnlh&repo=nps&type=fork&count=true&size=large\"\n                    frameborder=\"0\" scrolling=\"0\" width=\"158px\" height=\"30px\"></iframe>\n        </div>\n    </div>\n</div>\n<div class=\"row\">\n\n    <div class=\"col-md-12\">\n        <div class=\"tile\">\n            <h3 class=\"tile-title\">域名代理模式</h3>\n            <p>\n                <b>适用范围：</b> 小程序开发、微信公众号开发、产品演示\n            </p>\n            <p>\n                <b>假设场景：</b>\n            <li>有一个域名proxy.com，有一台公网机器ip为{{.ip}}</li>\n            <li>两个内网开发站点127.0.0.1:81，127.0.0.1:82</li>\n            <li>想通过a.proxy.com访问127.0.0.1:81，通过b.proxy.com访问127.0.0.1:82</li>\n            </p>\n            <p><b>使用步骤：</b></p>\n            <ul>\n                <li>将*.proxy.com解析到公网服务器{{.ip}}</li>\n                <li>在客户端管理中创建一个客户端，记录下验证密钥</li>\n                <li>点击该客户端的域名管理，添加两条规则规则：1、域名：a.proxy.com，内网目标：127.0.0.1:81，2、域名：b.proxy.com，内网目标：127.0.0.1:82</li>\n                <li>内网客户端运行<code>\n                    <pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>\n                </code></pre></li>\n                <li>现在访问a.proxy.com，b.proxy.com即可成功</li>\n            </ul>\n            <p>注：上文中提到公网ip（{{.ip}}）为系统自动识别，如果是在测试环境中请自行对应，<b>如需使用https请在配置文件中将https端口设置为443，和将对应的证书文件路径添加到配置文件中\n            </b></p>\n        </div>\n    </div>\n</div>\n<div class=\"row\">\n    <div class=\"col-md-6\">\n        <div class=\"tile\">\n            <h3 class=\"tile-title\">tcp隧道模式</h3>\n            <p>\n                <b>适用范围：</b> ssh、远程桌面等tcp连接场景\n            </p>\n            <p>\n                <b>假设场景：</b> 想通过访问公网服务器{{.ip}}的8001端口，连接内网机器10.1.50.101的22端口，实现ssh连接\n            </p>\n            <p><b>使用步骤：</b></p>\n            <ul>\n                <li>在客户端管理中创建一个客户端，记录下验证密钥</li>\n                <li>内网客户端运行<code>\n                    <pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>\n                </code></pre>\n                </li>\n                <li>在该客户端隧道管理中添加一条tcp隧道，填写监听的端口（8001）、内网目标ip和目标端口（10.1.50.101:22），选择压缩方式，保存。</li>\n                <li>访问公网服务器ip（{{.ip}}）,填写的监听端口(8001)，相当于访问内网ip(10.1.50.101):目标端口(22)，例如：ssh -p 8001 root@{{.ip}}</li>\n            </ul>\n            <p>注：上文中提到公网ip（{{.ip}}）为系统自动识别，如果是在测试环境中请自行对应，默认内网客户端已经启动</p>\n        </div>\n    </div>\n    <div class=\"col-md-6\">\n        <div class=\"tile\">\n            <h3 class=\"tile-title\">udp隧道模式</h3>\n            <p>\n                <b>适用范围：</b> 内网dns解析等udp连接场景\n            </p>\n            <p>\n                <b>假设场景：</b> 内网有一台dns（10.1.50.102:53），在非内网环境下想使用该dns，公网服务器为{{.ip}}\n            </p>\n            <p><b>使用步骤：</b></p>\n            <ul>\n                <li>在客户端管理中创建一个客户端，记录下验证密钥</li>\n                <li>内网客户端运行<code>\n                    <pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>\n                </code></pre>\n                </li>\n                <li>在该客户端的隧道管理中添加一条udp隧道，填写监听的端口（53）、内网目标ip和目标端口（10.1.50.102:53），选择压缩方式，保存。</li>\n                <li>修改本机dns为{{.ip}}，则相当于使用10.1.50.202作为dns服务器</li>\n            </ul>\n            <p>注：上文中提到公网ip（{{.ip}}）为系统自动识别，如果是在测试环境中请自行对应，默认内网客户端已经启动</p>\n        </div>\n    </div>\n</div>\n<div class=\"row\">\n    <div class=\"col-md-6\">\n        <div class=\"tile\">\n            <h3 class=\"tile-title\">socks5代理模式</h3>\n            <p>\n                <b>适用范围：</b> 在外网环境下如同使用vpn一样访问内网设备或者资源\n            </p>\n            <p>\n                <b>假设场景：</b> 想将公网服务器{{.ip}}的8003端口作为socks5代理，达到访问内网任意设备或者资源的效果\n            </p>\n            <p><b>使用步骤：</b></p>\n            <ul>\n                <li>在客户端管理中创建一个客户端，记录下验证密钥</li>\n                <li>内网客户端运行<code>\n                    <pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>\n                </code></pre>\n                </li>\n                <li>在该客户端隧道管理中添加一条socks5代理，填写监听的端口（8003），验证用户名和密码自行选择（建议先不填，部分客户端不支持，proxifer支持），选择压缩方式，保存。</li>\n                <li>在外网环境的本机配置socks5代理，ip为公网服务器ip（{{.ip}}），端口为填写的监听端口(8003)，即可畅享内网了</li>\n            </ul>\n            <p>注：上文中提到公网ip（{{.ip}}）为系统自动识别，如果是在测试环境中请自行对应，默认内网客户端已经启动</p>\n        </div>\n    </div>\n    <div class=\"col-md-6\">\n        <div class=\"tile\">\n            <h3 class=\"tile-title\">http代理模式</h3>\n            <p>\n                <b>适用范围：</b> 在外网环境下访问内网站点\n            </p>\n            <p>\n                <b>假设场景：</b> 想将公网服务器{{.ip}}的8004端口作为http代理，访问内网网站\n            </p>\n            <p><b>使用步骤：</b></p>\n            <ul>\n                <li>在客户端管理中创建一个客户端，记录下验证密钥</li>\n                <li>内网客户端运行<code>\n                    <pre>./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥</pre>\n                </code></pre>\n                </li>\n                <li>在该客户端隧道管理中添加一条http代理，填写监听的端口（8004），选择压缩方式，保存。</li>\n                <li>在外网环境的本机配置http代理，ip为公网服务器ip（{{.ip}}），端口为填写的监听端口(8004)，即可访问了</li>\n            </ul>\n            <p>注：上文中提到公网ip（{{.ip}}）为系统自动识别，如果是在测试环境中请自行对应，默认内网客户端已经启动</p>\n        </div>\n    </div>\n    <div class=\"col-md-12\">\n        <div class=\"tile\">\n            <p><b>单个客户端可以添加多条隧道或者域名解析</b></p>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "web/views/index/hlist.html",
    "content": "<div class=\"wrapper wrapper-content animated fadeInRight\">\n\n    <div class=\"row\">\n        <div class=\"col-lg-12\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"page-hostlist\"></h5>\n\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"content\">\n                    <div class=\"table-responsive\">\n                        <div id=\"toolbar\">\n                            <a href=\"{{.web_base_url}}/index/addhost?vkey={{.task_id}}&client_id={{.client_id}}\" class=\"btn btn-primary dim\">\n                            <i class=\"fa fa-fw fa-lg fa-plus\"></i> <span langtag=\"word-add\"></span></a>\n                        </div>\n                        <table id=\"taskList_table\" class=\"table-striped table-hover\"\n                               data-mobile-responsive=\"true\"></table>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n\n                    <table id=\"table\"></table>\n\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script>\n    /*bootstrap table*/\n    $('#table').bootstrapTable({\n        toolbar: \"#toolbar\",\n        method: 'post', // 服务器数据的请求方式 get or post\n        url: window.location, // 服务器数据的加载地址\n        queryParams: function (params) {\n            return {\n                \"offset\": params.offset,\n                \"limit\": params.limit,\n                \"search\": params.search\n            }\n        },\n        search: true,\n        contentType: \"application/x-www-form-urlencoded\",\n        striped: true, // 设置为true会有隔行变色效果\n        showHeader: true,\n        showColumns: true,\n        showRefresh: true,\n        pagination: true,//分页\n        sidePagination: 'server',//服务器端分页\n        pageNumber: 1,\n        pageList: [5, 10, 20, 50],//分页步进值\n        detailView: true,\n        smartDisplay: true, // 智能显示 pagination 和 cardview 等\n        onExpandRow: function () {$('body').setLang ('.detail-view');},\n        onPostBody: function (data) { if ($(this)[0].locale != undefined ) $('body').setLang ('#table'); },\n        detailFormatter: function (index, row, element) {\n            return '<b langtag=\"word-exportflow\"></b>: ' + changeunit(row.Flow.ExportFlow) + '&emsp;'\n                    + '<b langtag=\"word-inletflow\"></b>: ' + changeunit(row.Flow.InletFlow) + '&emsp;'\n                    + '<b langtag=\"word-crypt\"></b>: ' + row.Client.Cnf.Crypt + '&emsp;'\n                    + '<b langtag=\"word-compress\"></b>: ' + row.Client.Cnf.Compress + '&emsp;'\n                    + '<b langtag=\"word-basicusername\"></b>: ' + row.Client.Cnf.U + '&emsp;'\n                    + '<b langtag=\"word-basicpassword\"></b>: ' + row.Client.Cnf.P + '&emsp;<br/><br>'\n                    + '<b langtag=\"word-httpscert\"></b>: ' + row.CertFilePath + '&emsp;'\n                    + '<b langtag=\"word-httpskey\"></b>: ' + row.KeyFilePath + '&emsp;<br/><br>'\n                    + '<b langtag=\"word-requestheader\"></b>: ' + row.HeaderChange + '&emsp;<br/><br>'\n                    + '<b langtag=\"word-requesthost\"></b>: ' + row.HostChange + '&emsp;'\n        },\n        //表格的列\n        columns: [\n            {\n                field: 'Id',//域值\n                title: '<span langtag=\"word-id\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Id',//域值\n                title: '<span langtag=\"word-clientid\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return row.Client.Id\n                }\n            },\n            {\n                field: 'Remark',//域值\n                title: '<span langtag=\"word-remark\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Host',//域值\n                title: '<span langtag=\"word-host\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Scheme',//域值\n                title: '<span langtag=\"word-scheme\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return '<span langtag=\"word-' +value+ '\"></span>'\n                }\n            },\n            {\n                field: 'Target',//域值\n                title: '<span langtag=\"word-target\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return row.Target.TargetStr\n                }\n            },\n            {\n                field: 'Location',//域值\n                title: '<span langtag=\"word-location\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: '',//域值\n                title: '<span langtag=\"word-clientstatus\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    hosturl = ((row.Scheme == 'http' ) ? 'http://' : 'https://') + row.Host + row.Location\n                    if (row.Client.IsConnect) {\n                        return '<a href=\"' + hosturl + '\" target=\"_blank\"><span class=\"badge badge-primary\" langtag=\"word-online\"></span></a>'\n                    } else {\n                        return '<span class=\"badge badge-badge\" langtag=\"word-offline\"></span>'\n                    }\n                }\n            },\n            {\n                field: 'option',//域值\n                title: '<span langtag=\"word-option\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    btn_group = '<div class=\"btn-group\">'\n                    btn_group += \"<a onclick=\\\"submitform('delete', '{{.web_base_url}}/index/delhost', {'id':\" + row.Id\n                    btn_group += '})\" class=\"btn btn-outline btn-danger\"><i class=\"fa fa-trash\"></i></a>'\n                    btn_group += '<a href=\"{{.web_base_url}}/index/edithost?id=' + row.Id\n                    btn_group += '\" class=\"btn btn-outline btn-success\"><i class=\"fa fa-edit\"></i></a></div>'\n                    return btn_group\n                }\n            }\n        ]\n    });\n</script>\n"
  },
  {
    "path": "web/views/index/index.html",
    "content": "<div class=\"wrapper wrapper-content\">\n    <div class=\"row\">\n        <div class=\"col-lg-3\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-connectionport\"></h5>\n                </div>\n                <div class=\"ibox-content\">\n                    <h1 class=\"no-margins\">{{.p}}</h1>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-3\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-totalclients\"></h5>\n                </div>\n                <div class=\"ibox-content\">\n                    <h1 class=\"no-margins\">{{.data.clientCount}}</h1>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-3\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                {{/*<span class=\"label label-primary pull-right\">今日</span>*/}}\n                    <h5 langtag=\"word-onlineclients\"></h5>\n                </div>\n                <div class=\"ibox-content\">\n                    <h1 class=\"no-margins\">{{.data.clientOnlineCount}}</h1>\n                {{/*<div class=\"stat-percent font-bold text-navy\">44% <i class=\"fa fa-level-up\"></i></div>*/}}\n                {{/*<small>新访客</small>*/}}\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-3\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-tcpconnections\"></h5>\n                </div>\n                <div class=\"ibox-content\">\n                    <h1 class=\"no-margins\">{{.data.tcpCount}}</h1>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"row\">\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-configurationinformation\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content no-padding\">\n                    <ul class=\"list-group\">\n                        <li class=\"list-group-item\">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-bridgingmode\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.bridgeType}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item\">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-httpport\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.httpProxyPort}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item\">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-httpsport\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.httpsProxyPort}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-iprestriction\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong langtag=\"word-{{.data.ipLimit}}\"></strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-trafficdatapersistence\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.flowStoreInterval}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-loglevel\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.logLevel}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-p2pport\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.p2pPort}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-serveriP\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.serverIp}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-serverversion\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong>{{.data.version}}</strong>\n                                </div>\n                            </div>\n                        </li>\n                    </ul>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-systeminformation\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content no-padding\">\n                    <ul class=\"list-group\">\n                        <li class=\"list-group-item\">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-cpu\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_cpu\"></strong>\n                                </div>\n                            </div>\n                            <div class=\"progress progress-small\">\n                                <div id=\"overview_cpu_bar\" class=\"progress-bar\"></div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item\">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-memory\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_memory\"></strong>\n                                </div>\n                            </div>\n                            <div class=\"progress progress-small\">\n                                <div id=\"overview_memory_bar\" class=\"progress-bar\"></div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item\">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-load\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_load\"></strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-tcpconnections_established\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_tcp\"></strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-udpconnections_established\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_udp\"></strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-outbandwidth\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_send\"></strong>\n                                </div>\n                            </div>\n                        </li>\n                        <li class=\"list-group-item \">\n                            <div class=\"row\">\n                                <div class=\"col-sm-6\">\n                                    <strong langtag=\"word-inbandwidth\"></strong>\n                                </div>\n                                <div class=\"col-sm-6 text-right\">\n                                    <strong id=\"overview_recv\"></strong>\n                                </div>\n                            </div>\n                        </li>\n                    </ul>\n                </div>\n            </div>\n        </div>\n    </div>\n\n{{if eq true .system_info_display}}\n    <div class=\"row\">\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-load\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"load\" style=\"height: 300px\"></div>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-cpu\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"cpu\" style=\"height: 300px\"></div>\n                </div>\n            </div>\n        </div>\n    </div>\n    <div class=\"row\">\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-memory\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"memory\" style=\"height: 300px\"></div>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-connections_established\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"connections\" style=\"height: 300px\"></div>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"row\">\n        <div class=\"col-lg-12\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-bandwidth\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"bandwidth\" style=\"height: 300px\"></div>\n                </div>\n            </div>\n        </div>\n    </div>\n{{end}}\n\n    <div class=\"row\">\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-trafficstatistics\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"flow\" style=\"height: 400px;\"></div>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-lg-6\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5 langtag=\"word-type\"></h5>\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n                    <div id=\"counts\" style=\"height:400px;\"></div>\n                </div>\n            </div>\n        </div>\n\n\n    </div>\n</div>\n\n<script>\n    $(\"#overview_cpu\").text(\"{{.data.cpu}}%\")\n    $(\"#overview_cpu_bar\").width(\"{{.data.cpu}}%\")\n    $(\"#overview_memory\").text(\"{{.data.virtual_mem}}%\")\n    $(\"#overview_memory_bar\").width(\"{{.data.virtual_mem}}%\")\n    $.each( JSON.parse({{.data.load}}), function(i, value) { $(\"#overview_load\").append('&emsp;' + value) });\n    $(\"#overview_tcp\").text(\"{{.data.tcp}}\")\n    $(\"#overview_udp\").text(\"{{.data.udp}}\")\n    $(\"#overview_send\").text(changeunit({{.data.io_send}}) + \"/s\")\n    $(\"#overview_recv\").text(changeunit({{.data.io_recv}}) + \"/s\")\n\n\tchartdatas['load'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'axis',\n\t\t\tformatter: function (params) {\n\t\t\t\tvar str = params[0].axisValue + '<br/>';\n\t\t\t\tfor (i in params){\n\t\t\t\t\tstr += '<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';\"></span>' + params[i].seriesName +': '+ params[i].data +'</br>';\n\t\t\t\t\t}\n\t\t\t\treturn str;\n\t\t\t}\n\t\t},\n\t\tgrid: {\n\t\t\tleft: '3%',\n\t\t\tright: '3%',\n\t\t\ttop: '5%',\n\t\t\tbottom: '3%',\n\t\t\tcontainLabel: true\n\t\t},\n\t\txAxis: {\n\t\t\ttype: 'category',\n\t\t\tboundaryGap: false,\n\t\t\tdata: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']\n\t\t},\n\t\tyAxis: {\n\t\t\ttype: 'value'\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'load1',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.load1}}, {{.data.sys2.load1}}, {{.data.sys3.load1}}, {{.data.sys4.load1}}, {{.data.sys5.load1}}, {{.data.sys6.load1}}, {{.data.sys7.load1}}, {{.data.sys8.load1}}, {{.data.sys9.load1}}, {{.data.sys10.load1}}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'load5',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.load5}}, {{.data.sys2.load5}}, {{.data.sys3.load5}}, {{.data.sys4.load5}}, {{.data.sys5.load5}}, {{.data.sys6.load5}}, {{.data.sys7.load5}}, {{.data.sys8.load5}}, {{.data.sys9.load5}}, {{.data.sys10.load5}}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'load15',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.load15}}, {{.data.sys2.load15}}, {{.data.sys3.load15}}, {{.data.sys4.load15}}, {{.data.sys5.load15}}, {{.data.sys6.load15}}, {{.data.sys7.load15}}, {{.data.sys8.load15}}, {{.data.sys9.load15}}, {{.data.sys10.load15}}]\n\t\t\t}\n\t\t]\n\t};\n\n\tchartdatas['cpu'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'axis',\n\t\t\tformatter: function (params) {\n\t\t\t\tvar str = params[0].axisValue + '<br/>';\n\t\t\t\tfor (i in params){\n\t\t\t\t\tstr += '<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';\"></span>' + params[i].seriesName +': '+ params[i].data +'%</br>';\n\t\t\t\t}\n\t\t\t\treturn str;\n\t\t\t}\n\t\t},\n\t\tgrid: {\n\t\t\tleft: '3%',\n\t\t\tright: '3%',\n\t\t\ttop: '5%',\n\t\t\tbottom: '3%',\n\t\t\tcontainLabel: true\n\t\t},\n\t\txAxis: {\n\t\t\ttype: 'category',\n\t\t\tboundaryGap: false,\n\t\t\tdata: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']\n\t\t},\n\t\tyAxis: {\n\t\t\ttype: 'value',\n\t\t\taxisLabel: {\n\t\t\t\tshow: true,\n\t\t\t\tinterval: 'auto',\n\t\t\t\tformatter: '{value} %'\n\t\t\t}\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'cpu',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.cpu}}, {{.data.sys2.cpu}}, {{.data.sys3.cpu}}, {{.data.sys4.cpu}}, {{.data.sys5.cpu}}, {{.data.sys6.cpu}}, {{.data.sys7.cpu}}, {{.data.sys8.cpu}}, {{.data.sys9.cpu}}, {{.data.sys10.cpu}}]\n\t\t\t}\n\t\t]\n\t};\n\n\tchartdatas['memory'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'axis',\n\t\t\tformatter: function (params) {\n\t\t\t\tvar str = params[0].axisValue + '<br/>';\n\t\t\t\tfor (i in params){\n\t\t\t\t\tstr += '<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';\"></span>' + params[i].seriesName +': '+ params[i].data +'MB</br>';\n\t\t\t\t}\n\t\t\t\treturn str;\n\t\t\t}\n\t\t},\n\t\tgrid: {\n\t\t\tleft: '3%',\n\t\t\tright: '3%',\n\t\t\ttop: '5%',\n\t\t\tbottom: '3%',\n\t\t\tcontainLabel: true\n\t\t},\n\t\txAxis: {\n\t\t\ttype: 'category',\n\t\t\tboundaryGap: false,\n\t\t\tdata: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']\n\t\t\t},\n\t\tyAxis: {\n\t\t\ttype: 'value',\n\t\t\taxisLabel: {\n\t\t\t\tshow: true,\n\t\t\t\tinterval: 'auto',\n\t\t\t\tformatter: '{value} MB'\n\t\t\t}\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'virtual_mem',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.virtual_mem}}, {{.data.sys2.virtual_mem}}, {{.data.sys3.virtual_mem}}, {{.data.sys4.virtual_mem}}, {{.data.sys5.virtual_mem}}, {{.data.sys6.virtual_mem}}, {{.data.sys7.virtual_mem}}, {{.data.sys8.virtual_mem}}, {{.data.sys9.virtual_mem}}, {{.data.sys10.virtual_mem}}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'swap_mem',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.swap_mem}}, {{.data.sys2.swap_mem}}, {{.data.sys3.swap_mem}}, {{.data.sys4.swap_mem}}, {{.data.sys5.swap_mem}}, {{.data.sys6.swap_mem}}, {{.data.sys7.swap_mem}}, {{.data.sys8.swap_mem}}, {{.data.sys9.swap_mem}}, {{.data.sys10.swap_mem}}]\n\t\t\t}\n\t\t]\n\t};\n\n\tchartdatas['connections'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'axis'\n\t\t},\n\t\tgrid: {\n\t\t\tleft: '3%',\n\t\t\tright: '3%',\n\t\t\ttop: '5%',\n\t\t\tbottom: '3%',\n\t\t\tcontainLabel: true\n\t\t},\n\t\txAxis: {\n\t\t\ttype: 'category',\n\t\t\tboundaryGap: false,\n\t\t\tdata: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']\n\t\t},\n\t\tyAxis: {\n\t\t\ttype: 'value'\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'tcp',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.tcp}}, {{.data.sys2.tcp}}, {{.data.sys3.tcp}}, {{.data.sys4.tcp}}, {{.data.sys5.tcp}}, {{.data.sys6.tcp}}, {{.data.sys7.tcp}}, {{.data.sys8.tcp}}, {{.data.sys9.tcp}}, {{.data.sys10.tcp}}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'udp',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.udp}}, {{.data.sys2.udp}}, {{.data.sys3.udp}}, {{.data.sys4.udp}}, {{.data.sys5.udp}}, {{.data.sys6.udp}}, {{.data.sys7.udp}}, {{.data.sys8.udp}}, {{.data.sys9.udp}}, {{.data.sys10.udp}}]\n\t\t\t}\n\t\t]\n\t};\n\n\tchartdatas['bandwidth'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'axis',\n\t\t\tformatter: function (params) {\n\t\t\t\tvar str = params[0].axisValue + '<br/>';\n\t\t\t\tfor (i in params){\n\t\t\t\t\tstr += '<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + params[i].color + ';\"></span>' + params[i].seriesName +': '+ changeunit(params[i].data) +'/s</br>';\n\t\t\t\t}\n\t\t\t\treturn str;\n\t\t\t}\n\t\t},\n\t\tgrid: {\n\t\tleft: '3%',\n\t\tright: '3%',\n\t\ttop: '5%',\n\t\tbottom: '3%',\n\t\tcontainLabel: true\n\t\t},\n\t\txAxis: {\n\t\t\ttype: 'category',\n\t\t\tboundaryGap: false,\n\t\t\tdata: ['{{.data.sys1.time}}', '{{.data.sys2.time}}', '{{.data.sys3.time}}', '{{.data.sys4.time}}', '{{.data.sys5.time}}', '{{.data.sys6.time}}', '{{.data.sys7.time}}', '{{.data.sys8.time}}', '{{.data.sys9.time}}', '{{.data.sys10.time}}']\n\t\t},\n\t\tyAxis: {\n\t\t\ttype: 'value',\n\t\t\taxisLabel: {\n\t\t\t\tshow: true,\n\t\t\t\tinterval: 'auto',\n\t\t\t\tformatter: function (params){\n\t\t\t\t\treturn changeunit (params);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'in',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.io_recv}}, {{.data.sys2.io_recv}}, {{.data.sys3.io_recv}}, {{.data.sys4.io_recv}}, {{.data.sys5.io_recv}}, {{.data.sys6.io_recv}}, {{.data.sys7.io_recv}}, {{.data.sys8.io_recv}}, {{.data.sys9.io_recv}}, {{.data.sys10.io_recv}}]\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'line',\n\t\t\t\tstack: 'out',\n\t\t\t\tsmooth: true,\n\t\t\t\tdata: [{{.data.sys1.io_send}}, {{.data.sys2.io_send}}, {{.data.sys3.io_send}}, {{.data.sys4.io_send}}, {{.data.sys5.io_send}}, {{.data.sys6.io_send}}, {{.data.sys7.io_send}}, {{.data.sys8.io_send}}, {{.data.sys9.io_send}}, {{.data.sys10.io_send}}]\n\t\t\t}\n\t\t]\n\t};\n\n\tchartdatas['flow'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'item',\n\t\t\tformatter: function (p) {\n\t\t\t\treturn p.seriesName + '<br>' + p.name + ':' + changeunit(p.data.value);\n\t\t\t}\n\t\t},\n\t\tlegend: {\n\t\t\torient: 'vertical',\n\t\t\tleft: 'left',\n\t\t\tdata: ['', '']\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'pie',\n\t\t\t\tradius: '55%',\n\t\t\t\tcenter: ['50%', '60%'],\n\t\t\t\tdata: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.inletFlowCount}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.exportFlowCount}}\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\titemStyle: {\n\t\t\t\t\temphasis: {\n\t\t\t\t\t\tshadowBlur: 10,\n\t\t\t\t\t\tshadowOffsetX: 0,\n\t\t\t\t\t\tshadowColor: 'rgba(0, 0, 0, 0.5)'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t};\n\n\tchartdatas['counts'] = {\n\t\ttooltip: {\n\t\t\ttrigger: 'item',\n\t\t\tformatter: '{a} <br/>{b} : {c} ({d}%)'\n\t\t},\n\t\tlegend: {\n\t\t\torient: 'vertical',\n\t\t\tleft: 'left',\n\t\t\tdata: ['', '', '', '', '', '', '']\n\t\t},\n\t\tseries: [\n\t\t\t{\n\t\t\t\tname: '',\n\t\t\t\ttype: 'pie',\n\t\t\t\tradius: '55%',\n\t\t\t\tcenter: ['50%', '60%'],\n\t\t\t\tdata: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.hostCount}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.tcpC}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.udpCount}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.httpProxyCount}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.socks5Count}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.secretCount}}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: '',\n\t\t\t\t\t\tvalue:{{.data.p2pCount}}\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\titemStyle: {\n\t\t\t\t\temphasis: {\n\t\t\t\t\t\tshadowBlur: 10,\n\t\t\t\t\t\tshadowOffsetX: 0,\n\t\t\t\t\t\tshadowColor: 'rgba(0, 0, 0, 0.5)'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t]\n\t};\n\n    window.addEventListener('resize', () => {\n        for(var key in charts){\n            charts[key].resize();\n        }\n    });\n</script>\n"
  },
  {
    "path": "web/views/index/list.html",
    "content": "<div class=\"wrapper wrapper-content animated fadeInRight\">\n\n    <div class=\"row\">\n        <div class=\"col-lg-12\">\n            <div class=\"ibox float-e-margins\">\n                <div class=\"ibox-title\">\n                    <h5><span id=\"langtag\"></span><span></span></h5>\n\n                    <div class=\"ibox-tools\">\n                        <a class=\"collapse-link\">\n                            <i class=\"fa fa-chevron-up\"></i>\n                        </a>\n                        <a class=\"close-link\">\n                            <i class=\"fa fa-times\"></i>\n                        </a>\n                    </div>\n                </div>\n                <div class=\"content\">\n                    <div class=\"table-responsive\">\n                        <div id=\"toolbar\">\n                            <a href=\"{{.web_base_url}}/index/add?type={{.type}}&client_id={{.client_id}}\" class=\"btn btn-primary dim\">\n                            <i class=\"fa fa-fw fa-lg fa-plus\"></i> <span langtag=\"word-add\"></span></a>\n                        </div>\n                        <table id=\"taskList_table\" class=\"table-striped table-hover\" data-mobile-responsive=\"true\"></table>\n                    </div>\n                </div>\n                <div class=\"ibox-content\">\n\n                    <table id=\"table\"></table>\n\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script>\n    name = '{{.name}}:'.replace(/\\s*/g,\"\")\n    $('#langtag').attr('langtag','page-list' + name.replace(/:.*/,\"\")).next().text(name.split(\":\")[1])\n\n    /*bootstrap table*/\n    $('#table').bootstrapTable({\n        toolbar: \"#toolbar\",\n        method: 'post', // 服务器数据的请求方式 get or post\n        url: \"{{.web_base_url}}/index/gettunnel\", // 服务器数据的加载地址\n        queryParams: function (params) {\n            return {\n                \"offset\": params.offset,\n                \"limit\": params.limit,\n                \"type\":{{.type}},\n                \"client_id\":{{.client_id}},\n                \"search\": params.search\n            }\n        },\n        search: true,\n        contentType: \"application/x-www-form-urlencoded\",\n        striped: true, // 设置为true会有隔行变色效果\n        showHeader: true,\n        showColumns: true,\n        showRefresh: true,\n        pagination: true,//分页\n        sidePagination: 'server',//服务器端分页\n        pageNumber: 1,\n        pageList: [5, 10, 20, 50],//分页步进值\n        detailView: true,\n        smartDisplay: true, // 智能显示 pagination 和 cardview 等\n        onExpandRow: function () {$('body').setLang ('.detail-view');},\n        onLoadSuccess:function (data) {$('body').setLang ('.detail-view');},\n        onPostBody: function (data) { if ($(this)[0].locale != undefined ) $('body').setLang ('#table'); },\n        detailFormatter: function (index, row, element) {\n            tmp = '<b langtag=\"word-exportflow\"></b>: ' + changeunit(row.Flow.ExportFlow) + '&emsp;'\n                    + '<b langtag=\"word-inletflow\"></b>: ' + changeunit(row.Flow.InletFlow) + '&emsp;'\n                    + '<b langtag=\"word-crypt\"></b>: ' + row.Client.Cnf.Crypt + '&emsp;'\n                    + '<b langtag=\"word-compress\"></b>: ' + row.Client.Cnf.Compress + '&emsp;'\n                    + '<b langtag=\"word-basicusername\"></b>: ' + row.Client.Cnf.U + '&emsp;'\n                    + '<b langtag=\"word-basicpassword\"></b>: ' + row.Client.Cnf.P + '&emsp;'\n            if (row.Mode == \"p2p\") {\n                return tmp + \"<br/><br>\"\n                        + '<b langtag=\"word-commandaccessp2p\"></b>: ' + \"<code>./npc{{.win}} -server={{.ip}}:{{.p}} -vkey=\" + row.Client.VerifyKey \n                        + \" -type=\" +{{.bridgeType}} +\" -password=\" + row.Password + \" -target=\" + row.Target.TargetStr + \"</code>\" + \"<br/><br>\"\n                        + '<b langtag=\"word-commandaccessp2ps\"></b>: ' + \"<code>./npc{{.win}} -server={{.ip}}:{{.p}} -vkey=\" + row.Client.VerifyKey \n                        + \" -type=\" +{{.bridgeType}} +\" -password=\" + row.Password + \" -local_type=p2ps\" + \"</code>\" + \"<br/><br>\"\n                        + '<b langtag=\"word-commandaccessp2pt\"></b>: ' + \"<code>./npc{{.win}} -server={{.ip}}:{{.p}} -vkey=\" + row.Client.VerifyKey \n                        + \" -type=\" +{{.bridgeType}} +\" -password=\" + row.Password + \" -local_type=p2pt\" + \"</code>\"\n\n            }\n            if (row.Mode == \"secret\") {\n                return tmp + \"<br/><br>\" + '<b langtag=\"word-commandaccess\"></b>: ' + \"<code>./npc{{.win}} -server={{.ip}}:{{.p}} -vkey=\" + row.Client.VerifyKey \n                        + \" -type=\" +{{.bridgeType}} +\" -password=\" + row.Password + \" -local_type=secret\" + \"</code>\"\n            }\n            return tmp\n        },\n        //表格的列\n        columns: [\n            {\n                field: 'Id',//域值\n                title: '<span langtag=\"word-id\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Id',//域值\n                title: '<span langtag=\"word-clientid\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return row.Client.Id\n                }\n            },\n            {\n                field: 'Remark',//域值\n                title: '<span langtag=\"word-remark\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Mode',//域值\n                title: '<span langtag=\"word-scheme\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return '<span langtag=\"scheme-' + value + '\"></span>'\n                }\n            },\n            {\n                field: 'Port',//域值\n                title: '<span langtag=\"word-port\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Target',//域值\n                title: '<span langtag=\"word-target\"></span>',//标题\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    return row.Target.TargetStr\n                }\n            },\n            {\n                field: 'Password',//域值\n                title: '<span langtag=\"word-identificationkey\"></span>',//标题\n                halign: 'center',\n                visible: true//false表示不显示\n            },\n            {\n                field: 'Status',//域值\n                title: '<span langtag=\"word-status\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    if (value) {\n                        return '<span class=\"badge badge-primary\" langtag=\"word-open\"></span>'\n                    } else {\n                        return '<span class=\"badge badge-badge\" langtag=\"word-close\"></span>'\n                    }\n                }\n            },\n            {\n                field: 'RunStatus',//域值\n                title: '<span langtag=\"word-runstatus\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    if (value) {\n                        return '<span class=\"badge badge-primary\" langtag=\"word-open\"></span>'\n                    } else {\n                        return '<span class=\"badge badge-badge\" langtag=\"word-close\"></span>'\n                    }\n                }\n            },\n            {\n                field: '',//域值\n                title: '<span langtag=\"word-clientstatus\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    if (row.Client.IsConnect) {\n                        return '<span class=\"badge badge-primary\" langtag=\"word-online\"></span>'\n                    } else {\n                        return '<span class=\"badge badge-badge\" langtag=\"word-offline\"></span>'\n                    }\n                }\n            },\n            {\n                field: 'option',//域值\n                title: '<span langtag=\"word-option\"></span>',//内容\n                align: 'center',\n                halign: 'center',\n                visible: true,//false表示不显示\n                formatter: function (value, row, index) {\n                    btn_group = '<div class=\"btn-group\">'\n                    if (row.Status) {\n                        btn_group += \"<a onclick=\\\"submitform('stop', '{{.web_base_url}}/index/stop', {'id':\" + row.Id\n                        btn_group += '})\" class=\"btn btn-outline btn-warning\"><i class=\"fa fa-pause\"></i></a>'\n                    } else {\n                        btn_group += \"<a onclick=\\\"submitform('start', '{{.web_base_url}}/index/start', {\\'id\\':\" + row.Id\n                        btn_group += '})\" class=\"btn btn-outline btn-primary\"><i class=\"fa fa-play\"></i></a>'\n                    }\n                    btn_group += \"<a onclick=\\\"submitform('delete', '{{.web_base_url}}/index/del', {'id':\" + row.Id\n                    btn_group += '})\" class=\"btn btn-outline btn-danger\"><i class=\"fa fa-trash\"></i></a>'\n                    btn_group += '<a  href=\"{{.web_base_url}}/index/edit?id=' + row.Id\n                    btn_group += '\" class=\"btn btn-outline btn-success\"><i class=\"fa fa-edit\"></i></a></div>'\n                    return btn_group\n                }\n            }\n        ]\n    });\n</script>\n"
  },
  {
    "path": "web/views/login/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n    <title langtag=\"title-login\"></title>\n\n    <!-- Mainly scripts -->\n    <!-- Latest compiled and minified CSS -->\n    <link href=\"{{.web_base_url}}/static/css/fontawesome.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/solid.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/style.css\" rel=\"stylesheet\">\n\n    <!-- Latest compiled and minified JavaScript -->\n    <script src=\"{{.web_base_url}}/static/js/jquery-3.4.1.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/bootstrap.min.js\"></script>\n    <!-- Latest compiled and minified Locales -->\n    <script src=\"{{.web_base_url}}/static/js/language.js\" type=\"text/javascript\"></script>\n\n</head>\n\n<body class=\"gray-bg\">\n    <div class=\"row border-bottom\">\n        <nav class=\"navbar navbar-static-top navbar-right\" role=\"navigation\" style=\"margin: 20px 40px\">\n<div></div>\n            <h1 style=\"margin:0px\" class=\"navbar-header font-bold\" langtag=\"application\"></h1>\n            <span class=\"btn-group dropdown\">\n                <button id=\"languagemenu\" class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\"><i class=\"fa fa-globe-asia fa-lg\"></i><span></span></button>\n                <ul class=\"dropdown-menu\"></ul>\n            </span>\n        </nav>\n    </div>\n<div class=\"loginColumns animated fadeInDown mt-3 px-5\">\n    <div class=\"row\">\n\n        <div class=\"col-md-6 mt-3\">\n            <h3 langtag=\"info-tagline\"></h3>\n            <ul class=\"px-1 text-justify\">\n                <li langtag=\"info-feature1\"></li>\n                <li langtag=\"info-feature2\"></li>\n                <li langtag=\"info-feature3\"></li>\n                <li langtag=\"info-feature4\"></li>\n                <li langtag=\"info-feature5\"></li>\n                <li langtag=\"info-feature6\"></li>\n                <li langtag=\"info-feature7\"></li>\n                <li langtag=\"info-feature8\"></li>\n                <li langtag=\"info-feature9\"></li>\n            </ul>\n        </div>\n\n        <div class=\"col-md-6 mt-3\">\n            <div class=\"ibox-content\">\n                <form class=\"m-t\" onsubmit=\"return false\">\n                    <div class=\"form-group\">\n                        <input name=\"username\" class=\"form-control\" placeholder=\"username\" required=\"\" langtag=\"word-username\">\n                    </div>\n                    <div class=\"form-group\">\n                        <input name=\"password\" type=\"password\" class=\"form-control\" placeholder=\"password\" required=\"\" langtag=\"word-password\">\n                    </div>\n                    <button onclick=\"login()\" class=\"btn btn-primary block full-width m-b\" langtag=\"word-login\"></button>\n                {{if eq true .register_allow}}\n                    <p class=\"text-muted text-center\"><small langtag=\"info-noaccount\"></small></p>\n                    <a class=\"btn btn-sm btn-white btn-block\" href=\"{{.web_base_url}}/login/register\" langtag=\"word-register\"></a>\n                {{end}}\n                </form>\n            </div>\n        </div>\n    </div>\n    <hr/>\n    <div class=\"footer\" style=\"position: unset;\">\n        <div class=\"pull-right\">\n            <span langtag=\"word-readmore\"></span> <strong><a href=\"https://ehang.io/nps\" langtag=\"word-go\"></a></strong>\n        </div>\n        <div><strong langtag=\"word-copyright\"></strong> <span langtag=\"application\"></span> &copy; 2018-2020</div>\n    </div>\n</div>\n\n</body>\n</html>\n\n\n<script type=\"text/javascript\">\n    window.nps = { \"web_base_url\": {{.web_base_url}} }\n    // Login Page Flipbox control\n    function login() {\n        $.ajax({\n            type: \"POST\",\n            url: \"{{.web_base_url}}/login/verify\",\n            data: $(\"form\").serializeArray(),\n            success: function (res) {\n                if (res.status) {\n                    window.location.href = \"{{.web_base_url}}/index/index\"\n                } else {\n                    alert(res.msg)\n                }\n            }\n        })\n        return false\n    }\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "web/views/login/register.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n    <title langtag=\"title-register\"></title>\n\n    <!-- Mainly scripts -->\n    <!-- Latest compiled and minified CSS -->\n    <link href=\"{{.web_base_url}}/static/css/fontawesome.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/solid.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/style.css\" rel=\"stylesheet\">\n\n    <!-- Latest compiled and minified JavaScript -->\n    <script src=\"{{.web_base_url}}/static/js/jquery-3.4.1.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/bootstrap.min.js\"></script>\n    <!-- Latest compiled and minified Locales -->\n    <script src=\"{{.web_base_url}}/static/js/language.js\" type=\"text/javascript\"></script>\n\n</head>\n\n<body class=\"gray-bg\">\n    <div class=\"row border-bottom\">\n        <nav class=\"navbar navbar-static-top navbar-right\" role=\"navigation\" style=\"margin: 20px 40px\">\n            <div></div>\n            <span class=\"btn-group dropdown\">\n                <button id=\"languagemenu\" class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\"><i class=\"fa fa-globe-asia fa-lg\"></i><span></span></button>\n                <ul class=\"dropdown-menu\"></ul>\n            </span>\n        </nav>\n    </div>\n    <div class=\"middle-box text-center loginscreen   animated fadeInDown\">\n        <div>\n            <h1 class=\"logo-name\" langtag=\"application\"></h1>\n        </div>\n        <h3 langtag=\"info-register\"></h3>\n        <p langtag=\"info-createaccount\"></p>\n        <form class=\"m-t\" role=\"form\" onsubmit=\"return false\">\n            <div class=\"form-group\">\n                <input type=\"text\" class=\"form-control\" placeholder=\"username\" name=\"username\" required=\"\" langtag=\"word-username\">\n            </div>\n            <div class=\"form-group\">\n                <input type=\"password\" class=\"form-control\" placeholder=\"password\" name=\"password\" required=\"\" langtag=\"word-password\">\n            </div>\n            <button onclick=\"register()\" type=\"submit\" class=\"btn btn-primary block full-width m-b\" langtag=\"word-register\"></button>\n            <p class=\"text-muted text-center\"><small langtag=\"info-haveaccount\"></small></p>\n            <a class=\"btn btn-sm btn-white btn-block\" href=\"{{.web_base_url}}/login/index\" langtag=\"word-login\"></a>\n        </form>\n    </div>\n    <hr/>\n    <div class=\"footer\">\n        <div class=\"pull-right\">\n            <span langtag=\"word-readmore\"></span> <strong><a href=\"https://ehang.io/nps\" langtag=\"word-go\"></a></strong>\n        </div>\n        <div><strong langtag=\"word-copyright\"></strong> <span langtag=\"application\"></span> &copy; 2018-2019</div>\n    </div>\n<script>\n    window.nps = { \"web_base_url\": {{.web_base_url}} }\n    function register() {\n        $.ajax({\n            type: \"POST\",\n            url: \"{{.web_base_url}}/login/register\",\n            data: $(\"form\").serializeArray(),\n            success: function (res) {\n                alert(res.msg)\n                if (res.status) {\n                    window.location.href = \"{{.web_base_url}}/login/index\"\n                }\n            }\n        })\n        return false\n    }\n</script>\n</body>\n\n</html>\n"
  },
  {
    "path": "web/views/public/error.html",
    "content": "<div class=\"page-error tile\">\n    <h1><i class=\"fa fa-exclamation-circle\"></i> Error 404: Page not found</h1>\n    <p>The page you have requested is not found.</p>\n    <p><a class=\"btn btn-primary\" href=\"javascript:window.history.back();\">Go Back</a></p>\n</div>"
  },
  {
    "path": "web/views/public/layout.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n\n    <title langtag=\"title-admin\"></title>\n\n    <!-- Mainly scripts -->\n    <!-- Latest compiled and minified CSS -->\n    <link href=\"{{.web_base_url}}/static/css/fontawesome.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/solid.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/bootstrap-table.min.css\" rel=\"stylesheet\">\n    <link href=\"{{.web_base_url}}/static/css/style.css\" rel=\"stylesheet\">\n\n    <!-- Latest compiled and minified JavaScript -->\n    <script src=\"{{.web_base_url}}/static/js/jquery-3.4.1.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/popper.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/bootstrap.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/bootstrap-table.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/bootstrap-table-locale-all.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/echarts.min.js\"></script>\n    <script src=\"{{.web_base_url}}/static/js/inspinia.js\"></script>\n    <!-- Latest compiled and minified Locales -->\n    <script src=\"{{.web_base_url}}/static/js/language.js\" type=\"text/javascript\"></script>\n\n</head>\n\n<body class=\"pace-done fixed-nav fixed-nav-basic\">\n<div id=\"wrapper\">\n    <nav class=\"navbar-default navbar-static-side\" role=\"navigation\">\n        <div class=\"sidebar-collapse\">\n            <ul class=\"nav metismenu\" id=\"side-menu\">\n                <li class=\"nav-header\">\n                    <div class=\"dropdown profile-element\">\n                    {{if eq true .isAdmin}}\n                        <span><i class=\"fa fa-user-cog fa-3x\"></i></span>\n                        <span class=\"clear\"> <span class=\"block m-t-xs\"><strong class=\"font-bold\" langtag=\"word-admin\"></strong></span>\n                        <span class=\"text-muted text-xs block\" langtag=\"word-system\">\n                    {{else}}\n                        <span><i class=\"fa fa-user fa-3x\"></i></span>\n                        <span class=\"clear\"> <span class=\"block m-t-xs\"><strong class=\"font-bold\">{{.username}}</strong></span>\n                        <span class=\"text-muted text-xs block\" langtag=\"word-user\">\n                    {{end}}\n                        </span>\n                    </div>\n                    <div class=\"logo-element\" langtag=\"application\"></div>\n                </li>\n                <li class=\"{{if eq \"index\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/\"><i class=\"fa fa-tachometer-alt fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"word-dashboard\"></span></a>\n                </li>\n                <li class=\"{{if eq \"client\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/client/list\"><i class=\"fa fa-desktop fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"word-client\"></span></a>\n                </li>\n                <li class=\"{{if eq \"host\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/hostlist\"><i class=\"fa fa-globe fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-host\"></span></a>\n                </li>\n                <li class=\"{{if eq \"tcp\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/tcp\"><i class=\"fa fa-retweet fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-tcp\"></span></a>\n                </li>\n                <li class=\"{{if eq \"udp\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/udp\"><i class=\"fa fa-random fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-udp\"></span></a>\n                </li>\n                <li class=\"{{if eq \"http\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/http\"><i class=\"fa fa-server fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-httpproxy\"></span></a>\n                </li>\n                <li class=\"{{if eq \"socks5\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/socks5\"><i class=\"fa fa-layer-group fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-socks5\"></span></a>\n                </li>\n                <li class=\"{{if eq \"secret\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/secret\"><i class=\"fa fa-low-vision fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-secret\"></span></a>\n                </li>\n                <li class=\"{{if eq \"p2p\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/p2p\"><i class=\"fa fa-exchange-alt fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-p2p\"></span></a>\n                </li>\n                <li class=\"{{if eq \"file\" .menu}}active{{end}}\">\n                    <a href=\"{{.web_base_url}}/index/file\"><i class=\"fa fa-briefcase fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"scheme-file\"></span></a>\n                </li>\n                <li class=\"{{if eq \"help\" .menu}}active{{end}}\">\n                    <a href=\"https://ehang.io/nps/documents\" target=\"_blank\"><i class=\"fa fa-lightbulb fa-lg\"></i>\n                    <span class=\"nav-label\" langtag=\"word-help\"></span></a>\n                </li>\n            </ul>\n        </div>\n    </nav>\n    <div id=\"page-wrapper\" class=\"gray-bg\">\n        <div class=\"row border-bottom\">\n            <nav class=\"navbar white-bg navbar-fixed-top\" role=\"navigation\" style=\"margin-bottom: 0\">\n\n                <div class=\"navbar-header\">\n                    <a class=\"navbar-minimalize minimalize-styl-2 btn btn-primary \" href=\"#\"><i class=\"fa fa-bars\"></i></a>\n                </div>\n                <ul class=\"nav navbar-top-links navbar-right\">\n                    <li>\n                        <span class=\"m-r-sm text-muted welcome-message\"><span langtag=\"word-welcome\"></span>\n                        <a href=\"https://ehang.io/nps\" langtag=\"application\"></a></span>\n                    </li>\n                    <li>\n                        <span class=\"btn-group dropdown\">\n                            <button id=\"languagemenu\" class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\"><i class=\"fa fa-globe-asia fa-lg\"></i><span></span></button>\n                            <ul class=\"dropdown-menu\"></ul>\n                        </span>\n                    </li>\n                    <li>\n                        <a href=\"{{.web_base_url}}/login/out\">\n                            <i class=\"fa fa-sign-in-alt\"></i><span langtag=\"word-logout\"></span>\n                        </a>\n                    </li>\n                </ul>\n            </nav>\n        </div>\n\n    {{.LayoutContent}}\n\n        <div class=\"footer fixed\">\n            <div class=\"float-right\">\n                <span langtag=\"word-readmore\"></span> <strong><a href=\"https://ehang.io/nps\" langtag=\"word-go\"></a></strong>\n            </div>\n            <div><strong langtag=\"word-copyright\"></strong> <span langtag=\"application\"></span> &copy; 2018-2020</div>\n        </div>\n    </div>\n</div>\n\n</body>\n</html>\n\n<script>\n    window.nps = { \"web_base_url\": {{.web_base_url}} }\n/*     googleTranslateElementInit()\n    \n     function googleTranslateElementInit() {\n         new google.translate.TranslateElement({\n             layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL\n         }, 'wrapper');\n     }\n*/\n</script>\n{{/*<script src=\"http://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit\"></script>*/}}\n\n"
  }
]