Repository: ehang-io/nps Branch: master Commit: ab648d6f0c61 Files: 129 Total size: 710.5 KB Directory structure: gitextract_d7ijj7nu/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── release.yml ├── .gitignore ├── .travis.yml ├── Dockerfile.npc ├── Dockerfile.nps ├── LICENSE ├── Makefile ├── README.md ├── README_zh.md ├── bridge/ │ └── bridge.go ├── build.android.sh ├── build.assets.sh ├── build.sh ├── client/ │ ├── client.go │ ├── control.go │ ├── health.go │ ├── local.go │ └── register.go ├── conf/ │ ├── clients.json │ ├── hosts.json │ ├── multi_account.conf │ ├── npc.conf │ ├── nps.conf │ ├── server.key │ ├── server.pem │ └── tasks.json ├── docs/ │ ├── .nojekyll │ ├── README.md │ ├── _coverpage.md │ ├── _navbar.md │ ├── _sidebar.md │ ├── api.md │ ├── contribute.md │ ├── description.md │ ├── discuss.md │ ├── donate.md │ ├── example.md │ ├── faq.md │ ├── feature.md │ ├── index.html │ ├── install.md │ ├── introduction.md │ ├── npc_extend.md │ ├── npc_sdk.md │ ├── nps_extend.md │ ├── nps_use.md │ ├── run.md │ ├── server_config.md │ ├── thanks.md │ ├── use.md │ └── webapi.md ├── go.mod ├── go.sum ├── lib/ │ ├── cache/ │ │ └── lru.go │ ├── common/ │ │ ├── const.go │ │ ├── logs.go │ │ ├── netpackager.go │ │ ├── pool.go │ │ ├── pprof.go │ │ ├── run.go │ │ └── util.go │ ├── config/ │ │ ├── config.go │ │ └── config_test.go │ ├── conn/ │ │ ├── conn.go │ │ ├── link.go │ │ ├── listener.go │ │ └── snappy.go │ ├── crypt/ │ │ ├── clientHello.go │ │ ├── crypt.go │ │ └── tls.go │ ├── daemon/ │ │ ├── daemon.go │ │ └── reload.go │ ├── file/ │ │ ├── db.go │ │ ├── file.go │ │ ├── obj.go │ │ └── sort.go │ ├── goroutine/ │ │ └── pool.go │ ├── install/ │ │ └── install.go │ ├── pmux/ │ │ ├── pconn.go │ │ ├── plistener.go │ │ ├── pmux.go │ │ └── pmux_test.go │ ├── rate/ │ │ ├── conn.go │ │ └── rate.go │ ├── sheap/ │ │ └── heap.go │ └── version/ │ └── version.go ├── server/ │ ├── connection/ │ │ └── connection.go │ ├── proxy/ │ │ ├── base.go │ │ ├── http.go │ │ ├── https.go │ │ ├── p2p.go │ │ ├── socks5.go │ │ ├── tcp.go │ │ ├── transport.go │ │ ├── transport_windows.go │ │ └── udp.go │ ├── server.go │ ├── test/ │ │ └── test.go │ └── tool/ │ └── utils.go └── web/ ├── controllers/ │ ├── auth.go │ ├── base.go │ ├── client.go │ ├── index.go │ └── login.go ├── routers/ │ └── router.go ├── static/ │ ├── css/ │ │ ├── datatables.css │ │ └── style.css │ ├── js/ │ │ ├── inspinia.js │ │ └── language.js │ └── page/ │ ├── error.html │ └── languages.xml └── views/ ├── client/ │ ├── add.html │ ├── edit.html │ └── list.html ├── index/ │ ├── add.html │ ├── edit.html │ ├── hadd.html │ ├── hedit.html │ ├── help.html │ ├── hlist.html │ ├── index.html │ └── list.html ├── login/ │ ├── index.html │ └── register.html └── public/ ├── error.html └── layout.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.js linguist-language=golang *.css linguist-language=golang *.html linguist-language=golang ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Opening '...' 2. Click on '....' 3. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots or logs** Add screenshots or logs to help explain your problem. **Server (please complete the following information):** - OS: [e.g. Centos, Windows] - ARCH: [e.g. Amd64, Arm] - Tunnel [e.g. TCP, HTTP] - Version [e.g. 0.24.0] **Client (please complete the following information):** - OS: [e.g. Centos, Windows] - ARCH: [e.g. Amd64, Arm] - Tunnel [e.g. TCP, HTTP] - Version [e.g. 0.24.0] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: release: types: [published] branches: [ master ] jobs: build_assets: runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: 1.15 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go get -v -t -d ./... if [ -f Gopkg.toml ]; then curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - name: Build run: | chmod +x build.assets.sh ./build.assets.sh - name: Upload uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: | freebsd_386_client.tar.gz freebsd_386_server.tar.gz freebsd_amd64_client.tar.gz freebsd_amd64_server.tar.gz freebsd_arm_client.tar.gz freebsd_arm_server.tar.gz linux_386_client.tar.gz linux_386_server.tar.gz linux_amd64_client.tar.gz linux_amd64_server.tar.gz linux_arm64_client.tar.gz linux_arm64_server.tar.gz linux_arm_v5_client.tar.gz linux_arm_v6_client.tar.gz linux_arm_v7_client.tar.gz linux_arm_v5_server.tar.gz linux_arm_v6_server.tar.gz linux_arm_v7_server.tar.gz linux_mips64le_client.tar.gz linux_mips64le_server.tar.gz linux_mips64_client.tar.gz linux_mips64_server.tar.gz linux_mipsle_client.tar.gz linux_mipsle_server.tar.gz linux_mips_client.tar.gz linux_mips_server.tar.gz darwin_amd64_client.tar.gz darwin_amd64_server.tar.gz windows_386_client.tar.gz windows_386_server.tar.gz windows_amd64_client.tar.gz windows_amd64_server.tar.gz npc_sdk.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build_android: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Build run: | chmod +x build.android.sh 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 - name: Upload uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: | android_client.apk env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build_spk: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Build run: | git clone https://github.com/cnlh/spksrc.git ~/spksrc mkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/ docker run -id --name spksrc --env VERSION=${{ env.RELEASE_VERSION }} -e GOPROXY=direct -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash docker exec spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make' cp ~/spksrc/packages/npc_noarch-all_${{ env.RELEASE_VERSION }}-1.spk ./npc_syno.spk - name: Upload uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: | npc_syno.spk env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build_docker: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Cache Docker layers uses: actions/cache@v2 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push nps uses: docker/build-push-action@v2 with: context: . file: ./Dockerfile.nps platforms: linux/amd64,linux/arm,linux/arm64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/nps:latest ${{ secrets.DOCKERHUB_USERNAME }}/nps:${{ env.RELEASE_VERSION }} - name: Build and push npc uses: docker/build-push-action@v2 with: context: . file: ./Dockerfile.npc platforms: linux/amd64,linux/arm,linux/arm64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/npc:latest ${{ secrets.DOCKERHUB_USERNAME }}/npc:${{ env.RELEASE_VERSION }} ================================================ FILE: .gitignore ================================================ .idea nps npc ================================================ FILE: .travis.yml ================================================ language: go go: - 1.14.x services: - docker script: - GOPROXY=direct go test -v ./cmd/nps/ os: - linux before_deploy: - chmod +x ./build.sh && chmod +x ./build.android.sh && ./build.sh deploy: provider: releases edge: true token: ${GH_TOKEN} cleanup: false file: - freebsd_386_client.tar.gz - freebsd_386_server.tar.gz - freebsd_amd64_client.tar.gz - freebsd_amd64_server.tar.gz - freebsd_arm_client.tar.gz - freebsd_arm_server.tar.gz - linux_386_client.tar.gz - linux_386_server.tar.gz - linux_amd64_client.tar.gz - linux_amd64_server.tar.gz - linux_arm64_client.tar.gz - linux_arm64_server.tar.gz - linux_arm_v5_client.tar.gz - linux_arm_v6_client.tar.gz - linux_arm_v7_client.tar.gz - linux_arm_v5_server.tar.gz - linux_arm_v6_server.tar.gz - linux_arm_v7_server.tar.gz - linux_mips64le_client.tar.gz - linux_mips64le_server.tar.gz - linux_mips64_client.tar.gz - linux_mips64_server.tar.gz - linux_mipsle_client.tar.gz - linux_mipsle_server.tar.gz - linux_mips_client.tar.gz - linux_mips_server.tar.gz - darwin_amd64_client.tar.gz - darwin_amd64_server.tar.gz - windows_386_client.tar.gz - windows_386_server.tar.gz - windows_amd64_client.tar.gz - windows_amd64_server.tar.gz - npc_syno.spk - npc_sdk.tar.gz - android_client.apk on: tags: true all_branches: true ================================================ FILE: Dockerfile.npc ================================================ FROM golang:1.15 as builder ARG GOPROXY=direct WORKDIR /go/src/ehang.io/nps COPY . . RUN go get -d -v ./... RUN CGO_ENABLED=0 go build -ldflags="-w -s -extldflags -static" ./cmd/npc/npc.go FROM scratch COPY --from=builder /go/src/ehang.io/nps/npc / VOLUME /conf ENTRYPOINT ["/npc"] ================================================ FILE: Dockerfile.nps ================================================ FROM golang:1.15 as builder ARG GOPROXY=direct WORKDIR /go/src/ehang.io/nps COPY . . RUN go get -d -v ./... RUN CGO_ENABLED=0 go build -ldflags="-w -s -extldflags -static" ./cmd/nps/nps.go FROM scratch COPY --from=builder /go/src/ehang.io/nps/nps / COPY --from=builder /go/src/ehang.io/nps/web /web VOLUME /conf CMD ["/nps"] ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: Makefile ================================================ SOURCE_FILES?=./... TEST_PATTERN?=. TEST_OPTIONS?= export PATH := ./bin:$(PATH) export GO111MODULE := on export GOPROXY := https://gocenter.io # Build a beta version of goreleaser build: go build cmd/nps/nps.go go build cmd/npc/npc.go .PHONY: build # Install all the build and lint dependencies setup: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh curl -L https://git.io/misspell | sh go mod download .PHONY: setup # Run all the tests test: go test $(TEST_OPTIONS) -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m .PHONY: test # Run all the tests and opens the coverage report cover: test go tool cover -html=coverage.txt .PHONY: cover # gofmt and goimports all go files fmt: find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done .PHONY: fmt # Run all the linters lint: # TODO: fix tests and lll issues ./bin/golangci-lint run --tests=false --enable-all --disable=lll ./... ./bin/misspell -error **/* .PHONY: lint # Clean go.mod go-mod-tidy: @go mod tidy -v @git diff HEAD @git diff-index --quiet HEAD .PHONY: go-mod-tidy # Run all the tests and code checks ci: build test lint go-mod-tidy .PHONY: ci # Generate the static documentation static: @hugo --enableGitInfo --source www .PHONY: static # Show to-do items per file. todo: @grep \ --exclude-dir=vendor \ --exclude-dir=node_modules \ --exclude=Makefile \ --text \ --color \ -nRo -E ' TODO:.*|SkipNow' . .PHONY: todo clean: rm npc nps .PHONY: clean .DEFAULT_GOAL := build ================================================ FILE: README.md ================================================ # NPS ![](https://img.shields.io/github/stars/ehang-io/nps.svg) ![](https://img.shields.io/github/forks/ehang-io/nps.svg) [![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) ![Release](https://github.com/ehang-io/nps/workflows/Release/badge.svg) ![GitHub All Releases](https://img.shields.io/github/downloads/ehang-io/nps/total) [README](https://github.com/ehang-io/nps/blob/master/README.md)|[中文文档](https://github.com/ehang-io/nps/blob/master/README_zh.md) NPS is a lightweight, high-performance, powerful **intranet penetration** proxy server, with a powerful web management terminal. ![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true) ## Feature - Comprehensive protocol support, compatible with almost all commonly used protocols, such as tcp, udp, http(s), socks5, p2p, http proxy ... - Full platform compatibility (linux, windows, macos, Synology, etc.), support installation as a system service simply. - Comprehensive control, both client and server control are allowed. - Https integration, support to convert backend proxy and web services to https, and support multiple certificates. - Just simple configuration on web ui can complete most requirements. - Complete information display, such as traffic, system information, real-time bandwidth, client version, etc. - Powerful extension functions, everything is available (cache, compression, encryption, traffic limit, bandwidth limit, port reuse, etc.) - Domain name resolution has functions such as custom headers, 404 page configuration, host modification, site protection, URL routing, and pan-resolution. - Multi-user and user registration support on server. **Didn't find the feature you want? It doesn't matter, click [Enter the document](https://ehang-io.github.io/nps/) to find it!** ## Quick start ### Installation > [releases](https://github.com/ehang-io/nps/releases) Download the corresponding system version, the server and client are separate. ### Server start After downloading the server compressed package, unzip it, and then enter the unzipped folder. - execute installation command For linux、darwin ```sudo ./nps install``` For windows, run cmd as administrator and enter the installation directory ```nps.exe install``` - default ports The default configuration file of nps use 80,443,8080,8024 ports 80 and 443 ports for host mode default ports 8080 for web management access port 8024 for net bridge port, to communicate between server and client - start up For linux、darwin ```sudo nps start``` For windows, run cmd as administrator and enter the program directory ```nps.exe start``` ```After installation, the windows configuration file is located at C:\Program Files\nps, linux or darwin is located at /etc/nps``` **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).** - Access server IP:web service port (default is 8080). - Login with username and password (default is admin/123, must be modified when officially used). - Create a client. ### Client connection - Click the + sign in front of the client in web management and copy the startup command. - Execute the startup command, Linux can be executed directly, Windows will replace ./npc with npc.exe and execute it with cmd. If you need to register to the system service, you can check [Register to the system service](https://ehang-io.github.io/nps/#/use?id=注册到系统服务) ### Configuration - After the client connects, configure the corresponding penetration service in the web. - For more advanced usage, see [Complete Documentation](https://ehang-io.github.io/nps/) ## Contribution - If you encounter a bug, you can submit it to the dev branch directly. - If you encounter a problem, you can feedback through the issue. - 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. - If there is feedback on new features, you can feedback via issues or qq group. ================================================ FILE: README_zh.md ================================================ # nps ![](https://img.shields.io/github/stars/ehang-io/nps.svg) ![](https://img.shields.io/github/forks/ehang-io/nps.svg) [![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) ![Release](https://github.com/ehang-io/nps/workflows/Release/badge.svg) ![GitHub All Releases](https://img.shields.io/github/downloads/ehang-io/nps/total) [README](https://github.com/ehang-io/nps/blob/master/README.md)|[中文文档](https://github.com/ehang-io/nps/blob/master/README_zh.md) nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**、**p2p等**,并带有功能强大的web管理端。 ## 背景 ![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true) 1. 做微信公众号开发、小程序开发等----> 域名代理模式 2. 想在外网通过ssh连接内网的机器,做云服务器到内网服务器端口的映射,----> tcp代理模式 3. 在非内网环境下使用内网dns,或者需要通过udp访问内网机器等----> udp代理模式 4. 在外网使用HTTP代理访问内网站点----> http代理模式 5. 搭建一个内网穿透ss,在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式 ## 特点 - 协议支持全面,兼容几乎所有常用协议,例如tcp、udp、http(s)、socks5、p2p、http代理... - 全平台兼容(linux、windows、macos、群辉等),支持一键安装为系统服务 - 控制全面,同时支持服务端和客户端控制 - https集成,支持将后端代理和web服务转成https,同时支持多证书 - 操作简单,只需简单的配置即可在web ui上完成其余操作 - 展示信息全面,流量、系统信息、即时带宽、客户端版本等 - 扩展功能强大,该有的都有了(缓存、压缩、加密、流量限制、带宽限制、端口复用等等) - 域名解析具备自定义header、404页面配置、host修改、站点保护、URL路由、泛解析等功能 - 服务端支持多用户和用户注册功能 **没找到你想要的功能?不要紧,点击[进入文档](https://ehang-io.github.io/nps)查找吧** ## 快速开始 ### 安装 > [releases](https://github.com/ehang-io/nps/releases) 下载对应的系统版本即可,服务端和客户端是单独的 ### 服务端启动 下载完服务器压缩包后,解压,然后进入解压后的文件夹 - 执行安装命令 对于linux|darwin ```sudo ./nps install``` 对于windows,管理员身份运行cmd,进入安装目录 ```nps.exe install``` - 默认端口 nps默认配置文件使用了80,443,8080,8024端口 80与443端口为域名解析模式默认端口 8080为web管理访问端口 8024为网桥端口,用于客户端与服务器通信 - 启动 对于linux|darwin ```sudo nps start``` 对于windows,管理员身份运行cmd,进入程序目录 ```nps.exe start``` ```安装后windows配置文件位于 C:\Program Files\nps,linux和darwin位于/etc/nps``` **如果发现没有启动成功,可以查看日志(Windows日志文件位于当前运行目录下,linux和darwin位于/var/log/nps.log)** - 访问服务端ip:web服务端口(默认为8080) - 使用用户名和密码登陆(默认admin/123,正式使用一定要更改) - 创建客户端 ### 客户端连接 - 点击web管理中客户端前的+号,复制启动命令 - 执行启动命令,linux直接执行即可,windows将./npc换成npc.exe用cmd执行 如果需要注册到系统服务可查看[注册到系统服务](https://ehang-io.github.io/nps/#/use?id=注册到系统服务) ### 配置 - 客户端连接后,在web中配置对应穿透服务即可 - 更多高级用法见[完整文档](https://ehang-io.github.io/nps/) ## 贡献 - 如果遇到bug可以直接提交至dev分支 - 使用遇到问题可以通过issues反馈 - 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支 - 如果有新的功能特性反馈,可以通过issues或者qq群反馈 ================================================ FILE: bridge/bridge.go ================================================ package bridge import ( "ehang.io/nps-mux" "encoding/binary" "errors" "fmt" "net" "os" "strconv" "strings" "sync" "time" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/file" "ehang.io/nps/lib/version" "ehang.io/nps/server/connection" "ehang.io/nps/server/tool" "github.com/astaxie/beego" "github.com/astaxie/beego/logs" ) type Client struct { tunnel *nps_mux.Mux signal *conn.Conn file *nps_mux.Mux Version string retryTime int // it will be add 1 when ping not ok until to 3 will close the client } func NewClient(t, f *nps_mux.Mux, s *conn.Conn, vs string) *Client { return &Client{ signal: s, tunnel: t, file: f, Version: vs, } } type Bridge struct { TunnelPort int //通信隧道端口 Client sync.Map Register sync.Map tunnelType string //bridge type kcp or tcp OpenTask chan *file.Tunnel CloseTask chan *file.Tunnel CloseClient chan int SecretChan chan *conn.Secret ipVerify bool runList sync.Map //map[int]interface{} disconnectTime int } func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList sync.Map, disconnectTime int) *Bridge { return &Bridge{ TunnelPort: tunnelPort, tunnelType: tunnelType, OpenTask: make(chan *file.Tunnel), CloseTask: make(chan *file.Tunnel), CloseClient: make(chan int), SecretChan: make(chan *conn.Secret), ipVerify: ipVerify, runList: runList, disconnectTime: disconnectTime, } } func (s *Bridge) StartTunnel() error { go s.ping() if s.tunnelType == "kcp" { logs.Info("server start, the bridge type is %s, the bridge port is %d", s.tunnelType, s.TunnelPort) return conn.NewKcpListenerAndProcess(beego.AppConfig.String("bridge_ip")+":"+beego.AppConfig.String("bridge_port"), func(c net.Conn) { s.cliProcess(conn.NewConn(c)) }) } else { listener, err := connection.GetBridgeListener(s.tunnelType) if err != nil { logs.Error(err) os.Exit(0) return err } conn.Accept(listener, func(c net.Conn) { s.cliProcess(conn.NewConn(c)) }) } return nil } //get health information form client func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) { for { if info, status, err := c.GetHealthInfo(); err != nil { break } else if !status { //the status is true , return target to the targetArr file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*file.Tunnel) if v.Client.Id == id && v.Mode == "tcp" && strings.Contains(v.Target.TargetStr, info) { v.Lock() if v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) { v.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, "\n")) } v.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info) if v.HealthRemoveArr == nil { v.HealthRemoveArr = make([]string, 0) } v.HealthRemoveArr = append(v.HealthRemoveArr, info) v.Unlock() } return true }) file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*file.Host) if v.Client.Id == id && strings.Contains(v.Target.TargetStr, info) { v.Lock() if v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) { v.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, "\n")) } v.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info) if v.HealthRemoveArr == nil { v.HealthRemoveArr = make([]string, 0) } v.HealthRemoveArr = append(v.HealthRemoveArr, info) v.Unlock() } return true }) } else { //the status is false,remove target from the targetArr file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*file.Tunnel) if v.Client.Id == id && v.Mode == "tcp" && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) { v.Lock() v.Target.TargetArr = append(v.Target.TargetArr, info) v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info) v.Unlock() } return true }) file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*file.Host) if v.Client.Id == id && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) { v.Lock() v.Target.TargetArr = append(v.Target.TargetArr, info) v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info) v.Unlock() } return true }) } } s.DelClient(id) } //验证失败,返回错误验证flag,并且关闭连接 func (s *Bridge) verifyError(c *conn.Conn) { c.Write([]byte(common.VERIFY_EER)) } func (s *Bridge) verifySuccess(c *conn.Conn) { c.Write([]byte(common.VERIFY_SUCCESS)) } func (s *Bridge) cliProcess(c *conn.Conn) { //read test flag if _, err := c.GetShortContent(3); err != nil { logs.Info("The client %s connect error", c.Conn.RemoteAddr(), err.Error()) return } //version check if b, err := c.GetShortLenContent(); err != nil || string(b) != version.GetVersion() { logs.Info("The client %s version does not match", c.Conn.RemoteAddr()) c.Close() return } //version get var vs []byte var err error if vs, err = c.GetShortLenContent(); err != nil { logs.Info("get client %s version error", err.Error()) c.Close() return } //write server version to client c.Write([]byte(crypt.Md5(version.GetVersion()))) c.SetReadDeadlineBySecond(5) var buf []byte //get vKey from client if buf, err = c.GetShortContent(32); err != nil { c.Close() return } //verify id, err := file.GetDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String()) if err != nil { logs.Info("Current client connection validation error, close this client:", c.Conn.RemoteAddr()) s.verifyError(c) return } else { s.verifySuccess(c) } if flag, err := c.ReadFlag(); err == nil { s.typeDeal(flag, c, id, string(vs)) } else { logs.Warn(err, flag) } return } func (s *Bridge) DelClient(id int) { if v, ok := s.Client.Load(id); ok { if v.(*Client).signal != nil { v.(*Client).signal.Close() } s.Client.Delete(id) if file.GetDb().IsPubClient(id) { return } if c, err := file.GetDb().GetClient(id); err == nil { s.CloseClient <- c.Id } } } //use different func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) { isPub := file.GetDb().IsPubClient(id) switch typeVal { case common.WORK_MAIN: if isPub { c.Close() return } tcpConn, ok := c.Conn.(*net.TCPConn) if ok { // add tcp keep alive option for signal connection _ = tcpConn.SetKeepAlive(true) _ = tcpConn.SetKeepAlivePeriod(5 * time.Second) } //the vKey connect by another ,close the client of before if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c, vs)); ok { if v.(*Client).signal != nil { v.(*Client).signal.WriteClose() } v.(*Client).signal = c v.(*Client).Version = vs } go s.GetHealthFromClient(id, c) logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr()) case common.WORK_CHAN: muxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime) if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil, vs)); ok { v.(*Client).tunnel = muxConn } case common.WORK_CONFIG: client, err := file.GetDb().GetClient(id) if err != nil || (!isPub && !client.ConfigConnAllow) { c.Close() return } binary.Write(c, binary.LittleEndian, isPub) go s.getConfig(c, isPub, client) case common.WORK_REGISTER: go s.register(c) case common.WORK_SECRET: if b, err := c.GetShortContent(32); err == nil { s.SecretChan <- conn.NewSecret(string(b), c) } else { logs.Error("secret error, failed to match the key successfully") } case common.WORK_FILE: muxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime) if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil, vs)); ok { v.(*Client).file = muxConn } case common.WORK_P2P: //read md5 secret if b, err := c.GetShortContent(32); err != nil { logs.Error("p2p error,", err.Error()) } else if t := file.GetDb().GetTaskByMd5Password(string(b)); t == nil { logs.Error("p2p error, failed to match the key successfully") } else { if v, ok := s.Client.Load(t.Client.Id); !ok { return } else { //向密钥对应的客户端发送与服务端udp建立连接信息,地址,密钥 v.(*Client).signal.Write([]byte(common.NEW_UDP_CONN)) svrAddr := beego.AppConfig.String("p2p_ip") + ":" + beego.AppConfig.String("p2p_port") if err != nil { logs.Warn("get local udp addr error") return } v.(*Client).signal.WriteLenContent([]byte(svrAddr)) v.(*Client).signal.WriteLenContent(b) //向该请求者发送建立连接请求,服务器地址 c.WriteLenContent([]byte(svrAddr)) } } } c.SetAlive(s.tunnelType) return } //register ip func (s *Bridge) register(c *conn.Conn) { var hour int32 if err := binary.Read(c, binary.LittleEndian, &hour); err == nil { s.Register.Store(common.GetIpByAddr(c.Conn.RemoteAddr().String()), time.Now().Add(time.Hour*time.Duration(hour))) } } func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) { //if the proxy type is local if link.LocalProxy { target, err = net.Dial("tcp", link.Host) return } if v, ok := s.Client.Load(clientId); ok { //If ip is restricted to do ip verification if s.ipVerify { ip := common.GetIpByAddr(link.RemoteAddr) if v, ok := s.Register.Load(ip); !ok { return nil, errors.New(fmt.Sprintf("The ip %s is not in the validation list", ip)) } else { if !v.(time.Time).After(time.Now()) { return nil, errors.New(fmt.Sprintf("The validity of the ip %s has expired", ip)) } } } var tunnel *nps_mux.Mux if t != nil && t.Mode == "file" { tunnel = v.(*Client).file } else { tunnel = v.(*Client).tunnel } if tunnel == nil { err = errors.New("the client connect error") return } if target, err = tunnel.NewConn(); err != nil { return } if t != nil && t.Mode == "file" { //TODO if t.mode is file ,not use crypt or compress link.Crypt = false link.Compress = false return } if _, err = conn.NewConn(target).SendInfo(link, ""); err != nil { logs.Info("new connect error ,the target %s refuse to connect", link.Host) return } } else { err = errors.New(fmt.Sprintf("the client %d is not connect", clientId)) } return } func (s *Bridge) ping() { ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() for { select { case <-ticker.C: arr := make([]int, 0) s.Client.Range(func(key, value interface{}) bool { v := value.(*Client) if v.tunnel == nil || v.signal == nil { v.retryTime += 1 if v.retryTime >= 3 { arr = append(arr, key.(int)) } return true } if v.tunnel.IsClose { arr = append(arr, key.(int)) } return true }) for _, v := range arr { logs.Info("the client %d closed", v) s.DelClient(v) } } } } //get config and add task from client config func (s *Bridge) getConfig(c *conn.Conn, isPub bool, client *file.Client) { var fail bool loop: for { flag, err := c.ReadFlag() if err != nil { break } switch flag { case common.WORK_STATUS: if b, err := c.GetShortContent(32); err != nil { break loop } else { var str string id, err := file.GetDb().GetClientIdByVkey(string(b)) if err != nil { break loop } file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*file.Host) if v.Client.Id == id { str += v.Remark + common.CONN_DATA_SEQ } return true }) file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*file.Tunnel) //if _, ok := s.runList[v.Id]; ok && v.Client.Id == id { if _, ok := s.runList.Load(v.Id); ok && v.Client.Id == id { str += v.Remark + common.CONN_DATA_SEQ } return true }) binary.Write(c, binary.LittleEndian, int32(len([]byte(str)))) binary.Write(c, binary.LittleEndian, []byte(str)) } case common.NEW_CONF: var err error if client, err = c.GetConfigInfo(); err != nil { fail = true c.WriteAddFail() break loop } else { if err = file.GetDb().NewClient(client); err != nil { fail = true c.WriteAddFail() break loop } c.WriteAddOk() c.Write([]byte(client.VerifyKey)) s.Client.Store(client.Id, NewClient(nil, nil, nil, "")) } case common.NEW_HOST: h, err := c.GetHostInfo() if err != nil { fail = true c.WriteAddFail() break loop } h.Client = client if h.Location == "" { h.Location = "/" } if !client.HasHost(h) { if file.GetDb().IsHostExist(h) { fail = true c.WriteAddFail() break loop } else { file.GetDb().NewHost(h) c.WriteAddOk() } } else { c.WriteAddOk() } case common.NEW_TASK: if t, err := c.GetTaskInfo(); err != nil { fail = true c.WriteAddFail() break loop } else { ports := common.GetPorts(t.Ports) targets := common.GetPorts(t.Target.TargetStr) if len(ports) > 1 && (t.Mode == "tcp" || t.Mode == "udp") && (len(ports) != len(targets)) { fail = true c.WriteAddFail() break loop } else if t.Mode == "secret" || t.Mode == "p2p" { ports = append(ports, 0) } if len(ports) == 0 { fail = true c.WriteAddFail() break loop } for i := 0; i < len(ports); i++ { tl := new(file.Tunnel) tl.Mode = t.Mode tl.Port = ports[i] tl.ServerIp = t.ServerIp if len(ports) == 1 { tl.Target = t.Target tl.Remark = t.Remark } else { tl.Remark = t.Remark + "_" + strconv.Itoa(tl.Port) tl.Target = new(file.Target) if t.TargetAddr != "" { tl.Target.TargetStr = t.TargetAddr + ":" + strconv.Itoa(targets[i]) } else { tl.Target.TargetStr = strconv.Itoa(targets[i]) } } tl.Id = int(file.GetDb().JsonDb.GetTaskId()) tl.Status = true tl.Flow = new(file.Flow) tl.NoStore = true tl.Client = client tl.Password = t.Password tl.LocalPath = t.LocalPath tl.StripPre = t.StripPre tl.MultiAccount = t.MultiAccount if !client.HasTunnel(tl) { if err := file.GetDb().NewTask(tl); err != nil { logs.Notice("Add task error ", err.Error()) fail = true c.WriteAddFail() break loop } if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secret" && t.Mode != "p2p" { fail = true c.WriteAddFail() break loop } else { s.OpenTask <- tl } } c.WriteAddOk() } } } } if fail && client != nil { s.DelClient(client.Id) } c.Close() } ================================================ FILE: build.android.sh ================================================ #/bin/bash cd /go apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev -y go get -u fyne.io/fyne/v2/cmd/fyne fyne.io/fyne/v2 #mkdir -p /go/src/fyne.io #cd src/fyne.io #git clone https://github.com/fyne-io/fyne.git #cd fyne #git checkout v1.2.0 #go install -v ./cmd/fyne #fyne package -os android fyne.io/fyne/cmd/hello echo "fyne install success" mkdir -p /go/src/ehang.io/nps cp -R /app/* /go/src/ehang.io/nps cd /go/src/ehang.io/nps #go get -u fyne.io/fyne fyne.io/fyne/cmd/fyne rm cmd/npc/sdk.go #go get -u ./... #go mod tidy #rm -rf /go/src/golang.org/x/mobile echo "tidy success" cd /go/src/ehang.io/nps go mod vendor cd vendor cp -R * /go/src cd .. rm -rf vendor #rm -rf ~/.cache/* echo "vendor success" cd gui/npc fyne package -appID org.nps.client -os android -icon ../../docs/logo.png mv npc.apk /app/android_client.apk echo "android build success" ================================================ FILE: build.assets.sh ================================================ export GOPROXY=direct sudo apt-get update sudo apt-get install gcc-mingw-w64-i686 gcc-multilib env 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 env 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 tar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.so npc_sdk.h CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf freebsd_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf freebsd_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf freebsd_arm_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm_v7_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm_v6_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm_v5_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mips64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mips64le_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mipsle_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mips_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf windows_386_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf windows_amd64_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf darwin_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 ================================================ FILE: build.sh ================================================ #/bash/sh export VERSION=0.26.10 export GOPROXY=direct sudo apt-get update sudo apt-get install gcc-mingw-w64-i686 gcc-multilib env 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 env 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 tar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.so npc_sdk.h wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz tar -xvf upx-3.95-amd64_linux.tar.xz cp upx-3.95-amd64_linux/upx ./ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf freebsd_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf freebsd_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf freebsd_arm_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm_v7_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm_v6_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm_v5_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mips64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mips64le_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mipsle_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf linux_mips_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf windows_386_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf windows_amd64_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf darwin_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go tar -czvf darwin_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go tar -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 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce docker --version docker 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 git clone https://github.com/cnlh/spksrc.git ~/spksrc mkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/ docker run -itd --name spksrc --env VERSION=$VERSION -e GOPROXY=direct -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash docker exec -it spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make arch-x64-7.0' cp ~/spksrc/packages/npc_x64-7.0_$VERSION-1.spk ./npc_syno.spk echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin export DOCKER_CLI_EXPERIMENTAL=enabled docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d docker buildx create --use --name mybuilder docker 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 . docker 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 . ================================================ FILE: client/client.go ================================================ package client import ( "bufio" "bytes" "ehang.io/nps-mux" "net" "net/http" "strconv" "sync" "time" "github.com/astaxie/beego/logs" "github.com/xtaci/kcp-go" "ehang.io/nps/lib/common" "ehang.io/nps/lib/config" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/crypt" ) type TRPClient struct { svrAddr string bridgeConnType string proxyUrl string vKey string p2pAddr map[string]string tunnel *nps_mux.Mux signal *conn.Conn ticker *time.Ticker cnf *config.Config disconnectTime int once sync.Once } //new client func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl string, cnf *config.Config, disconnectTime int) *TRPClient { return &TRPClient{ svrAddr: svraddr, p2pAddr: make(map[string]string, 0), vKey: vKey, bridgeConnType: bridgeConnType, proxyUrl: proxyUrl, cnf: cnf, disconnectTime: disconnectTime, once: sync.Once{}, } } var NowStatus int var CloseClient bool //start func (s *TRPClient) Start() { CloseClient = false retry: if CloseClient { return } NowStatus = 0 c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl) if err != nil { logs.Error("The connection server failed and will be reconnected in five seconds, error", err.Error()) time.Sleep(time.Second * 5) goto retry } if c == nil { logs.Error("Error data from server, and will be reconnected in five seconds") time.Sleep(time.Second * 5) goto retry } logs.Info("Successful connection with server %s", s.svrAddr) //monitor the connection go s.ping() s.signal = c //start a channel connection go s.newChan() //start health check if the it's open if s.cnf != nil && len(s.cnf.Healths) > 0 { go heathCheck(s.cnf.Healths, s.signal) } NowStatus = 1 //msg connection, eg udp s.handleMain() } //handle main connection func (s *TRPClient) handleMain() { for { flags, err := s.signal.ReadFlag() if err != nil { logs.Error("Accept server data error %s, end this service", err.Error()) break } switch flags { case common.NEW_UDP_CONN: //read server udp addr and password if lAddr, err := s.signal.GetShortLenContent(); err != nil { logs.Warn(err) return } else if pwd, err := s.signal.GetShortLenContent(); err == nil { var localAddr string //The local port remains unchanged for a certain period of time if v, ok := s.p2pAddr[crypt.Md5(string(pwd)+strconv.Itoa(int(time.Now().Unix()/100)))]; !ok { tmpConn, err := common.GetLocalUdpAddr() if err != nil { logs.Error(err) return } localAddr = tmpConn.LocalAddr().String() } else { localAddr = v } go s.newUdpConn(localAddr, string(lAddr), string(pwd)) } } } s.Close() } func (s *TRPClient) newUdpConn(localAddr, rAddr string, md5Password string) { var localConn net.PacketConn var err error var remoteAddress string if remoteAddress, localConn, err = handleP2PUdp(localAddr, rAddr, md5Password, common.WORK_P2P_PROVIDER); err != nil { logs.Error(err) return } l, err := kcp.ServeConn(nil, 150, 3, localConn) if err != nil { logs.Error(err) return } logs.Trace("start local p2p udp listen, local address", localConn.LocalAddr().String()) for { udpTunnel, err := l.AcceptKCP() if err != nil { logs.Error(err) l.Close() return } if udpTunnel.RemoteAddr().String() == string(remoteAddress) { conn.SetUdpSession(udpTunnel) logs.Trace("successful connection with client ,address %s", udpTunnel.RemoteAddr().String()) //read link info from remote conn.Accept(nps_mux.NewMux(udpTunnel, s.bridgeConnType, s.disconnectTime), func(c net.Conn) { go s.handleChan(c) }) break } } } //pmux tunnel func (s *TRPClient) newChan() { tunnel, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl) if err != nil { logs.Error("connect to ", s.svrAddr, "error:", err) return } s.tunnel = nps_mux.NewMux(tunnel.Conn, s.bridgeConnType, s.disconnectTime) for { src, err := s.tunnel.Accept() if err != nil { logs.Warn(err) s.Close() break } go s.handleChan(src) } } func (s *TRPClient) handleChan(src net.Conn) { lk, err := conn.NewConn(src).GetLinkInfo() if err != nil || lk == nil { src.Close() logs.Error("get connection info from server error ", err) return } //host for target processing lk.Host = common.FormatAddress(lk.Host) //if Conn type is http, read the request and log if lk.ConnType == "http" { if targetConn, err := net.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil { logs.Warn("connect to %s error %s", lk.Host, err.Error()) src.Close() } else { srcConn := conn.GetConn(src, lk.Crypt, lk.Compress, nil, false) go func() { common.CopyBuffer(srcConn, targetConn) srcConn.Close() targetConn.Close() }() for { if r, err := http.ReadRequest(bufio.NewReader(srcConn)); err != nil { srcConn.Close() targetConn.Close() break } else { logs.Trace("http request, method %s, host %s, url %s, remote address %s", r.Method, r.Host, r.URL.Path, r.RemoteAddr) r.Write(targetConn) } } } return } if lk.ConnType == "udp5" { logs.Trace("new %s connection with the goal of %s, remote address:%s", lk.ConnType, lk.Host, lk.RemoteAddr) s.handleUdp(src) } //connect to target if conn type is tcp or udp if targetConn, err := net.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil { logs.Warn("connect to %s error %s", lk.Host, err.Error()) src.Close() } else { logs.Trace("new %s connection with the goal of %s, remote address:%s", lk.ConnType, lk.Host, lk.RemoteAddr) conn.CopyWaitGroup(src, targetConn, lk.Crypt, lk.Compress, nil, nil, false, nil) } } func (s *TRPClient) handleUdp(serverConn net.Conn) { // bind a local udp port local, err := net.ListenUDP("udp", nil) defer serverConn.Close() if err != nil { logs.Error("bind local udp port error ", err.Error()) return } defer local.Close() go func() { defer serverConn.Close() b := common.BufPoolUdp.Get().([]byte) defer common.BufPoolUdp.Put(b) for { n, raddr, err := local.ReadFrom(b) if err != nil { logs.Error("read data from remote server error", err.Error()) } buf := bytes.Buffer{} dgram := common.NewUDPDatagram(common.NewUDPHeader(0, 0, common.ToSocksAddr(raddr)), b[:n]) dgram.Write(&buf) b, err := conn.GetLenBytes(buf.Bytes()) if err != nil { logs.Warn("get len bytes error", err.Error()) continue } if _, err := serverConn.Write(b); err != nil { logs.Error("write data to remote error", err.Error()) return } } }() b := common.BufPoolUdp.Get().([]byte) defer common.BufPoolUdp.Put(b) for { n, err := serverConn.Read(b) if err != nil { logs.Error("read udp data from server error ", err.Error()) return } udpData, err := common.ReadUDPDatagram(bytes.NewReader(b[:n])) if err != nil { logs.Error("unpack data error", err.Error()) return } raddr, err := net.ResolveUDPAddr("udp", udpData.Header.Addr.String()) if err != nil { logs.Error("build remote addr err", err.Error()) continue // drop silently } _, err = local.WriteTo(udpData.Data, raddr) if err != nil { logs.Error("write data to remote ", raddr.String(), "error", err.Error()) return } } } // Whether the monitor channel is closed func (s *TRPClient) ping() { s.ticker = time.NewTicker(time.Second * 5) loop: for { select { case <-s.ticker.C: if s.tunnel != nil && s.tunnel.IsClose { s.Close() break loop } } } } func (s *TRPClient) Close() { s.once.Do(s.closing) } func (s *TRPClient) closing() { CloseClient = true NowStatus = 0 if s.tunnel != nil { _ = s.tunnel.Close() } if s.signal != nil { _ = s.signal.Close() } if s.ticker != nil { s.ticker.Stop() } } ================================================ FILE: client/control.go ================================================ package client import ( "bufio" "encoding/base64" "encoding/binary" "errors" "fmt" "io/ioutil" "log" "math" "math/rand" "net" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "time" "ehang.io/nps/lib/common" "ehang.io/nps/lib/config" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/version" "github.com/astaxie/beego/logs" "github.com/xtaci/kcp-go" "golang.org/x/net/proxy" ) func GetTaskStatus(path string) { cnf, err := config.NewConfig(path) if err != nil { log.Fatalln(err) } c, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl) if err != nil { log.Fatalln(err) } if _, err := c.Write([]byte(common.WORK_STATUS)); err != nil { log.Fatalln(err) } //read now vKey and write to server if f, err := common.ReadAllFromFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt")); err != nil { log.Fatalln(err) } else if _, err := c.Write([]byte(crypt.Md5(string(f)))); err != nil { log.Fatalln(err) } var isPub bool binary.Read(c, binary.LittleEndian, &isPub) if l, err := c.GetLen(); err != nil { log.Fatalln(err) } else if b, err := c.GetShortContent(l); err != nil { log.Fatalln(err) } else { arr := strings.Split(string(b), common.CONN_DATA_SEQ) for _, v := range cnf.Hosts { if common.InStrArr(arr, v.Remark) { log.Println(v.Remark, "ok") } else { log.Println(v.Remark, "not running") } } for _, v := range cnf.Tasks { ports := common.GetPorts(v.Ports) if v.Mode == "secret" { ports = append(ports, 0) } for _, vv := range ports { var remark string if len(ports) > 1 { remark = v.Remark + "_" + strconv.Itoa(vv) } else { remark = v.Remark } if common.InStrArr(arr, remark) { log.Println(remark, "ok") } else { log.Println(remark, "not running") } } } } os.Exit(0) } var errAdd = errors.New("The server returned an error, which port or host may have been occupied or not allowed to open.") func StartFromFile(path string) { first := true cnf, err := config.NewConfig(path) if err != nil || cnf.CommonConfig == nil { logs.Error("Config file %s loading error %s", path, err.Error()) os.Exit(0) } logs.Info("Loading configuration file %s successfully", path) re: if first || cnf.CommonConfig.AutoReconnection { if !first { logs.Info("Reconnecting...") time.Sleep(time.Second * 5) } } else { return } first = false c, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl) if err != nil { logs.Error(err) goto re } var isPub bool binary.Read(c, binary.LittleEndian, &isPub) // get tmp password var b []byte vkey := cnf.CommonConfig.VKey if isPub { // send global configuration to server and get status of config setting if _, err := c.SendInfo(cnf.CommonConfig.Client, common.NEW_CONF); err != nil { logs.Error(err) goto re } if !c.GetAddStatus() { logs.Error("the web_user may have been occupied!") goto re } if b, err = c.GetShortContent(16); err != nil { logs.Error(err) goto re } vkey = string(b) } ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(vkey), 0600) //send hosts to server for _, v := range cnf.Hosts { if _, err := c.SendInfo(v, common.NEW_HOST); err != nil { logs.Error(err) goto re } if !c.GetAddStatus() { logs.Error(errAdd, v.Host) goto re } } //send task to server for _, v := range cnf.Tasks { if _, err := c.SendInfo(v, common.NEW_TASK); err != nil { logs.Error(err) goto re } if !c.GetAddStatus() { logs.Error(errAdd, v.Ports, v.Remark) goto re } if v.Mode == "file" { //start local file server go startLocalFileServer(cnf.CommonConfig, v, vkey) } } //create local server secret or p2p for _, v := range cnf.LocalServer { go StartLocalServer(v, cnf.CommonConfig) } c.Close() if cnf.CommonConfig.Client.WebUserName == "" || cnf.CommonConfig.Client.WebPassword == "" { logs.Notice("web access login username:user password:%s", vkey) } else { logs.Notice("web access login username:%s password:%s", cnf.CommonConfig.Client.WebUserName, cnf.CommonConfig.Client.WebPassword) } NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl, cnf, cnf.CommonConfig.DisconnectTime).Start() CloseLocalServer() goto re } // Create a new connection with the server and verify it func NewConn(tp string, vkey string, server string, connType string, proxyUrl string) (*conn.Conn, error) { var err error var connection net.Conn var sess *kcp.UDPSession if tp == "tcp" { if proxyUrl != "" { u, er := url.Parse(proxyUrl) if er != nil { return nil, er } switch u.Scheme { case "socks5": n, er := proxy.FromURL(u, nil) if er != nil { return nil, er } connection, err = n.Dial("tcp", server) default: connection, err = NewHttpProxyConn(u, server) } } else { connection, err = net.Dial("tcp", server) } } else { sess, err = kcp.DialWithOptions(server, nil, 10, 3) if err == nil { conn.SetUdpSession(sess) connection = sess } } if err != nil { return nil, err } connection.SetDeadline(time.Now().Add(time.Second * 10)) defer connection.SetDeadline(time.Time{}) c := conn.NewConn(connection) if _, err := c.Write([]byte(common.CONN_TEST)); err != nil { return nil, err } if err := c.WriteLenContent([]byte(version.GetVersion())); err != nil { return nil, err } if err := c.WriteLenContent([]byte(version.VERSION)); err != nil { return nil, err } b, err := c.GetShortContent(32) if err != nil { logs.Error(err) return nil, err } if crypt.Md5(version.GetVersion()) != string(b) { logs.Error("The client does not match the server version. The current core version of the client is", version.GetVersion()) return nil, err } if _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil { return nil, err } if s, err := c.ReadFlag(); err != nil { return nil, err } else if s == common.VERIFY_EER { return nil, errors.New(fmt.Sprintf("Validation key %s incorrect", vkey)) } if _, err := c.Write([]byte(connType)); err != nil { return nil, err } c.SetAlive(tp) return c, nil } //http proxy connection func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) { req, err := http.NewRequest("CONNECT", "http://"+remoteAddr, nil) if err != nil { return nil, err } password, _ := url.User.Password() req.Header.Set("Authorization", "Basic "+basicAuth(strings.Trim(url.User.Username(), " "), password)) // we make a http proxy request proxyConn, err := net.Dial("tcp", url.Host) if err != nil { return nil, err } if err := req.Write(proxyConn); err != nil { return nil, err } res, err := http.ReadResponse(bufio.NewReader(proxyConn), req) if err != nil { return nil, err } _ = res.Body.Close() if res.StatusCode != 200 { return nil, errors.New("Proxy error " + res.Status) } return proxyConn, nil } //get a basic auth string func basicAuth(username, password string) string { auth := username + ":" + password return base64.StdEncoding.EncodeToString([]byte(auth)) } func getRemoteAddressFromServer(rAddr string, localConn *net.UDPConn, md5Password, role string, add int) error { rAddr, err := getNextAddr(rAddr, add) if err != nil { logs.Error(err) return err } addr, err := net.ResolveUDPAddr("udp", rAddr) if err != nil { return err } if _, err := localConn.WriteTo(common.GetWriteStr(md5Password, role), addr); err != nil { return err } return nil } func handleP2PUdp(localAddr, rAddr, md5Password, role string) (remoteAddress string, c net.PacketConn, err error) { localConn, err := newUdpConnByAddr(localAddr) if err != nil { return } err = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 0) if err != nil { logs.Error(err) return } err = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 1) if err != nil { logs.Error(err) return } err = getRemoteAddressFromServer(rAddr, localConn, md5Password, role, 2) if err != nil { logs.Error(err) return } var remoteAddr1, remoteAddr2, remoteAddr3 string for { buf := make([]byte, 1024) if n, addr, er := localConn.ReadFromUDP(buf); er != nil { err = er return } else { rAddr2, _ := getNextAddr(rAddr, 1) rAddr3, _ := getNextAddr(rAddr, 2) switch addr.String() { case rAddr: remoteAddr1 = string(buf[:n]) case rAddr2: remoteAddr2 = string(buf[:n]) case rAddr3: remoteAddr3 = string(buf[:n]) } } if remoteAddr1 != "" && remoteAddr2 != "" && remoteAddr3 != "" { break } } if remoteAddress, err = sendP2PTestMsg(localConn, remoteAddr1, remoteAddr2, remoteAddr3); err != nil { return } c, err = newUdpConnByAddr(localAddr) return } func sendP2PTestMsg(localConn *net.UDPConn, remoteAddr1, remoteAddr2, remoteAddr3 string) (string, error) { logs.Trace(remoteAddr3, remoteAddr2, remoteAddr1) defer localConn.Close() isClose := false defer func() { isClose = true }() interval, err := getAddrInterval(remoteAddr1, remoteAddr2, remoteAddr3) if err != nil { return "", err } go func() { addr, err := getNextAddr(remoteAddr3, interval) if err != nil { return } remoteUdpAddr, err := net.ResolveUDPAddr("udp", addr) if err != nil { return } logs.Trace("try send test packet to target %s", addr) ticker := time.NewTicker(time.Millisecond * 500) defer ticker.Stop() for { select { case <-ticker.C: if isClose { return } if _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil { return } } } }() if interval != 0 { ip := common.GetIpByAddr(remoteAddr2) go func() { ports := getRandomPortArr(common.GetPortByAddr(remoteAddr3), common.GetPortByAddr(remoteAddr3)+interval*50) for i := 0; i <= 50; i++ { go func(port int) { trueAddress := ip + ":" + strconv.Itoa(port) logs.Trace("try send test packet to target %s", trueAddress) remoteUdpAddr, err := net.ResolveUDPAddr("udp", trueAddress) if err != nil { return } ticker := time.NewTicker(time.Second * 2) defer ticker.Stop() for { select { case <-ticker.C: if isClose { return } if _, err := localConn.WriteTo([]byte(common.WORK_P2P_CONNECT), remoteUdpAddr); err != nil { return } } } }(ports[i]) time.Sleep(time.Millisecond * 10) } }() } buf := make([]byte, 10) for { localConn.SetReadDeadline(time.Now().Add(time.Second * 10)) n, addr, err := localConn.ReadFromUDP(buf) localConn.SetReadDeadline(time.Time{}) if err != nil { break } switch string(buf[:n]) { case common.WORK_P2P_SUCCESS: for i := 20; i > 0; i-- { if _, err = localConn.WriteTo([]byte(common.WORK_P2P_END), addr); err != nil { return "", err } } return addr.String(), nil case common.WORK_P2P_END: logs.Trace("Remotely Address %s Reply Packet Successfully Received", addr.String()) return addr.String(), nil case common.WORK_P2P_CONNECT: go func() { for i := 20; i > 0; i-- { logs.Trace("try send receive success packet to target %s", addr.String()) if _, err = localConn.WriteTo([]byte(common.WORK_P2P_SUCCESS), addr); err != nil { return } time.Sleep(time.Second) } }() default: continue } } return "", errors.New("connect to the target failed, maybe the nat type is not support p2p") } func newUdpConnByAddr(addr string) (*net.UDPConn, error) { udpAddr, err := net.ResolveUDPAddr("udp", addr) if err != nil { return nil, err } udpConn, err := net.ListenUDP("udp", udpAddr) if err != nil { return nil, err } return udpConn, nil } func getNextAddr(addr string, n int) (string, error) { arr := strings.Split(addr, ":") if len(arr) != 2 { return "", errors.New(fmt.Sprintf("the format of %s incorrect", addr)) } if p, err := strconv.Atoi(arr[1]); err != nil { return "", err } else { return arr[0] + ":" + strconv.Itoa(p+n), nil } } func getAddrInterval(addr1, addr2, addr3 string) (int, error) { arr1 := strings.Split(addr1, ":") if len(arr1) != 2 { return 0, errors.New(fmt.Sprintf("the format of %s incorrect", addr1)) } arr2 := strings.Split(addr2, ":") if len(arr2) != 2 { return 0, errors.New(fmt.Sprintf("the format of %s incorrect", addr2)) } arr3 := strings.Split(addr3, ":") if len(arr3) != 2 { return 0, errors.New(fmt.Sprintf("the format of %s incorrect", addr3)) } p1, err := strconv.Atoi(arr1[1]) if err != nil { return 0, err } p2, err := strconv.Atoi(arr2[1]) if err != nil { return 0, err } p3, err := strconv.Atoi(arr3[1]) if err != nil { return 0, err } interVal := int(math.Floor(math.Min(math.Abs(float64(p3-p2)), math.Abs(float64(p2-p1))))) if p3-p1 < 0 { return -interVal, nil } return interVal, nil } func getRandomPortArr(min, max int) []int { if min > max { min, max = max, min } addrAddr := make([]int, max-min+1) for i := min; i <= max; i++ { addrAddr[max-i] = i } rand.Seed(time.Now().UnixNano()) var r, temp int for i := max - min; i > 0; i-- { r = rand.Int() % i temp = addrAddr[i] addrAddr[i] = addrAddr[r] addrAddr[r] = temp } return addrAddr } ================================================ FILE: client/health.go ================================================ package client import ( "container/heap" "net" "net/http" "strings" "time" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/file" "ehang.io/nps/lib/sheap" "github.com/astaxie/beego/logs" "github.com/pkg/errors" ) var isStart bool var serverConn *conn.Conn func heathCheck(healths []*file.Health, c *conn.Conn) bool { serverConn = c if isStart { for _, v := range healths { v.HealthMap = make(map[string]int) } return true } isStart = true h := &sheap.IntHeap{} for _, v := range healths { if v.HealthMaxFail > 0 && v.HealthCheckTimeout > 0 && v.HealthCheckInterval > 0 { v.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second) heap.Push(h, v.HealthNextTime.Unix()) v.HealthMap = make(map[string]int) } } go session(healths, h) return true } func session(healths []*file.Health, h *sheap.IntHeap) { for { if h.Len() == 0 { logs.Error("health check error") break } rs := heap.Pop(h).(int64) - time.Now().Unix() if rs <= 0 { continue } timer := time.NewTimer(time.Duration(rs) * time.Second) select { case <-timer.C: for _, v := range healths { if v.HealthNextTime.Before(time.Now()) { v.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second) //check go check(v) //reset time heap.Push(h, v.HealthNextTime.Unix()) } } } } } // work when just one port and many target func check(t *file.Health) { arr := strings.Split(t.HealthCheckTarget, ",") var err error var rs *http.Response for _, v := range arr { if t.HealthCheckType == "tcp" { var c net.Conn c, err = net.DialTimeout("tcp", v, time.Duration(t.HealthCheckTimeout)*time.Second) if err == nil { c.Close() } } else { client := &http.Client{} client.Timeout = time.Duration(t.HealthCheckTimeout) * time.Second rs, err = client.Get("http://" + v + t.HttpHealthUrl) if err == nil && rs.StatusCode != 200 { err = errors.New("status code is not match") } } t.Lock() if err != nil { t.HealthMap[v] += 1 } else if t.HealthMap[v] >= t.HealthMaxFail { //send recovery add serverConn.SendHealthInfo(v, "1") t.HealthMap[v] = 0 } if t.HealthMap[v] > 0 && t.HealthMap[v]%t.HealthMaxFail == 0 { //send fail remove serverConn.SendHealthInfo(v, "0") } t.Unlock() } } ================================================ FILE: client/local.go ================================================ package client import ( "ehang.io/nps-mux" "errors" "net" "net/http" "runtime" "sync" "time" "ehang.io/nps/lib/common" "ehang.io/nps/lib/config" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/file" "ehang.io/nps/server/proxy" "github.com/astaxie/beego/logs" "github.com/xtaci/kcp-go" ) var ( LocalServer []*net.TCPListener udpConn net.Conn muxSession *nps_mux.Mux fileServer []*http.Server p2pNetBridge *p2pBridge lock sync.RWMutex udpConnStatus bool ) type p2pBridge struct { } func (p2pBridge *p2pBridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) { for i := 0; muxSession == nil; i++ { if i >= 20 { err = errors.New("p2pBridge:too many times to get muxSession") logs.Error(err) return } runtime.Gosched() // waiting for another goroutine establish the mux connection } nowConn, err := muxSession.NewConn() if err != nil { udpConn = nil return nil, err } if _, err := conn.NewConn(nowConn).SendInfo(link, ""); err != nil { udpConnStatus = false return nil, err } return nowConn, nil } func CloseLocalServer() { for _, v := range LocalServer { v.Close() } for _, v := range fileServer { v.Close() } } func startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey string) { remoteConn, err := NewConn(config.Tp, vkey, config.Server, common.WORK_FILE, config.ProxyUrl) if err != nil { logs.Error("Local connection server failed ", err.Error()) return } srv := &http.Server{ Handler: http.StripPrefix(t.StripPre, http.FileServer(http.Dir(t.LocalPath))), } logs.Info("start local file system, local path %s, strip prefix %s ,remote port %s ", t.LocalPath, t.StripPre, t.Ports) fileServer = append(fileServer, srv) listener := nps_mux.NewMux(remoteConn.Conn, common.CONN_TCP, config.DisconnectTime) logs.Error(srv.Serve(listener)) } func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error { if l.Type != "secret" { go handleUdpMonitor(config, l) } task := &file.Tunnel{ Port: l.Port, ServerIp: "0.0.0.0", Status: true, Client: &file.Client{ Cnf: &file.Config{ U: "", P: "", Compress: config.Client.Cnf.Compress, }, Status: true, RateLimit: 0, Flow: &file.Flow{}, }, Flow: &file.Flow{}, Target: &file.Target{}, } switch l.Type { case "p2ps": logs.Info("successful start-up of local socks5 monitoring, port", l.Port) return proxy.NewSock5ModeServer(p2pNetBridge, task).Start() case "p2pt": logs.Info("successful start-up of local tcp trans monitoring, port", l.Port) return proxy.NewTunnelModeServer(proxy.HandleTrans, p2pNetBridge, task).Start() case "p2p", "secret": listener, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), l.Port, ""}) if err != nil { logs.Error("local listener startup failed port %d, error %s", l.Port, err.Error()) return err } LocalServer = append(LocalServer, listener) logs.Info("successful start-up of local tcp monitoring, port", l.Port) conn.Accept(listener, func(c net.Conn) { logs.Trace("new %s connection", l.Type) if l.Type == "secret" { handleSecret(c, config, l) } else if l.Type == "p2p" { handleP2PVisitor(c, config, l) } }) } return nil } func handleUdpMonitor(config *config.CommonConfig, l *config.LocalServer) { ticker := time.NewTicker(time.Second * 1) defer ticker.Stop() for { select { case <-ticker.C: if !udpConnStatus { udpConn = nil tmpConn, err := common.GetLocalUdpAddr() if err != nil { logs.Error(err) return } for i := 0; i < 10; i++ { logs.Notice("try to connect to the server", i+1) newUdpConn(tmpConn.LocalAddr().String(), config, l) if udpConn != nil { udpConnStatus = true break } } } } } } func handleSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { remoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_SECRET, config.ProxyUrl) if err != nil { logs.Error("Local connection server failed ", err.Error()) return } if _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil { logs.Error("Local connection server failed ", err.Error()) return } conn.CopyWaitGroup(remoteConn.Conn, localTcpConn, false, false, nil, nil, false, nil) } func handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) { if udpConn == nil { logs.Notice("new conn, P2P can not penetrate successfully, traffic will be transferred through the server") handleSecret(localTcpConn, config, l) return } logs.Trace("start trying to connect with the server") //TODO just support compress now because there is not tls file in client packages link := conn.NewLink(common.CONN_TCP, l.Target, false, config.Client.Cnf.Compress, localTcpConn.LocalAddr().String(), false) if target, err := p2pNetBridge.SendLinkInfo(0, link, nil); err != nil { logs.Error(err) udpConnStatus = false return } else { conn.CopyWaitGroup(target, localTcpConn, false, config.Client.Cnf.Compress, nil, nil, false, nil) } } func newUdpConn(localAddr string, config *config.CommonConfig, l *config.LocalServer) { lock.Lock() defer lock.Unlock() remoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_P2P, config.ProxyUrl) if err != nil { logs.Error("Local connection server failed ", err.Error()) return } if _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil { logs.Error("Local connection server failed ", err.Error()) return } var rAddr []byte //读取服务端地址、密钥 继续做处理 if rAddr, err = remoteConn.GetShortLenContent(); err != nil { logs.Error(err) return } var localConn net.PacketConn var remoteAddress string if remoteAddress, localConn, err = handleP2PUdp(localAddr, string(rAddr), crypt.Md5(l.Password), common.WORK_P2P_VISITOR); err != nil { logs.Error(err) return } udpTunnel, err := kcp.NewConn(remoteAddress, nil, 150, 3, localConn) if err != nil || udpTunnel == nil { logs.Warn(err) return } logs.Trace("successful create a connection with server", remoteAddress) conn.SetUdpSession(udpTunnel) udpConn = udpTunnel muxSession = nps_mux.NewMux(udpConn, "kcp", config.DisconnectTime) p2pNetBridge = &p2pBridge{} } ================================================ FILE: client/register.go ================================================ package client import ( "encoding/binary" "log" "os" "ehang.io/nps/lib/common" ) func RegisterLocalIp(server string, vKey string, tp string, proxyUrl string, hour int) { c, err := NewConn(tp, vKey, server, common.WORK_REGISTER, proxyUrl) if err != nil { log.Fatalln(err) } if err := binary.Write(c, binary.LittleEndian, int32(hour)); err != nil { log.Fatalln(err) } log.Printf("Successful ip registration for local public network, the validity period is %d hours.", hour) os.Exit(0) } ================================================ FILE: conf/clients.json ================================================ ================================================ FILE: conf/hosts.json ================================================ ================================================ FILE: conf/multi_account.conf ================================================ # key -> user | value -> pwd npc=npc.pwd ================================================ FILE: conf/npc.conf ================================================ [common] server_addr=127.0.0.1:8024 conn_type=tcp vkey=123 auto_reconnection=true max_conn=1000 flow_limit=1000 rate_limit=1000 basic_username=11 basic_password=3 web_username=user web_password=1234 crypt=true compress=true #pprof_addr=0.0.0.0:9999 disconnect_timeout=60 [health_check_test1] health_check_timeout=1 health_check_max_failed=3 health_check_interval=1 health_http_url=/ health_check_type=http health_check_target=127.0.0.1:8083,127.0.0.1:8082 [health_check_test2] health_check_timeout=1 health_check_max_failed=3 health_check_interval=1 health_check_type=tcp health_check_target=127.0.0.1:8083,127.0.0.1:8082 [web] host=c.o.com target_addr=127.0.0.1:8083,127.0.0.1:8082 [tcp] mode=tcp target_addr=127.0.0.1:8080 server_port=10000 [socks5] mode=socks5 server_port=19009 multi_account=multi_account.conf [file] mode=file server_port=19008 local_path=/Users/liuhe/Downloads strip_pre=/web/ [http] mode=httpProxy server_port=19004 [udp] mode=udp server_port=12253 target_addr=114.114.114.114:53 [ssh_secret] mode=secret password=ssh2 target_addr=123.206.77.88:22 [ssh_p2p] mode=p2p password=ssh3 [secret_ssh] local_port=2001 password=ssh2 [p2p_ssh] local_port=2002 password=ssh3 target_addr=123.206.77.88:22 ================================================ FILE: conf/nps.conf ================================================ appname = nps #Boot mode(dev|pro) runmode = dev #HTTP(S) proxy port, no startup if empty http_proxy_ip=0.0.0.0 http_proxy_port=80 https_proxy_port=443 https_just_proxy=true #default https certificate setting https_default_cert_file=conf/server.pem https_default_key_file=conf/server.key ##bridge bridge_type=tcp bridge_port=8024 bridge_ip=0.0.0.0 # Public password, which clients can use to connect to the server # After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file. public_vkey=123 #Traffic data persistence interval(minute) #Ignorance means no persistence #flow_store_interval=1 # log level LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7 log_level=7 #log_path=nps.log #Whether to restrict IP access, true or false or ignore #ip_limit=true #p2p #p2p_ip=127.0.0.1 #p2p_port=6000 #web web_host=a.o.com web_username=admin web_password=123 web_port = 8080 web_ip=0.0.0.0 web_base_url= web_open_ssl=false web_cert_file=conf/server.pem web_key_file=conf/server.key # if web under proxy use sub path. like http://host/nps need this. #web_base_url=/nps #Web API unauthenticated IP address(the len of auth_crypt_key must be 16) #Remove comments if needed #auth_key=test auth_crypt_key =1234567812345678 #allow_ports=9001-9009,10001,11000-12000 #Web management multi-user login allow_user_login=false allow_user_register=false allow_user_change_username=false #extension allow_flow_limit=false allow_rate_limit=false allow_tunnel_num_limit=false allow_local_proxy=false allow_connection_num_limit=false allow_multi_ip=false system_info_display=false #cache http_cache=false http_cache_length=100 #get origin ip http_add_origin_header=false #pprof debug options #pprof_ip=0.0.0.0 #pprof_port=9999 #client disconnect timeout disconnect_timeout=60 ================================================ FILE: conf/server.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7 Lbt3q5Knz8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeK FZM0Gp/WhSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0 aAMqJEm88jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQ pRUWqZeJY4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAe yAHsAwmaP8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABAoIBAD40x/RKoEKIyE8B D6g0pB1EQo+CePFoN3SYewO1uR4WgtVmtxWVoa7r5BpdZGLe3uCWhpMX7z7W6bGs f1LFQOckjkHIfMIfTGfecRjO5Yqu+Pbxtq+gUah+S/plJr3IzdC+SUVNvzBnBMeX eU3Vmg2UQ2nQ+9GWu8D/c/vDwxx0X8oQ2G8QaxX0tUurlSMNA3M7xySwEvhx54fO UrDF3Q4yF48eA4butxVLFWf3cnlY+nR8uYd2vKfmp689/8C6kkfoM9igB78e93sm uDM2eRLm4kU5WLl301T42n6AF7w8J0MhLLVOIeLs4l5gZPa3uKvYFmuHQao7e/5R U/jHKrECgYEA8alPXuxFSVOvdhIsSN//Frj9CdExVdYmaLkt/2LO4FMnOaWh1xh7 5iCY1bJT8D9dhfbqRg3qW2oguZD8gu04R8fTRegQ89qmAIwsEYqVf9salR41lZU4 Rc+5yc7O11WIe9Lzu+ONFBFkAh3UFMR4zVZ/JhKIG/P5Srm7SUdKW2cCgYEA5aHo x2LR+yKhjkrBzHG3Qrfy1PtlYHjOpYYAKHQcBFuiG08W3CK/vkYl+mhv0uyhT7mn q6NDqrpZPRnDlOoEqgRS1X/QWKN6Pgd4HNLIawvp0vK9jYXDPcAXFzVthXCIwFcn 3a3m4cHiuLdRNOHkydiHQyTOF6eEneN07TDvwvkCgYEApzOd1u9igPmFzQuF2GYi +HXFnaU/nUQuDwcQ7EJRIKRn31raPxiRoQesty5LJU6yRp4wOYgnPliPi9Tk4TGA XynC4/tMv2vorzhMxVY9Wdke602bhYNZC/RNd3O/aP2lEQdD3Bv04I2nxE8fDb9i VbAjCRSJV83WDf2zt1+78sECgYEAzezjRiKdcZu9y0/I+WEk2cUCE/MaF2he0FsZ uy1cjp/qAJltQ5452xUnK6cKWNlxU4CHF0mC/hC8xCldliZCZoEYE3PaUBLSJdwm 35o6tpxpZI3gZJCG5NJlIp/8BkVDrVC7ZHV17hAkFEf4n/bPaB8wNYtE8jt8luaK TcarzGkCgYBn2alN0RLN2PHDurraFZB6GuCvh/arEjSCY3SDFQPF10CVjTDV7sx3 eqJkwJ81syTmfJwZIceWbOFGgsuSx37UrQAVlHZSvzeqEg9dA5HqSoOACyidJI7j RG2+HB+KpsIZjGgLrEM4i7VOpYUDRdaouIXngFq/t9HNT+MDck5/Lw== -----END RSA PRIVATE KEY----- ================================================ FILE: conf/server.pem ================================================ -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIJAPXRSiP0Fs7sMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTcxMTA3MDg1MzQ2WhcNMjcxMTA1MDg1MzQ2WjBF MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7Lbt3q5Kn z8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeKFZM0Gp/W hSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0aAMqJEm8 8jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQpRUWqZeJ Y4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAeyAHsAwma P8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABo4GnMIGkMB0GA1UdDgQWBBQdPc0R a8alY6Ab7voidkTGaH4PxzB1BgNVHSMEbjBsgBQdPc0Ra8alY6Ab7voidkTGaH4P x6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPXRSiP0Fs7sMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAH1IZNkjuvt2nZPzXsuiVNyCE1vm346z naE0Uzt3aseAN9m/iiB8mLz+ryvWc2aFMX5lTdsHdm2rqmqBCBXeRwTLf4OeHIju ZQW6makWt6PxANEo6gbdPbQXbS420ssUhnR2irIH1SdI31iikVFPdiS0baRRE/gS +440M1jOOOnKm0Qin92ejsshmji/0qaD2+6D5TNw4HmIZaFTBw+kfjxCL6trfeBn 4fT0RJ121V3G3+AtG5sWQ93B3pCg+jtD+fGKkNSLhphq84bD1Zv7l73QGOoylkEn Sc0ajTLOXFBb83yRdlgV3Da95jH9rDZ4jSod48m+KemoZTDQw0vSwAU= -----END CERTIFICATE----- ================================================ FILE: conf/tasks.json ================================================ ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/README.md ================================================ # nps ![](https://img.shields.io/github/stars/cnlh/nps.svg) ![](https://img.shields.io/github/forks/cnlh/nps.svg) [![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) [![Build Status](https://travis-ci.org/ehang-io/nps.svg?branch=master)](https://travis-ci.org/cnlh/nps) nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**、**p2p等**,并带有功能强大的web管理端。 ## 背景 ![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true) 1. 做微信公众号开发、小程序开发等----> 域名代理模式 2. 想在外网通过ssh连接内网的机器,做云服务器到内网服务器端口的映射,----> tcp代理模式 3. 在非内网环境下使用内网dns,或者需要通过udp访问内网机器等----> udp代理模式 4. 在外网使用HTTP代理访问内网站点----> http代理模式 5. 搭建一个内网穿透ss,在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式 ================================================ FILE: docs/_coverpage.md ================================================ ![logo](logo.svg) # NPS 0.26.10 > 一款轻量级、高性能、功能强大的内网穿透代理服务器 - 几乎支持所有协议 - 支持内网http代理、内网socks5代理、p2p等 - 简洁但功能强大的WEB管理界面 - 支持服务端、客户端同时控制 - 扩展功能强大 - 全平台兼容,一键注册为服务 [GitHub](https://github.com/ehang-io/nps/) [开始使用](#nps) ================================================ FILE: docs/_navbar.md ================================================ * [![GitHub stars](https://img.shields.io/github/stars/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/stargazers) * [![GitHub forks](https://img.shields.io/github/forks/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/network) ================================================ FILE: docs/_sidebar.md ================================================ * 入门 * [安装](install.md) * [启动](run.md) * [使用示例](example.md) * 服务端 * [介绍](introduction.md) * [使用](nps_use.md) * [配置文件](server_config.md) * [增强功能](nps_extend.md) * 客户端 * [基本使用](use.md) * [增强功能](npc_extend.md) * 扩展 * [功能](feature.md) * [说明](description.md) * [web api](api.md) * [sdk](npc_sdk.md) * 其他 * [FAQ](faq.md) * [贡献](contribute.md) * [捐助](donate.md) * [致谢](thanks.md) * [交流](discuss.md) ================================================ FILE: docs/api.md ================================================ # web api 需要开启请先去掉`nps.conf`中`auth_key`的注释并配置一个合适的密钥 ## webAPI验证说明 - 采用auth_key的验证方式 - 在提交的每个请求后面附带两个参数,`auth_key` 和`timestamp` ``` auth_key的生成方式为:md5(配置文件中的auth_key+当前时间戳) ``` ``` timestamp为当前时间戳 ``` ``` curl --request POST \ --url http://127.0.0.1:8080/client/list \ --data 'auth_key=2a0000d9229e7dbcf79dd0f5e04bb084×tamp=1553045344&start=0&limit=10' ``` **注意:** 为保证安全,时间戳的有效范围为20秒内,所以每次提交请求必须重新生成。 ## 获取服务端时间 由于服务端与api请求的客户端时间差异不能太大,所以提供了一个可以获取服务端时间的接口 ``` POST /auth/gettime ``` ## 获取服务端authKey 如果想获取authKey,服务端提供获取authKey的接口 ``` POST /auth/getauthkey ``` 将返回加密后的authKey,采用aes cbc加密,请使用与服务端配置文件中cryptKey相同的密钥进行解密 **注意:** nps配置文件中`auth_crypt_key`需为16位 - 解密密钥长度128 - 偏移量与密钥相同 - 补码方式pkcs5padding - 解密串编码方式 十六进制 ## 详细文档 - **[详见](webapi.md)** (感谢@avengexyz) ================================================ FILE: docs/contribute.md ================================================ # 贡献 - 如果遇到bug可以直接提交至dev分支 - 使用遇到问题可以通过issues反馈 - 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支 - 如果有新的功能特性反馈,可以通过issues或者qq群反馈 ================================================ FILE: docs/description.md ================================================ # 说明 ## 获取用户真实ip 如需使用需要在`nps.conf`中设置`http_add_origin_header=true` 在域名代理模式中,可以通过request请求 header 中的 X-Forwarded-For 和 X-Real-IP 来获取用户真实 IP。 **本代理前会在每一个http(s)请求中添加了这两个 header。** ## 热更新支持 对于绝大多数配置,在web管理中的修改将实时使用,无需重启客户端或者服务端 ## 客户端地址显示 在web管理中将显示客户端的连接地址 ## 流量统计 可统计显示每个代理使用的流量,由于压缩和加密等原因,会和实际环境中的略有差异 ## 当前客户端带宽 可统计每个客户端当前的带宽,可能和实际有一定差异,仅供参考。 ## 客户端与服务端版本对比 为了程序正常运行,客户端与服务端的核心版本必须一致,否则将导致客户端无法成功连接致服务端。 ## Linux系统限制 默认情况下linux对连接数量有限制,对于性能好的机器完全可以调整内核参数以处理更多的连接。 `tcp_max_syn_backlog` `somaxconn` 酌情调整参数,增强网络性能 ## web管理保护 当一个ip连续登陆失败次数超过10次,将在一分钟内禁止该ip再次尝试。 ================================================ FILE: docs/discuss.md ================================================ # 交流群 ![二维码.jpeg](https://i.loli.net/2019/02/15/5c66c32a42074.jpeg) ================================================ FILE: docs/donate.md ================================================ # 捐助 如果您觉得nps对你有帮助,欢迎给予我们一定捐助,也是帮助nps更好的发展。 ## 支付宝 ![image](https://github.com/ehang-io/nps/blob/master/image/donation_zfb.png?raw=true) ## 微信 ![image](https://github.com/ehang-io/nps/blob/master/image/donation_wx.png?raw=true) ================================================ FILE: docs/example.md ================================================ # 使用示例 ## 统一准备工作(必做) - 开启服务端,假设公网服务器ip为1.1.1.1,配置文件中`bridge_port`为8024,配置文件中`web_port`为8080 - 访问1.1.1.1:8080 - 在客户端管理中创建一个客户端,记录下验证密钥 - 内网客户端运行(windows使用cmd运行加.exe) ```shell ./npc -server=1.1.1.1:8024 -vkey=客户端的密钥 ``` **注意:运行服务端后,请确保能从客户端设备上正常访问配置文件中所配置的`bridge_port`端口,telnet,netcat这类的来检查** ## 域名解析 **适用范围:** 小程序开发、微信公众号开发、产品演示 **注意:域名解析模式为http反向代理,不是dns服务器,在web上能够轻松灵活配置** **假设场景:** - 有一个域名proxy.com,有一台公网机器ip为1.1.1.1 - 两个内网开发站点127.0.0.1:81,127.0.0.1:82 - 想通过(http|https://)a.proxy.com访问127.0.0.1:81,通过(http|https://)b.proxy.com访问127.0.0.1:82 **使用步骤** - 将*.proxy.com解析到公网服务器1.1.1.1 - 点击刚才创建的客户端的域名管理,添加两条规则规则:1、域名:`a.proxy.com`,内网目标:`127.0.0.1:81`,2、域名:`b.proxy.com`,内网目标:`127.0.0.1:82` 现在访问(http|https://)`a.proxy.com`,`b.proxy.com`即可成功 **https:** 如需使用https请进行相关配置,详见 [使用https](/nps_extend?id=使用https) ## tcp隧道 **适用范围:** ssh、远程桌面等tcp连接场景 **假设场景:** 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接 **使用步骤** - 在刚才创建的客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),保存。 - 访问公网服务器ip(1.1.1.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@1.1.1.1` ## udp隧道 **适用范围:** 内网dns解析等udp连接场景 **假设场景:** 内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1 **使用步骤** - 在刚才创建的客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),保存。 - 修改需要使用的dns地址为1.1.1.1,则相当于使用10.1.50.102作为dns服务器 ## socks5代理 **适用范围:** 在外网环境下如同使用vpn一样访问内网设备或者资源 **假设场景:** 想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果 **使用步骤** - 在刚才创建的客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),保存。 - 在外网环境的本机配置socks5代理(例如使用proxifier进行全局代理),ip为公网服务器ip(1.1.1.1),端口为填写的监听端口(8003),即可畅享内网了 **注意** 经过socks5代理,当收到socks5数据包时socket已经是accept状态。表现是扫描端口全open,建立连接后短时间关闭。若想同内网表现一致,建议远程连接一台设备。 ## http正向代理 **适用范围:** 在外网环境下使用http正向代理访问内网站点 **假设场景:** 想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站 **使用步骤** - 在刚才创建的客户端隧道管理中添加一条http代理,填写监听的端口(8004),保存。 - 在外网环境的本机配置http代理,ip为公网服务器ip(1.1.1.1),端口为填写的监听端口(8004),即可访问了 **注意:对于私密代理与p2p,除了统一配置的客户端和服务端,还需要一个客户端作为访问端提供一个端口来访问** ## 私密代理 **适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的tcp服务,例如ssh。 **假设场景:** 无需新增多的端口实现访问内网服务器10.1.50.2的22端口 **使用步骤** - 在刚才创建的客户端中添加一条私密代理,并设置唯一密钥secrettest和内网目标10.1.50.2:22 - 在需要连接ssh的机器上以执行命令 ``` ./npc -server=1.1.1.1:8024 -vkey=vkey -type=tcp -password=secrettest -local_type=secret ``` 如需指定本地端口可加参数`-local_port=xx`,默认为2000 **注意:** password为web管理上添加的唯一密钥,具体命令可查看web管理上的命令提示 假设10.1.50.2用户名为root,现在执行`ssh -p 2000 root@127.0.0.1`即可访问ssh ## p2p服务 **适用范围:** 大流量传输场景,流量不经过公网服务器,但是由于p2p穿透和nat类型关系较大,不保证100%成功,支持大部分nat类型。[nat类型检测](/npc_extend?id=nat类型检测) **假设场景:** 想通过访问使用端机器(访问端,也就是本机)的2000端口---->访问到内网机器 10.2.50.2的22端口 **使用步骤** - 在`nps.conf`中设置`p2p_ip`(nps服务器ip)和`p2p_port`(nps服务器udp端口) > 注:若 `p2p_port` 设置为6000,请在防火墙开放6000~6002(额外添加2个端口)udp端口 - 在刚才刚才创建的客户端中添加一条p2p代理,并设置唯一密钥p2pssh - 在使用端机器(本机)执行命令 ``` ./npc -server=1.1.1.1:8024 -vkey=123 -password=p2pssh -target=10.2.50.2:22 ``` 如需指定本地端口可加参数`-local_port=xx`,默认为2000 **注意:** password为web管理上添加的唯一密钥,具体命令可查看web管理上的命令提示 假设内网机器为10.2.50.2的ssh用户名为root,现在在本机上执行`ssh -p 2000 root@127.0.0.1`即可访问机器2的ssh,如果是网站在浏览器访问127.0.0.1:2000端口即可。 ================================================ FILE: docs/faq.md ================================================ # FAQ - 服务端无法启动 ``` 服务端默认配置启用了8024,8080,80,443端口,端口冲突无法启动,请修改配置 ``` - 客户端无法连接服务端 ``` 请检查配置文件中的所有端口是否在安全组,防火墙放行 请检查vkey是否对应 请检查版本是否对应 ``` - 服务端配置文件修改无效 ``` install 之后,Linux 配置文件在 /etc/nps ``` - p2p穿透失败 [p2p服务](https://ehang-io.github.io/nps/#/example?id=p2p%e6%9c%8d%e5%8a%a1) ``` 双方nat类型都是Symmetric Nat一定不成功,建议先查看nat类型。请按照文档操作(标题上有超链接) ``` ================================================ FILE: docs/feature.md ================================================ # 扩展功能 ## 缓存支持 对于web站点来说,一些静态文件往往消耗更大的流量,且在内网穿透中,静态文件还需到客户端获取一次,这将导致更大的流量消耗。nps在域名解析代理中支持对静态文件进行缓存。 即假设一个站点有a.css,nps将只需从npc客户端读取一次该文件,然后把该文件的内容放在内存中,下一次将不再对npc客户端进行请求而直接返回内存中的对应内容。该功能默认是关闭的,如需开启请在`nps.conf`中设置`http_cache=true`,并设置`http_cache_length`(缓存文件的个数,消耗内存,不宜过大,0表示不限制个数) ## 数据压缩支持 由于是内网穿透,内网客户端与服务端之间的隧道存在大量的数据交换,为节省流量,加快传输速度,由此本程序支持SNNAPY形式的压缩。 - 所有模式均支持数据压缩 - 在web管理或客户端配置文件中设置 ## 加密传输 如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了ssh协议等,通过设置 配置文件,将服务端与客户端之间的通信内容加密传输,将会有效防止流量被拦截。 - nps现在默认每次启动时随机生成tls证书,用于加密传输 ## 站点保护 域名代理模式所有客户端共用一个http服务端口,在知道域名后任何人都可访问,一些开发或者测试环境需要保密,所以可以设置用户名和密码,nps将通过 Http Basic Auth 来保护,访问时需要输入正确的用户名和密码。 - 在web管理或客户端配置文件中设置 ## host修改 由于内网站点需要的host可能与公网域名不一致,域名代理支持host修改功能,即修改request的header中的host字段。 **使用方法:在web管理中设置** ## 自定义header 支持对header进行新增或者修改,以配合服务的需要 ## 404页面配置 支持域名解析模式的自定义404页面,修改/web/static/page/error.html中内容即可,暂不支持静态文件等内容 ## 流量限制 支持客户端级流量限制,当该客户端入口流量与出口流量达到设定的总量后会拒绝服务 ,域名代理会返回404页面,其他代理会拒绝连接,使用该功能需要在`nps.conf`中设置`allow_flow_limit`,默认是关闭的。 ## 带宽限制 支持客户端级带宽限制,带宽计算方式为入口和出口总和,权重均衡,使用该功能需要在`nps.conf`中设置`allow_rate_limit`,默认是关闭的。 ## 负载均衡 本代理支持域名解析模式和tcp代理的负载均衡,在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡 ## 端口白名单 为了防止服务端上的端口被滥用,可在nps.conf中配置allow_ports限制可开启的端口,忽略或者不填表示端口不受限制,格式: ```ini allow_ports=9001-9009,10001,11000-12000 ``` ## 端口范围映射 当客户端以配置文件的方式启动时,可以将本地的端口进行范围映射,仅支持tcp和udp模式,例如: ```ini [tcp] mode=tcp server_port=9001-9009,10001,11000-12000 target_port=8001-8009,10002,13000-14000 ``` 逗号分隔,可单个或者范围,注意上下端口的对应关系,无法一一对应将不能成功 ## 端口范围映射到其他机器 ```ini [tcp] mode=tcp server_port=9001-9009,10001,11000-12000 target_port=8001-8009,10002,13000-14000 target_ip=10.1.50.2 ``` 填写target_ip后则表示映射的该地址机器的端口,忽略则便是映射本地127.0.0.1,仅范围映射时有效 ## KCP协议支持 在网络质量非常好的情况下,例如专线,内网,可以开启略微降低延迟。如需使用可在nps.conf中修改`bridge_type`为kcp ,设置后本代理将开启udp端口(`bridge_port`) 注意:当服务端为kcp时,客户端连接时也需要使用相同配置,无配置文件模式加上参数type=kcp,配置文件模式在配置文件中设置tp=kcp ## 域名泛解析 支持域名泛解析,例如将host设置为*.proxy.com,a.proxy.com、b.proxy.com等都将解析到同一目标,在web管理中或客户端配置文件中将host设置为此格式即可。 ## URL路由 本代理支持根据URL将同一域名转发到不同的内网服务器,可在web中或客户端配置文件中设置,此参数也可忽略,例如在客户端配置文件中 ```ini [web1] host=a.proxy.com target_addr=127.0.0.1:7001 location=/test [web2] host=a.proxy.com target_addr=127.0.0.1:7002 location=/static ``` 对于`a.proxy.com/test`将转发到`web1`,对于`a.proxy.com/static`将转发到`web2` ## 限制ip访问 如果将一些危险性高的端口例如ssh端口暴露在公网上,可能会带来一些风险,本代理支持限制ip访问。 **使用方法:** 在配置文件nps.conf中设置`ip_limit`=true,设置后仅通过注册的ip方可访问。 **ip注册**: **方式一:** 在需要访问的机器上,运行客户端 ``` ./npc register -server=ip:port -vkey=公钥或客户端密钥 time=2 ``` time为有效小时数,例如time=2,在当前时间后的两小时内,本机公网ip都可以访问nps代理. **方式二:** 此外nps的web登陆也可提供验证的功能,成功登陆nps web admin后将自动为登陆的ip注册两小时的允许访问权限。 **注意:** 本机公网ip并不是一成不变的,请自行注意有效期的设置,同时同一网络下,多人也可能是在公用同一个公网ip。 ## 客户端最大连接数 为防止恶意大量长连接,影响服务端程序的稳定性,可以在web或客户端配置文件中为每个客户端设置最大连接数。该功能针对`socks5`、`http正向代理`、`域名代理`、`tcp代理`、`udp代理`、`私密代理`生效,使用该功能需要在`nps.conf`中设置`allow_connection_num_limit=true`,默认是关闭的。 ## 客户端最大隧道数限制 nps支持对客户端的隧道数量进行限制,该功能默认是关闭的,如需开启,请在`nps.conf`中设置`allow_tunnel_num_limit=true`。 ## 端口复用 在一些严格的网络环境中,对端口的个数等限制较大,nps支持强大端口复用功能。将`bridge_port`、 `http_proxy_port`、 `https_proxy_port` 、`web_port`都设置为同一端口,也能正常使用。 - 使用时将需要复用的端口设置为与`bridge_port`一致即可,将自动识别。 - 如需将web管理的端口也复用,需要配置`web_host`也就是一个二级域名以便区分 ## 多路复用 nps主要通信默认基于多路复用,无需开启。 多路复用基于TCP滑动窗口原理设计,动态计算延迟以及带宽来算出应该往网络管道中打入的流量。 由于主要通信大多采用TCP协议,并无法探测其实时丢包情况,对于产生丢包重传的情况,采用较大的宽容度, 5分钟的等待时间,超时将会关闭当前隧道连接并重新建立,这将会抛弃当前所有的连接。 在Linux上,可以通过调节内核参数来适应不同应用场景。 对于需求大带宽又有一定的丢包的场景,可以保持默认参数不变,尽可能少抛弃连接 高并发下可根据[Linux系统限制](## Linux系统限制) 调整 对于延迟敏感而又有一定丢包的场景,可以适当调整TCP重传次数 `tcp_syn_retries`, `tcp_retries1`, `tcp_retries2` 高并发同上 nps会在系统主动关闭连接的时候拿到报错,进而重新建立隧道连接 ## 环境变量渲染 npc支持环境变量渲染以适应在某些特殊场景下的要求。 **在无配置文件启动模式下:** 设置环境变量 ``` export NPC_SERVER_ADDR=1.1.1.1:8024 export NPC_SERVER_VKEY=xxxxx ``` 直接执行./npc即可运行 **在配置文件启动模式下:** ```ini [common] server_addr={{.NPC_SERVER_ADDR}} conn_type=tcp vkey={{.NPC_SERVER_VKEY}} auto_reconnection=true [web] host={{.NPC_WEB_HOST}} target_addr={{.NPC_WEB_TARGET}} ``` 在配置文件中填入相应的环境变量名称,npc将自动进行渲染配置文件替换环境变量 ## 健康检查 当客户端以配置文件模式启动时,支持多节点的健康检查。配置示例如下 ```ini [health_check_test1] health_check_timeout=1 health_check_max_failed=3 health_check_interval=1 health_http_url=/ health_check_type=http health_check_target=127.0.0.1:8083,127.0.0.1:8082 [health_check_test2] health_check_timeout=1 health_check_max_failed=3 health_check_interval=1 health_check_type=tcp health_check_target=127.0.0.1:8083,127.0.0.1:8082 ``` **health关键词必须在开头存在** 第一种是http模式,也就是以get的方式请求目标+url,返回状态码为200表示成功 第一种是tcp模式,也就是以tcp的方式与目标建立连接,能成功建立连接表示成功 如果失败次数超过`health_check_max_failed`,nps则会移除该npc下的所有该目标,如果失败后目标重新上线,nps将自动将目标重新加入。 项 | 含义 ---|--- health_check_timeout | 健康检查超时时间 health_check_max_failed | 健康检查允许失败次数 health_check_interval | 健康检查间隔 health_check_type | 健康检查类型 health_check_target | 健康检查目标,多个以逗号(,)分隔 health_check_type | 健康检查类型 health_http_url | 健康检查url,仅http模式适用 ## 日志输出 日志输出级别 **对于npc:** ``` -log_level=0~7 -log_path=npc.log ``` ``` LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7 ``` 默认为全输出,级别为0到7 **对于nps:** 在`nps.conf`中设置相关配置即可 ## pprof性能分析与调试 可在服务端与客户端配置中开启pprof端口,用于性能分析与调试,注释或留空相应参数为关闭。 默认为关闭状态 ## 自定义客户端超时检测断开时间 客户端与服务端间会间隔5s相互发送延迟测量包,这个时间间隔不可修改。 可修改延迟测量包丢包的次数,默认为60也就是5分钟都收不到一个延迟测量回包,则会断开客户端连接。 值得注意的是需要客户端的socket关闭,才会进行重连,也就是当客户端无法收到服务端的fin包时,只有客户端自行关闭socket才行。 也就是假如服务端设置为较低值,而客户端设置较高值,而此时服务端断开连接而客户端无法收到服务端的fin包,客户端也会继续等着直到触发客户端的超时设置。 在`nps.conf`或`npc.conf`中设置`disconnect_timeout`即可,客户端还可附带`-disconnect_timeout=60`参数启动 ================================================ FILE: docs/index.html ================================================ Document
================================================ FILE: docs/install.md ================================================ # 安装 ## 安装包安装 [releases](https://github.com/ehang-io/nps/releases) 下载对应的系统版本即可,服务端和客户端是单独的 ## 源码安装 - 安装源码 ```go get -u ehang.io/nps``` - 编译 服务端```go build cmd/nps/nps.go``` 客户端```go build cmd/npc/npc.go``` ## docker安装 > [server](https://hub.docker.com/r/ffdfgdfg/nps) > [client](https://hub.docker.com/r/ffdfgdfg/npc) ================================================ FILE: docs/introduction.md ================================================ ![image](https://github.com/ehang-io/nps/blob/master/image/web2.png?raw=true) # 介绍 可在网页上配置和管理各个tcp、udp隧道、内网站点代理,http、https解析等,功能强大,操作方便。 ================================================ FILE: docs/npc_extend.md ================================================ # 增强功能 ## nat类型检测 ``` ./npc nat -stun_addr=stun.stunprotocol.org:3478 ``` 如果p2p双方都是Symmetric Nat,肯定不能成功,其他组合都有较大成功率。`stun_addr`可以指定stun服务器地址。 ## 状态检查 ``` ./npc status -config=npc配置文件路径 ``` ## 重载配置文件 ``` ./npc restart -config=npc配置文件路径 ``` ## 通过代理连接nps 有时候运行npc的内网机器无法直接访问外网,此时可以可以通过socks5代理连接nps 对于配置文件方式启动,设置 ```ini [common] proxy_url=socks5://111:222@127.0.0.1:8024 ``` 对于无配置文件模式,加上参数 ``` -proxy=socks5://111:222@127.0.0.1:8024 ``` 支持socks5和http两种模式 即socks5://username:password@ip:port 或http://username:password@ip:port ## 群晖支持 可在releases中下载spk群晖套件,例如`npc_x64-6.1_0.19.0-1.spk` ================================================ FILE: docs/npc_sdk.md ================================================ # npc sdk文档 ``` 命令行模式启动客户端 从v0.26.10开始,此函数会阻塞,直到客户端退出返回,请自行管理是否重连 p0->连接地址 p1->vkey p2->连接类型(tcp or udp) p3->连接代理 extern GoInt StartClientByVerifyKey(char* p0, char* p1, char* p2, char* p3); 查看当前启动的客户端状态,在线为1,离线为0 extern GoInt GetClientStatus(); 关闭客户端 extern void CloseClient(); 获取当前客户端版本 extern char* Version(); 获取日志,实时更新 extern char* Logs(); ``` ================================================ FILE: docs/nps_extend.md ================================================ # 增强功能 ## 使用https **方式一:** 类似于nginx实现https的处理 在配置文件中将https_proxy_port设置为443或者其他你想配置的端口,将`https_just_proxy`设置为false,nps 重启后,在web管理界面,域名新增或修改界面中修改域名证书和密钥。 **此外:** 可以在`nps.conf`中设置一个默认的https配置,当遇到未在web中设置https证书的域名解析时,将自动使用默认证书,另还有一种情况就是对于某些请求的clienthello不携带sni扩展信息,nps也将自动使用默认证书 **方式二:** 在内网对应服务器上设置https 在`nps.conf`中将`https_just_proxy`设置为true,并且打开`https_proxy_port`端口,然后nps将直接转发https请求到内网服务器上,由内网服务器进行https处理 ## 与nginx配合 有时候我们还需要在云服务器上运行nginx来保证静态文件缓存等,本代理可和nginx配合使用,在配置文件中将httpProxyPort设置为非80端口,并在nginx中配置代理,例如httpProxyPort为8010时 ``` server { listen 80; server_name *.proxy.com; location / { proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:8010; } } ``` 如需使用https也可在nginx监听443端口并配置ssl,并将本代理的httpsProxyPort设置为空关闭https即可,例如httpProxyPort为8020时 ``` server { listen 443; server_name *.proxy.com; ssl on; ssl_certificate certificate.crt; ssl_certificate_key private.key; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; location / { proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:8020; } } ``` ## web管理使用https 如果web管理需要使用https,可以在配置文件`nps.conf`中设置`web_open_ssl=true`,并配置`web_cert_file`和`web_key_file` ## web使用Caddy代理 如果将web配置到Caddy代理,实现子路径访问nps,可以这样配置. 假设我们想通过 `http://caddy_ip:caddy_port/nps` 来访问后台, Caddyfile 这样配置: ```Caddyfile caddy_ip:caddy_port/nps { ##server_ip 为 nps 服务器IP ##web_port 为 nps 后台端口 proxy / http://server_ip:web_port/nps { transparent } } ``` nps.conf 修改 `web_base_url` 为 `/nps` 即可 ``` web_base_url=/nps ``` ## 关闭代理 如需关闭http代理可在配置文件中将http_proxy_port设置为空,如需关闭https代理可在配置文件中将https_proxy_port设置为空。 ## 流量数据持久化 服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flow_store_interval`参数,单位为分钟 **注意:** nps不会持久化通过公钥连接的客户端 ## 系统信息显示 nps服务端支持在web上显示和统计服务器的相关信息,但默认一些统计图表是关闭的,如需开启请在`nps.conf`中设置`system_info_display=true` ## 自定义客户端连接密钥 web上可以自定义客户端连接的密钥,但是必须具有唯一性 ## 关闭公钥访问 可以将`nps.conf`中的`public_vkey`设置为空或者删除 ## 关闭web管理 可以将`nps.conf`中的`web_port`设置为空或者删除 ## 服务端多用户登陆 如果将`nps.conf`中的`allow_user_login`设置为true,服务端web将支持多用户登陆,登陆用户名为user,默认密码为每个客户端的验证密钥,登陆后可以进入客户端编辑修改web登陆的用户名和密码,默认该功能是关闭的。 ## 用户注册功能 nps服务端支持用户注册功能,可将`nps.conf`中的`allow_user_register`设置为true,开启后登陆页将会有有注册功能, ## 监听指定ip nps支持每个隧道监听不同的服务端端口,在`nps.conf`中设置`allow_multi_ip=true`后,可在web中控制,或者npc配置文件中(可忽略,默认为0.0.0.0) ```ini server_ip=xxx ``` ## 代理到服务端本地 在使用nps监听80或者443端口时,默认是将所有的请求都会转发到内网上,但有时候我们的nps服务器的上一些服务也需要使用这两个端口,nps提供类似于`nginx` `proxy_pass` 的功能,支持将代理到服务器本地,该功能支持域名解析,tcp、udp隧道,默认关闭。 **即:** 假设在nps的vps服务器上有一个服务使用5000端口,这时候nps占用了80端口和443,我们想能使用一个域名通过http(s)访问到5000的服务。 **使用方式:** 在`nps.conf`中设置`allow_local_proxy=true`,然后在web上设置想转发的隧道或者域名然后选择转发到本地选项即可成功。 ================================================ FILE: docs/nps_use.md ================================================ # 使用 **提示:使用web模式时,服务端执行文件必须在项目根目录,否则无法正确加载配置文件** ## web管理 进入web界面,公网ip:web界面端口(默认8080),密码默认为123 进入web管理界面,有详细的说明 ## 服务端配置文件重载 对于linux、darwin ```shell sudo nps reload ``` 对于windows ```shell nps.exe reload ``` **说明:** 仅支持部分配置重载,例如`allow_user_login` `auth_crypt_key` `auth_key` `web_username` `web_password` 等,未来将支持更多 ## 服务端停止或重启 对于linux、darwin ```shell sudo nps stop|restart ``` 对于windows ```shell nps.exe stop|restart ``` ## 服务端更新 请首先执行 `sudo nps stop` 或者 `nps.exe stop` 停止运行,然后 对于linux ```shell sudo nps-update update ``` 对于windows ```shell nps-update.exe update ``` 更新完成后,执行执行 `sudo nps start` 或者 `nps.exe start` 重新运行即可完成升级 如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的nps二进制文件和web目录 注意:`nps install` 之后的 nps 不在原位置,请使用 `whereis nps` 查找具体目录覆盖 nps 二进制文件 ================================================ FILE: docs/run.md ================================================ # 启动 ## 服务端 下载完服务器压缩包后,解压,然后进入解压后的文件夹 - 执行安装命令 对于linux|darwin ```sudo ./nps install``` 对于windows,管理员身份运行cmd,进入安装目录 ```nps.exe install``` - 启动 对于linux|darwin ```sudo nps start``` 对于windows,管理员身份运行cmd,进入程序目录 ```nps.exe start``` ```安装后windows配置文件位于 C:\Program Files\nps,linux和darwin位于/etc/nps``` 停止和重启可用,stop和restart **如果发现没有启动成功,可以使用`nps(.exe) stop`,然后运行`nps.(exe)`运行调试,或查看日志**(Windows日志文件位于当前运行目录下,linux和darwin位于/var/log/nps.log) - 访问服务端ip:web服务端口(默认为8080) - 使用用户名和密码登陆(默认admin/123,正式使用一定要更改) - 创建客户端 ## 客户端 - 下载客户端安装包并解压,进入到解压目录 - 点击web管理中客户端前的+号,复制启动命令 - 执行启动命令,linux直接执行即可,windows将./npc换成npc.exe用**cmd执行** 如果使用`powershell`运行,**请将ip括起来!** 如果需要注册到系统服务可查看[注册到系统服务](/use?id=注册到系统服务) ## 版本检查 - 对客户端以及服务的均可以使用参数`-version`打印版本 - `nps -version`或`./nps -version` - `npc -version`或`./npc -version` ## 配置 - 客户端连接后,在web中配置对应穿透服务即可 - 可以查看[使用示例](/example) ================================================ FILE: docs/server_config.md ================================================ # 服务端配置文件 - /etc/nps/conf/nps.conf 名称 | 含义 ---|--- web_port | web管理端口 web_password | web界面管理密码 web_username | web界面管理账号 web_base_url | web管理主路径,用于将web管理置于代理子路径后面 bridge_port | 服务端客户端通信端口 https_proxy_port | 域名代理https代理监听端口 http_proxy_port | 域名代理http代理监听端口 auth_key|web api密钥 bridge_type|客户端与服务端连接方式kcp或tcp public_vkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式 ip_limit|是否限制ip访问,true或false或忽略 flow_store_interval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化 log_level|日志输出级别 auth_crypt_key | 获取服务端authKey时的aes加密密钥,16位 p2p_ip| 服务端Ip,使用p2p模式必填 p2p_port|p2p模式开启的udp端口 pprof_ip|debug pprof 服务端ip pprof_port|debug pprof 端口 disconnect_timeout|客户端连接超时,单位 5s,默认值 60,即 300s = 5mins ================================================ FILE: docs/thanks.md ================================================ Thanks [jetbrains](https://www.jetbrains.com/?from=nps) for providing development tools for nps ================================================ FILE: docs/use.md ================================================ # 基本使用 ## 无配置文件模式 此模式的各种配置在服务端web管理中完成,客户端除运行一条命令外无需任何其他设置 ``` ./npc -server=ip:port -vkey=web界面中显示的密钥 ``` ## 注册到系统服务(开机启动、守护进程) 对于linux、darwin - 注册:`sudo ./npc install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)` - 启动:`sudo npc start` - 停止:`sudo npc stop` - 如果需要更换命令内容需要先卸载`./npc uninstall`,再重新注册 对于windows,使用管理员身份运行cmd - 注册:`npc.exe install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)` - 启动:`npc.exe start` - 停止:`npc.exe stop` - 如果需要更换命令内容需要先卸载`npc.exe uninstall`,再重新注册 - 如果需要当客户端退出时自动重启客户端,请按照如图所示配置 ![image](https://github.com/ehang-io/nps/blob/master/docs/windows_client_service_configuration.png?raw=true) 注册到服务后,日志文件windows位于当前目录下,linux和darwin位于/var/log/npc.log ## 客户端更新 首先进入到对于的客户端二进制文件目录 请首先执行`sudo npc stop`或者`npc.exe stop`停止运行,然后 对于linux ```shell sudo npc-update update ``` 对于windows ```shell npc-update.exe update ``` 更新完成后,执行执行`sudo npc start`或者`npc.exe start`重新运行即可完成升级 如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的npc二进制文件 ## 配置文件模式 此模式使用nps的公钥或者客户端私钥验证,各种配置在客户端完成,同时服务端web也可以进行管理 ``` ./npc -config=npc配置文件路径 ``` ## 配置文件说明 [示例配置文件](https://github.com/ehang-io/nps/tree/master/conf/npc.conf) #### 全局配置 ```ini [common] server_addr=1.1.1.1:8024 conn_type=tcp vkey=123 username=111 password=222 compress=true crypt=true rate_limit=10000 flow_limit=100 remark=test max_conn=10 #pprof_addr=0.0.0.0:9999 ``` 项 | 含义 ---|--- server_addr | 服务端ip/域名:port conn_type | 与服务端通信模式(tcp或kcp) vkey|服务端配置文件中的密钥(非web) username|socks5或http(s)密码保护用户名(可忽略) password|socks5或http(s)密码保护密码(可忽略) compress|是否压缩传输(true或false或忽略) crypt|是否加密传输(true或false或忽略) rate_limit|速度限制,可忽略 flow_limit|流量限制,可忽略 remark|客户端备注,可忽略 max_conn|最大连接数,可忽略 pprof_addr|debug pprof ip:port #### 域名代理 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [web1] host=a.proxy.com target_addr=127.0.0.1:8080,127.0.0.1:8082 host_change=www.proxy.com header_set_proxy=nps ``` 项 | 含义 ---|--- web1 | 备注 host | 域名(http|https都可解析) target_addr|内网目标,负载均衡时多个目标,逗号隔开 host_change|请求host修改 header_xxx|请求header修改或添加,header_proxy表示添加header proxy:nps #### tcp隧道模式 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [tcp] mode=tcp target_addr=127.0.0.1:8080 server_port=9001 ``` 项 | 含义 ---|--- mode | tcp server_port | 在服务端的代理端口 tartget_addr|内网目标 #### udp隧道模式 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [udp] mode=udp target_addr=127.0.0.1:8080 server_port=9002 ``` 项 | 含义 ---|--- mode | udp server_port | 在服务端的代理端口 target_addr|内网目标 #### http代理模式 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [http] mode=httpProxy server_port=9003 ``` 项 | 含义 ---|--- mode | httpProxy server_port | 在服务端的代理端口 #### socks5代理模式 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [socks5] mode=socks5 server_port=9004 multi_account=multi_account.conf ``` 项 | 含义 ---|--- mode | socks5 server_port | 在服务端的代理端口 multi_account | socks5多账号配置文件(可选),配置后使用basic_username和basic_password无法通过认证 #### 私密代理模式 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [secret_ssh] mode=secret password=ssh2 target_addr=10.1.50.2:22 ``` 项 | 含义 ---|--- mode | secret password | 唯一密钥 target_addr|内网目标 #### p2p代理模式 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [p2p_ssh] mode=p2p password=ssh2 target_addr=10.1.50.2:22 ``` 项 | 含义 ---|--- mode | p2p password | 唯一密钥 target_addr|内网目标 #### 文件访问模式 利用nps提供一个公网可访问的本地文件服务,此模式仅客户端使用配置文件模式方可启动 ```ini [common] server_addr=1.1.1.1:8024 vkey=123 [file] mode=file server_port=9100 local_path=/tmp/ strip_pre=/web/ ```` 项 | 含义 ---|--- mode | file server_port | 服务端开启的端口 local_path|本地文件目录 strip_pre|前缀 对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录 #### 断线重连 ```ini [common] auto_reconnection=true ``` ================================================ FILE: docs/webapi.md ================================================ 获取客户端列表 ``` POST /client/list/ ``` | 参数 | 含义 | | --- | --- | | search | 搜索 | | order | 排序asc 正序 desc倒序 | | offset | 分页(第几页) | | limit | 条数(分页显示的条数) | *** 获取单个客户端 ``` POST /client/getclient/ ``` | 参数 | 含义 | | --- | --- | | id | 客户端id | *** 添加客户端 ``` POST /client/add/ ``` | 参数 | 含义 | | --- | --- | | remark | 备注 | | u | basic权限认证用户名 | | p | basic权限认证密码 | | limit | 条数(分页显示的条数) | | vkey | 客户端验证密钥 | | config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 | | compress | 压缩1允许 0不允许 | | crypt | 是否加密(1或者0)1允许 0不允许 | | rate\_limit | 带宽限制 单位KB/S 空则为不限制 | | flow\_limit | 流量限制 单位M 空则为不限制 | | max\_conn | 客户端最大连接数量 空则为不限制 | | max\_tunnel | 客户端最大隧道数量 空则为不限制 | *** 修改客户端 ``` POST /client/edit/ ``` | 参数 | 含义 | | --- | --- | | remark | 备注 | | u | basic权限认证用户名 | | p | basic权限认证密码 | | limit | 条数(分页显示的条数) | | vkey | 客户端验证密钥 | | config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 | | compress | 压缩1允许 0不允许 | | crypt | 是否加密(1或者0)1允许 0不允许 | | rate\_limit | 带宽限制 单位KB/S 空则为不限制 | | flow\_limit | 流量限制 单位M 空则为不限制 | | max\_conn | 客户端最大连接数量 空则为不限制 | | max\_tunnel | 客户端最大隧道数量 空则为不限制 | | id | 要修改的客户端id | *** 删除客户端 ``` POST /client/del/ ``` | 参数 | 含义 | | --- | --- | | id | 要删除的客户端id | *** 获取域名解析列表 ``` POST /index/hostlist/ ``` | 参数 | 含义 | | --- | --- | | search | 搜索(可以搜域名/备注什么的) | | offset | 分页(第几页) | | limit | 条数(分页显示的条数) | *** 添加域名解析 ``` POST /index/addhost/ ``` | 参数 | 含义 | | --- | --- | | remark | 备注 | | host | 域名 | | scheme | 协议类型(三种 all http https) | | location | url路由 空则为不限制 | | client\_id | 客户端id | | target | 内网目标(ip:端口) | | header | request header 请求头 | | hostchange | request host 请求主机 | *** 修改域名解析 ``` POST /index/edithost/ ``` | 参数 | 含义 | | --- | --- | | remark | 备注 | | host | 域名 | | scheme | 协议类型(三种 all http https) | | location | url路由 空则为不限制 | | client\_id | 客户端id | | target | 内网目标(ip:端口) | | header | request header 请求头 | | hostchange | request host 请求主机 | | id | 需要修改的域名解析id | *** 删除域名解析 ``` POST /index/delhost/ ``` | 参数 | 含义 | | --- | --- | | id | 需要删除的域名解析id | *** 获取单条隧道信息 ``` POST /index/getonetunnel/ ``` | 参数 | 含义 | | --- | --- | | id | 隧道的id | *** 获取隧道列表 ``` POST /index/gettunnel/ ``` | 参数 | 含义 | | --- | --- | | client\_id | 穿透隧道的客户端id | | type | 类型tcp udp httpProx socks5 secret p2p | | search | 搜索 | | offset | 分页(第几页) | | limit | 条数(分页显示的条数) | *** 添加隧道 ``` POST /index/add/ ``` | 参数 | 含义 | | --- | --- | | type | 类型tcp udp httpProx socks5 secret p2p | | remark | 备注 | | port | 服务端端口 | | target | 目标(ip:端口) | | client\_id | 客户端id | *** 修改隧道 ``` POST /index/edit/ ``` | 参数 | 含义 | | --- | --- | | type | 类型tcp udp httpProx socks5 secret p2p | | remark | 备注 | | port | 服务端端口 | | target | 目标(ip:端口) | | client\_id | 客户端id | | id | 隧道id | *** 删除隧道 ``` POST /index/del/ ``` | 参数 | 含义 | | --- | --- | | id | 隧道id | *** 隧道停止工作 ``` POST /index/stop/ ``` | 参数 | 含义 | | --- | --- | | id | 隧道id | *** 隧道开始工作 ``` POST /index/start/ ``` | 参数 | 含义 | | --- | --- | | id | 隧道id | ================================================ FILE: go.mod ================================================ module ehang.io/nps go 1.15 require ( ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992 fyne.io/fyne/v2 v2.0.2 github.com/astaxie/beego v1.12.0 github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d github.com/dsnet/compress v0.0.1 // indirect github.com/golang/snappy v0.0.3 github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect github.com/kardianos/service v1.2.0 github.com/klauspost/cpuid v1.3.1 // indirect github.com/klauspost/cpuid/v2 v2.0.6 // indirect github.com/klauspost/pgzip v1.2.1 // indirect github.com/klauspost/reedsolomon v1.9.12 // indirect github.com/panjf2000/ants/v2 v2.4.2 github.com/pkg/errors v0.9.1 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect github.com/shirou/gopsutil/v3 v3.21.3 github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect github.com/tjfoc/gmsm v1.4.0 // indirect github.com/xtaci/kcp-go v5.4.20+incompatible github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect ) replace github.com/astaxie/beego => github.com/exfly/beego v1.12.0-export-init ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992 h1:LvlcB+8JveSBprHnva0g+OyLwAH8CRxEwtWzTe6ocoE= ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992/go.mod h1:v54y/8ICChiM/aVUuKxGIcWwjm4HGNRyyAwbgLBoMbI= fyne.io/fyne/v2 v2.0.2 h1:6pDvFuCmL1odyT/fPI+2L54hMJW1Zt9Dno41HmLInRs= fyne.io/fyne/v2 v2.0.2/go.mod h1:3+FYmLJVgeb8EvTPJ5YzZeo7LkAq4bbuY3Zrir6xHbg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c h1:aprLqMn7gSPT+vdDSl+/E6NLEuArwD/J7IWd8bJt5lQ= github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c/go.mod h1:Ie6SubJv/NTO9Q0UBH0QCl3Ve50lu9hjbi5YJUw03TE= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io= github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exfly/beego v1.12.0-export-init h1:VQNYKdXhAwZGUaFmQv8Aj921O3rQJZRIF8xeGrhsjrI= github.com/exfly/beego v1.12.0-export-init/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI= github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 h1:WgfvpuKg42WVLkxNwzfFraXkTXPK36bMqXvMFN67clI= github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214/go.mod h1:kj6hFWqfwSjFjLnYW5PK1DoxZ4O0uapwHRmd9jhln4E= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.2 h1:pd2FBxFydtPn2ywTLStbFg9CJKrojATnpeJWSP7Ys4k= github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM= github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.9.12 h1:EyOucRmcrLH+2hqKGdoA5SM8pwPKR6BJsf3r6zpYOA0= github.com/klauspost/reedsolomon v1.9.12/go.mod h1:nLvuzNvy1ZDNQW30IuMc2ZWCbiqrJgdLoUS2X8HAUVg= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/panjf2000/ants/v2 v2.4.2 h1:kesjjo8JipN3vNNg1XaiXaeSs6xJweBTgenkBtsrHf8= github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/shirou/gopsutil/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8= github.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s= github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M= github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc= github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: lib/cache/lru.go ================================================ package cache import ( "container/list" "sync" ) // Cache is an LRU cache. It is safe for concurrent access. type Cache struct { // MaxEntries is the maximum number of cache entries before // an item is evicted. Zero means no limit. MaxEntries int //Execute this callback function when an element is culled OnEvicted func(key Key, value interface{}) ll *list.List //list cache sync.Map } // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators type Key interface{} type entry struct { key Key value interface{} } // New creates a new Cache. // If maxEntries is 0, the cache has no length limit. // that eviction is done by the caller. func New(maxEntries int) *Cache { return &Cache{ MaxEntries: maxEntries, ll: list.New(), //cache: make(map[interface{}]*list.Element), } } // If the key value already exists, move the key to the front func (c *Cache) Add(key Key, value interface{}) { if ee, ok := c.cache.Load(key); ok { c.ll.MoveToFront(ee.(*list.Element)) // move to the front ee.(*list.Element).Value.(*entry).value = value return } ele := c.ll.PushFront(&entry{key, value}) c.cache.Store(key, ele) if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { // Remove the oldest element if the limit is exceeded c.RemoveOldest() } } // Get looks up a key's value from the cache. func (c *Cache) Get(key Key) (value interface{}, ok bool) { if ele, hit := c.cache.Load(key); hit { c.ll.MoveToFront(ele.(*list.Element)) return ele.(*list.Element).Value.(*entry).value, true } return } // Remove removes the provided key from the cache. func (c *Cache) Remove(key Key) { if ele, hit := c.cache.Load(key); hit { c.removeElement(ele.(*list.Element)) } } // RemoveOldest removes the oldest item from the cache. func (c *Cache) RemoveOldest() { ele := c.ll.Back() if ele != nil { c.removeElement(ele) } } func (c *Cache) removeElement(e *list.Element) { c.ll.Remove(e) kv := e.Value.(*entry) c.cache.Delete(kv.key) if c.OnEvicted != nil { c.OnEvicted(kv.key, kv.value) } } // Len returns the number of items in the cache. func (c *Cache) Len() int { return c.ll.Len() } // Clear purges all stored items from the cache. func (c *Cache) Clear() { if c.OnEvicted != nil { c.cache.Range(func(key, value interface{}) bool { kv := value.(*list.Element).Value.(*entry) c.OnEvicted(kv.key, kv.value) return true }) } c.ll = nil } ================================================ FILE: lib/common/const.go ================================================ package common const ( CONN_DATA_SEQ = "*#*" //Separator VERIFY_EER = "vkey" VERIFY_SUCCESS = "sucs" WORK_MAIN = "main" WORK_CHAN = "chan" WORK_CONFIG = "conf" WORK_REGISTER = "rgst" WORK_SECRET = "sert" WORK_FILE = "file" WORK_P2P = "p2pm" WORK_P2P_VISITOR = "p2pv" WORK_P2P_PROVIDER = "p2pp" WORK_P2P_CONNECT = "p2pc" WORK_P2P_SUCCESS = "p2ps" WORK_P2P_END = "p2pe" WORK_P2P_LAST = "p2pl" WORK_STATUS = "stus" RES_MSG = "msg0" RES_CLOSE = "clse" NEW_UDP_CONN = "udpc" //p2p udp conn NEW_TASK = "task" NEW_CONF = "conf" NEW_HOST = "host" CONN_TCP = "tcp" CONN_UDP = "udp" CONN_TEST = "TST" UnauthorizedBytes = `HTTP/1.1 401 Unauthorized Content-Type: text/plain; charset=utf-8 WWW-Authenticate: Basic realm="easyProxy" 401 Unauthorized` ConnectionFailBytes = `HTTP/1.1 404 Not Found ` ) ================================================ FILE: lib/common/logs.go ================================================ package common import ( "github.com/astaxie/beego/logs" "time" ) const MaxMsgLen = 5000 var logMsgs string func init() { logs.Register("store", func() logs.Logger { return new(StoreMsg) }) } func GetLogMsg() string { return logMsgs } type StoreMsg struct { } func (lg *StoreMsg) Init(config string) error { return nil } func (lg *StoreMsg) WriteMsg(when time.Time, msg string, level int) error { m := when.Format("2006-01-02 15:04:05") + " " + msg + "\r\n" if len(logMsgs) > MaxMsgLen { start := MaxMsgLen - len(m) if start <= 0 { start = MaxMsgLen } logMsgs = logMsgs[start:] } logMsgs += m return nil } func (lg *StoreMsg) Destroy() { return } func (lg *StoreMsg) Flush() { return } ================================================ FILE: lib/common/netpackager.go ================================================ package common import ( "bytes" "encoding/binary" "errors" "io" "io/ioutil" "net" "strconv" ) type NetPackager interface { Pack(writer io.Writer) (err error) UnPack(reader io.Reader) (err error) } const ( ipV4 = 1 domainName = 3 ipV6 = 4 ) type UDPHeader struct { Rsv uint16 Frag uint8 Addr *Addr } func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader { return &UDPHeader{ Rsv: rsv, Frag: frag, Addr: addr, } } type Addr struct { Type uint8 Host string Port uint16 } func (addr *Addr) String() string { return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port))) } func (addr *Addr) Decode(b []byte) error { addr.Type = b[0] pos := 1 switch addr.Type { case ipV4: addr.Host = net.IP(b[pos : pos+net.IPv4len]).String() pos += net.IPv4len case ipV6: addr.Host = net.IP(b[pos : pos+net.IPv6len]).String() pos += net.IPv6len case domainName: addrlen := int(b[pos]) pos++ addr.Host = string(b[pos : pos+addrlen]) pos += addrlen default: return errors.New("decode error") } addr.Port = binary.BigEndian.Uint16(b[pos:]) return nil } func (addr *Addr) Encode(b []byte) (int, error) { b[0] = addr.Type pos := 1 switch addr.Type { case ipV4: ip4 := net.ParseIP(addr.Host).To4() if ip4 == nil { ip4 = net.IPv4zero.To4() } pos += copy(b[pos:], ip4) case domainName: b[pos] = byte(len(addr.Host)) pos++ pos += copy(b[pos:], []byte(addr.Host)) case ipV6: ip16 := net.ParseIP(addr.Host).To16() if ip16 == nil { ip16 = net.IPv6zero.To16() } pos += copy(b[pos:], ip16) default: b[0] = ipV4 copy(b[pos:pos+4], net.IPv4zero.To4()) pos += 4 } binary.BigEndian.PutUint16(b[pos:], addr.Port) pos += 2 return pos, nil } func (h *UDPHeader) Write(w io.Writer) error { b := BufPoolUdp.Get().([]byte) defer BufPoolUdp.Put(b) binary.BigEndian.PutUint16(b[:2], h.Rsv) b[2] = h.Frag addr := h.Addr if addr == nil { addr = &Addr{} } length, _ := addr.Encode(b[3:]) _, err := w.Write(b[:3+length]) return err } type UDPDatagram struct { Header *UDPHeader Data []byte } func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) { b := BufPoolUdp.Get().([]byte) defer BufPoolUdp.Put(b) // when r is a streaming (such as TCP connection), we may read more than the required data, // but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast // to make sure that no redundant data will be discarded. n, err := io.ReadFull(r, b[:5]) if err != nil { return nil, err } header := &UDPHeader{ Rsv: binary.BigEndian.Uint16(b[:2]), Frag: b[2], } atype := b[3] hlen := 0 switch atype { case ipV4: hlen = 10 case ipV6: hlen = 22 case domainName: hlen = 7 + int(b[4]) default: return nil, errors.New("addr not support") } dlen := int(header.Rsv) if dlen == 0 { // standard SOCKS5 UDP datagram extra, err := ioutil.ReadAll(r) // we assume no redundant data if err != nil { return nil, err } copy(b[n:], extra) n += len(extra) // total length dlen = n - hlen // data length } else { // extended feature, for UDP over TCP, using reserved field as data length if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil { return nil, err } n = hlen + dlen } header.Addr = new(Addr) if err := header.Addr.Decode(b[3:hlen]); err != nil { return nil, err } data := make([]byte, dlen) copy(data, b[hlen:n]) d := &UDPDatagram{ Header: header, Data: data, } return d, nil } func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram { return &UDPDatagram{ Header: header, Data: data, } } func (d *UDPDatagram) Write(w io.Writer) error { h := d.Header if h == nil { h = &UDPHeader{} } buf := bytes.Buffer{} if err := h.Write(&buf); err != nil { return err } if _, err := buf.Write(d.Data); err != nil { return err } _, err := buf.WriteTo(w) return err } func ToSocksAddr(addr net.Addr) *Addr { host := "0.0.0.0" port := 0 if addr != nil { h, p, _ := net.SplitHostPort(addr.String()) host = h port, _ = strconv.Atoi(p) } return &Addr{ Type: ipV4, Host: host, Port: uint16(port), } } ================================================ FILE: lib/common/pool.go ================================================ package common import ( "sync" ) const PoolSize = 64 * 1024 const PoolSizeSmall = 100 const PoolSizeUdp = 1472 + 200 const PoolSizeCopy = 32 << 10 var BufPool = sync.Pool{ New: func() interface{} { return make([]byte, PoolSize) }, } var BufPoolUdp = sync.Pool{ New: func() interface{} { return make([]byte, PoolSizeUdp) }, } var BufPoolMax = sync.Pool{ New: func() interface{} { return make([]byte, PoolSize) }, } var BufPoolSmall = sync.Pool{ New: func() interface{} { return make([]byte, PoolSizeSmall) }, } var BufPoolCopy = sync.Pool{ New: func() interface{} { return make([]byte, PoolSizeCopy) }, } func PutBufPoolUdp(buf []byte) { if cap(buf) == PoolSizeUdp { BufPoolUdp.Put(buf[:PoolSizeUdp]) } } func PutBufPoolCopy(buf []byte) { if cap(buf) == PoolSizeCopy { BufPoolCopy.Put(buf[:PoolSizeCopy]) } } func GetBufPoolCopy() []byte { return (BufPoolCopy.Get().([]byte))[:PoolSizeCopy] } func PutBufPoolMax(buf []byte) { if cap(buf) == PoolSize { BufPoolMax.Put(buf[:PoolSize]) } } type copyBufferPool struct { pool sync.Pool } func (Self *copyBufferPool) New() { Self.pool = sync.Pool{ New: func() interface{} { return make([]byte, PoolSizeCopy, PoolSizeCopy) }, } } func (Self *copyBufferPool) Get() []byte { buf := Self.pool.Get().([]byte) return buf[:PoolSizeCopy] // just like make a new slice, but data may not be 0 } func (Self *copyBufferPool) Put(x []byte) { if len(x) == PoolSizeCopy { Self.pool.Put(x) } else { x = nil // buf is not full, not allowed, New method returns a full buf } } var once = sync.Once{} var CopyBuff = copyBufferPool{} func newPool() { CopyBuff.New() } func init() { once.Do(newPool) } ================================================ FILE: lib/common/pprof.go ================================================ package common import ( "github.com/astaxie/beego" "github.com/astaxie/beego/logs" "net/http" _ "net/http/pprof" ) func InitPProfFromFile() { ip := beego.AppConfig.String("pprof_ip") p := beego.AppConfig.String("pprof_port") if len(ip) > 0 && len(p) > 0 && IsPort(p) { runPProf(ip + ":" + p) } } func InitPProfFromArg(arg string) { if len(arg) > 0 { runPProf(arg) } } func runPProf(ipPort string) { go func() { _ = http.ListenAndServe(ipPort, nil) }() logs.Info("PProf debug listen on", ipPort) } ================================================ FILE: lib/common/run.go ================================================ package common import ( "os" "path/filepath" "runtime" ) //Get the currently selected configuration file directory //For non-Windows systems, select the /etc/nps as config directory if exist, or select ./ //windows system, select the C:\Program Files\nps as config directory if exist, or select ./ func GetRunPath() string { var path string if path = GetInstallPath(); !FileExists(path) { return GetAppPath() } return path } //Different systems get different installation paths func GetInstallPath() string { var path string if IsWindows() { path = `C:\Program Files\nps` } else { path = "/etc/nps" } return path } //Get the absolute path to the running directory func GetAppPath() string { if path, err := filepath.Abs(filepath.Dir(os.Args[0])); err == nil { return path } return os.Args[0] } //Determine whether the current system is a Windows system? func IsWindows() bool { if runtime.GOOS == "windows" { return true } return false } //interface log file path func GetLogPath() string { var path string if IsWindows() { path = filepath.Join(GetAppPath(), "nps.log") } else { path = "/var/log/nps.log" } return path } //interface npc log file path func GetNpcLogPath() string { var path string if IsWindows() { path = filepath.Join(GetAppPath(), "npc.log") } else { path = "/var/log/npc.log" } return path } //interface pid file path func GetTmpPath() string { var path string if IsWindows() { path = GetAppPath() } else { path = "/tmp" } return path } //config file path func GetConfigPath() string { var path string if IsWindows() { path = filepath.Join(GetAppPath(), "conf/npc.conf") } else { path = "conf/npc.conf" } return path } ================================================ FILE: lib/common/util.go ================================================ package common import ( "bytes" "ehang.io/nps/lib/version" "encoding/base64" "encoding/binary" "errors" "fmt" "html/template" "io" "io/ioutil" "net" "net/http" "os" "regexp" "strconv" "strings" "sync" "ehang.io/nps/lib/crypt" ) //Get the corresponding IP address through domain name func GetHostByName(hostname string) string { if !DomainCheck(hostname) { return hostname } ips, _ := net.LookupIP(hostname) if ips != nil { for _, v := range ips { if v.To4() != nil { return v.String() } } } return "" } //Check the legality of domain func DomainCheck(domain string) bool { var match bool IsLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/)" NotLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}" match, _ = regexp.MatchString(IsLine, domain) if !match { match, _ = regexp.MatchString(NotLine, domain) } return match } //Check if the Request request is validated func CheckAuth(r *http.Request, user, passwd string) bool { s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) if len(s) != 2 { s = strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2) if len(s) != 2 { return false } } b, err := base64.StdEncoding.DecodeString(s[1]) if err != nil { return false } pair := strings.SplitN(string(b), ":", 2) if len(pair) != 2 { return false } return pair[0] == user && pair[1] == passwd } //get bool by str func GetBoolByStr(s string) bool { switch s { case "1", "true": return true } return false } //get str by bool func GetStrByBool(b bool) string { if b { return "1" } return "0" } //int func GetIntNoErrByStr(str string) int { i, _ := strconv.Atoi(strings.TrimSpace(str)) return i } //Get verify value func Getverifyval(vkey string) string { return crypt.Md5(vkey) } //Change headers and host of request func ChangeHostAndHeader(r *http.Request, host string, header string, addr string, addOrigin bool) { if host != "" { r.Host = host } if header != "" { h := strings.Split(header, "\n") for _, v := range h { hd := strings.Split(v, ":") if len(hd) == 2 { r.Header.Set(hd[0], hd[1]) } } } addr = strings.Split(addr, ":")[0] if prior, ok := r.Header["X-Forwarded-For"]; ok { addr = strings.Join(prior, ", ") + ", " + addr } if addOrigin { r.Header.Set("X-Forwarded-For", addr) r.Header.Set("X-Real-IP", addr) } } //Read file content by file path func ReadAllFromFile(filePath string) ([]byte, error) { f, err := os.Open(filePath) if err != nil { return nil, err } defer f.Close() return ioutil.ReadAll(f) } // FileExists reports whether the named file or directory exists. func FileExists(name string) bool { if _, err := os.Stat(name); err != nil { if os.IsNotExist(err) { return false } } return true } //Judge whether the TCP port can open normally func TestTcpPort(port int) bool { l, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), port, ""}) defer func() { if l != nil { l.Close() } }() if err != nil { return false } return true } //Judge whether the UDP port can open normally func TestUdpPort(port int) bool { l, err := net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), port, ""}) defer func() { if l != nil { l.Close() } }() if err != nil { return false } return true } //Write length and individual byte data //Length prevents sticking //# Characters are used to separate data func BinaryWrite(raw *bytes.Buffer, v ...string) { b := GetWriteStr(v...) binary.Write(raw, binary.LittleEndian, int32(len(b))) binary.Write(raw, binary.LittleEndian, b) } // get seq str func GetWriteStr(v ...string) []byte { buffer := new(bytes.Buffer) var l int32 for _, v := range v { l += int32(len([]byte(v))) + int32(len([]byte(CONN_DATA_SEQ))) binary.Write(buffer, binary.LittleEndian, []byte(v)) binary.Write(buffer, binary.LittleEndian, []byte(CONN_DATA_SEQ)) } return buffer.Bytes() } //inArray str interface func InStrArr(arr []string, val string) bool { for _, v := range arr { if v == val { return true } } return false } //inArray int interface func InIntArr(arr []int, val int) bool { for _, v := range arr { if v == val { return true } } return false } //format ports str to a int array func GetPorts(p string) []int { var ps []int arr := strings.Split(p, ",") for _, v := range arr { fw := strings.Split(v, "-") if len(fw) == 2 { if IsPort(fw[0]) && IsPort(fw[1]) { start, _ := strconv.Atoi(fw[0]) end, _ := strconv.Atoi(fw[1]) for i := start; i <= end; i++ { ps = append(ps, i) } } else { continue } } else if IsPort(v) { p, _ := strconv.Atoi(v) ps = append(ps, p) } } return ps } //is the string a port func IsPort(p string) bool { pi, err := strconv.Atoi(p) if err != nil { return false } if pi > 65536 || pi < 1 { return false } return true } //if the s is just a port,return 127.0.0.1:s func FormatAddress(s string) string { if strings.Contains(s, ":") { return s } return "127.0.0.1:" + s } //get address from the complete address func GetIpByAddr(addr string) string { arr := strings.Split(addr, ":") return arr[0] } //get port from the complete address func GetPortByAddr(addr string) int { arr := strings.Split(addr, ":") if len(arr) < 2 { return 0 } p, err := strconv.Atoi(arr[1]) if err != nil { return 0 } return p } func CopyBuffer(dst io.Writer, src io.Reader, label ...string) (written int64, err error) { buf := CopyBuff.Get() defer CopyBuff.Put(buf) for { nr, er := src.Read(buf) //if len(pr)>0 && pr[0] && nr > 50 { // logs.Warn(string(buf[:50])) //} if nr > 0 { nw, ew := dst.Write(buf[0:nr]) if nw > 0 { written += int64(nw) } if ew != nil { err = ew break } if nr != nw { err = io.ErrShortWrite break } } if er != nil { err = er break } } return written, err } //send this ip forget to get a local udp port func GetLocalUdpAddr() (net.Conn, error) { tmpConn, err := net.Dial("udp", "114.114.114.114:53") if err != nil { return nil, err } return tmpConn, tmpConn.Close() } //parse template func ParseStr(str string) (string, error) { tmp := template.New("npc") var err error w := new(bytes.Buffer) if tmp, err = tmp.Parse(str); err != nil { return "", err } if err = tmp.Execute(w, GetEnvMap()); err != nil { return "", err } return w.String(), nil } //get env func GetEnvMap() map[string]string { m := make(map[string]string) environ := os.Environ() for i := range environ { tmp := strings.Split(environ[i], "=") if len(tmp) == 2 { m[tmp[0]] = tmp[1] } } return m } //throw the empty element of the string array func TrimArr(arr []string) []string { newArr := make([]string, 0) for _, v := range arr { if v != "" { newArr = append(newArr, v) } } return newArr } // func IsArrContains(arr []string, val string) bool { if arr == nil { return false } for _, v := range arr { if v == val { return true } } return false } //remove value from string array func RemoveArrVal(arr []string, val string) []string { for k, v := range arr { if v == val { arr = append(arr[:k], arr[k+1:]...) return arr } } return arr } //convert bytes to num func BytesToNum(b []byte) int { var str string for i := 0; i < len(b); i++ { str += strconv.Itoa(int(b[i])) } x, _ := strconv.Atoi(str) return int(x) } //get the length of the sync map func GeSynctMapLen(m sync.Map) int { var c int m.Range(func(key, value interface{}) bool { c++ return true }) return c } func GetExtFromPath(path string) string { s := strings.Split(path, ".") re, err := regexp.Compile(`(\w+)`) if err != nil { return "" } return string(re.Find([]byte(s[0]))) } var externalIp string func GetExternalIp() string { if externalIp != "" { return externalIp } resp, err := http.Get("http://myexternalip.com/raw") if err != nil { return "" } defer resp.Body.Close() content, _ := ioutil.ReadAll(resp.Body) externalIp = string(content) return externalIp } func GetIntranetIp() (error, string) { addrs, err := net.InterfaceAddrs() if err != nil { return nil, "" } for _, address := range addrs { // 检查ip地址判断是否回环地址 if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { return nil, ipnet.IP.To4().String() } } } return errors.New("get intranet ip error"), "" } func IsPublicIP(IP net.IP) bool { if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() { return false } if ip4 := IP.To4(); ip4 != nil { switch true { case ip4[0] == 10: return false case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31: return false case ip4[0] == 192 && ip4[1] == 168: return false default: return true } } return false } func GetServerIpByClientIp(clientIp net.IP) string { if IsPublicIP(clientIp) { return GetExternalIp() } _, ip := GetIntranetIp() return ip } func PrintVersion() { fmt.Printf("Version: %s\nCore version: %s\nSame core version of client and server can connect each other\n", version.VERSION, version.GetVersion()) } ================================================ FILE: lib/config/config.go ================================================ package config import ( "errors" "fmt" "regexp" "strings" "ehang.io/nps/lib/common" "ehang.io/nps/lib/file" ) type CommonConfig struct { Server string VKey string Tp string //bridgeType kcp or tcp AutoReconnection bool ProxyUrl string Client *file.Client DisconnectTime int } type LocalServer struct { Type string Port int Ip string Password string Target string } type Config struct { content string title []string CommonConfig *CommonConfig Hosts []*file.Host Tasks []*file.Tunnel Healths []*file.Health LocalServer []*LocalServer } func NewConfig(path string) (c *Config, err error) { c = new(Config) var b []byte if b, err = common.ReadAllFromFile(path); err != nil { return } else { if c.content, err = common.ParseStr(string(b)); err != nil { return nil, err } if c.title, err = getAllTitle(c.content); err != nil { return } var nowIndex int var nextIndex int var nowContent string for i := 0; i < len(c.title); i++ { nowIndex = strings.Index(c.content, c.title[i]) + len(c.title[i]) if i < len(c.title)-1 { nextIndex = strings.Index(c.content, c.title[i+1]) } else { nextIndex = len(c.content) } nowContent = c.content[nowIndex:nextIndex] if strings.Index(getTitleContent(c.title[i]), "secret") == 0 && !strings.Contains(nowContent, "mode") { local := delLocalService(nowContent) local.Type = "secret" c.LocalServer = append(c.LocalServer, local) continue } //except mode if strings.Index(getTitleContent(c.title[i]), "p2p") == 0 && !strings.Contains(nowContent, "mode") { local := delLocalService(nowContent) local.Type = "p2p" c.LocalServer = append(c.LocalServer, local) continue } //health set if strings.Index(getTitleContent(c.title[i]), "health") == 0 { c.Healths = append(c.Healths, dealHealth(nowContent)) continue } switch c.title[i] { case "[common]": c.CommonConfig = dealCommon(nowContent) default: if strings.Index(nowContent, "host") > -1 { h := dealHost(nowContent) h.Remark = getTitleContent(c.title[i]) c.Hosts = append(c.Hosts, h) } else { t := dealTunnel(nowContent) t.Remark = getTitleContent(c.title[i]) c.Tasks = append(c.Tasks, t) } } } } return } func getTitleContent(s string) string { re, _ := regexp.Compile(`[\[\]]`) return re.ReplaceAllString(s, "") } func dealCommon(s string) *CommonConfig { c := &CommonConfig{} c.Client = file.NewClient("", true, true) c.Client.Cnf = new(file.Config) for _, v := range splitStr(s) { item := strings.Split(v, "=") if len(item) == 0 { continue } else if len(item) == 1 { item = append(item, "") } switch item[0] { case "server_addr": c.Server = item[1] case "vkey": c.VKey = item[1] case "conn_type": c.Tp = item[1] case "auto_reconnection": c.AutoReconnection = common.GetBoolByStr(item[1]) case "basic_username": c.Client.Cnf.U = item[1] case "basic_password": c.Client.Cnf.P = item[1] case "web_password": c.Client.WebPassword = item[1] case "web_username": c.Client.WebUserName = item[1] case "compress": c.Client.Cnf.Compress = common.GetBoolByStr(item[1]) case "crypt": c.Client.Cnf.Crypt = common.GetBoolByStr(item[1]) case "proxy_url": c.ProxyUrl = item[1] case "rate_limit": c.Client.RateLimit = common.GetIntNoErrByStr(item[1]) case "flow_limit": c.Client.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[1])) case "max_conn": c.Client.MaxConn = common.GetIntNoErrByStr(item[1]) case "remark": c.Client.Remark = item[1] case "pprof_addr": common.InitPProfFromArg(item[1]) case "disconnect_timeout": c.DisconnectTime = common.GetIntNoErrByStr(item[1]) } } return c } func dealHost(s string) *file.Host { h := &file.Host{} h.Target = new(file.Target) h.Scheme = "all" var headerChange string for _, v := range splitStr(s) { item := strings.Split(v, "=") if len(item) == 0 { continue } else if len(item) == 1 { item = append(item, "") } switch strings.TrimSpace(item[0]) { case "host": h.Host = item[1] case "target_addr": h.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1) case "host_change": h.HostChange = item[1] case "scheme": h.Scheme = item[1] case "location": h.Location = item[1] default: if strings.Contains(item[0], "header") { headerChange += strings.Replace(item[0], "header_", "", -1) + ":" + item[1] + "\n" } h.HeaderChange = headerChange } } return h } func dealHealth(s string) *file.Health { h := &file.Health{} for _, v := range splitStr(s) { item := strings.Split(v, "=") if len(item) == 0 { continue } else if len(item) == 1 { item = append(item, "") } switch strings.TrimSpace(item[0]) { case "health_check_timeout": h.HealthCheckTimeout = common.GetIntNoErrByStr(item[1]) case "health_check_max_failed": h.HealthMaxFail = common.GetIntNoErrByStr(item[1]) case "health_check_interval": h.HealthCheckInterval = common.GetIntNoErrByStr(item[1]) case "health_http_url": h.HttpHealthUrl = item[1] case "health_check_type": h.HealthCheckType = item[1] case "health_check_target": h.HealthCheckTarget = item[1] } } return h } func dealTunnel(s string) *file.Tunnel { t := &file.Tunnel{} t.Target = new(file.Target) for _, v := range splitStr(s) { item := strings.Split(v, "=") if len(item) == 0 { continue } else if len(item) == 1 { item = append(item, "") } switch strings.TrimSpace(item[0]) { case "server_port": t.Ports = item[1] case "server_ip": t.ServerIp = item[1] case "mode": t.Mode = item[1] case "target_addr": t.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1) case "target_port": t.Target.TargetStr = item[1] case "target_ip": t.TargetAddr = item[1] case "password": t.Password = item[1] case "local_path": t.LocalPath = item[1] case "strip_pre": t.StripPre = item[1] case "multi_account": t.MultiAccount = &file.MultiAccount{} if common.FileExists(item[1]) { if b, err := common.ReadAllFromFile(item[1]); err != nil { panic(err) } else { if content, err := common.ParseStr(string(b)); err != nil { panic(err) } else { t.MultiAccount.AccountMap = dealMultiUser(content) } } } } } return t } func dealMultiUser(s string) map[string]string { multiUserMap := make(map[string]string) for _, v := range splitStr(s) { item := strings.Split(v, "=") if len(item) == 0 { continue } else if len(item) == 1 { item = append(item, "") } multiUserMap[strings.TrimSpace(item[0])] = item[1] } return multiUserMap } func delLocalService(s string) *LocalServer { l := new(LocalServer) for _, v := range splitStr(s) { item := strings.Split(v, "=") if len(item) == 0 { continue } else if len(item) == 1 { item = append(item, "") } switch item[0] { case "local_port": l.Port = common.GetIntNoErrByStr(item[1]) case "local_ip": l.Ip = item[1] case "password": l.Password = item[1] case "target_addr": l.Target = item[1] } } return l } func getAllTitle(content string) (arr []string, err error) { var re *regexp.Regexp re, err = regexp.Compile(`(?m)^\[[^\[\]\r\n]+\]`) if err != nil { return } arr = re.FindAllString(content, -1) m := make(map[string]bool) for _, v := range arr { if _, ok := m[v]; ok { err = errors.New(fmt.Sprintf("Item names %s are not allowed to be duplicated", v)) return } m[v] = true } return } func splitStr(s string) (configDataArr []string) { if common.IsWindows() { configDataArr = strings.Split(s, "\r\n") } if len(configDataArr) < 3 { configDataArr = strings.Split(s, "\n") } return } ================================================ FILE: lib/config/config_test.go ================================================ package config import ( "log" "regexp" "testing" ) func TestReg(t *testing.T) { content := ` [common] server=127.0.0.1:8284 tp=tcp vkey=123 [web2] host=www.baidu.com host_change=www.sina.com target=127.0.0.1:8080,127.0.0.1:8082 header_cookkile=122123 header_user-Agent=122123 [web2] host=www.baidu.com host_change=www.sina.com target=127.0.0.1:8080,127.0.0.1:8082 header_cookkile="122123" header_user-Agent=122123 [tunnel1] type=udp target=127.0.0.1:8080 port=9001 compress=snappy crypt=true u=1 p=2 [tunnel2] type=tcp target=127.0.0.1:8080 port=9001 compress=snappy crypt=true u=1 p=2 ` re, err := regexp.Compile(`\[.+?\]`) if err != nil { t.Fail() } log.Println(re.FindAllString(content, -1)) } func TestDealCommon(t *testing.T) { s := `server=127.0.0.1:8284 tp=tcp vkey=123` f := new(CommonConfig) f.Server = "127.0.0.1:8284" f.Tp = "tcp" f.VKey = "123" if c := dealCommon(s); *c != *f { t.Fail() } } func TestGetTitleContent(t *testing.T) { s := "[common]" if getTitleContent(s) != "common" { t.Fail() } } ================================================ FILE: lib/conn/conn.go ================================================ package conn import ( "bufio" "bytes" "ehang.io/nps/lib/goroutine" "encoding/binary" "encoding/json" "errors" "github.com/astaxie/beego/logs" "io" "net" "net/http" "net/url" "strconv" "strings" "sync" "time" "ehang.io/nps/lib/common" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/file" "ehang.io/nps/lib/pmux" "ehang.io/nps/lib/rate" "github.com/xtaci/kcp-go" ) type Conn struct { Conn net.Conn Rb []byte } //new conn func NewConn(conn net.Conn) *Conn { return &Conn{Conn: conn} } func (s *Conn) readRequest(buf []byte) (n int, err error) { var rd int for { rd, err = s.Read(buf[n:]) if err != nil { return } n += rd if n < 4 { continue } if string(buf[n-4:n]) == "\r\n\r\n" { return } // buf is full, can't contain the request if n == cap(buf) { err = io.ErrUnexpectedEOF return } } } //get host 、connection type、method...from connection func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) { var b [32 * 1024]byte var n int if n, err = s.readRequest(b[:]); err != nil { return } rb = b[:n] r, err = http.ReadRequest(bufio.NewReader(bytes.NewReader(rb))) if err != nil { return } hostPortURL, err := url.Parse(r.Host) if err != nil { address = r.Host err = nil return } if hostPortURL.Opaque == "443" { if strings.Index(r.Host, ":") == -1 { address = r.Host + ":443" } else { address = r.Host } } else { if strings.Index(r.Host, ":") == -1 { address = r.Host + ":80" } else { address = r.Host } } return } func (s *Conn) GetShortLenContent() (b []byte, err error) { var l int if l, err = s.GetLen(); err != nil { return } if l < 0 || l > 32<<10 { err = errors.New("read length error") return } return s.GetShortContent(l) } func (s *Conn) GetShortContent(l int) (b []byte, err error) { buf := make([]byte, l) return buf, binary.Read(s, binary.LittleEndian, &buf) } //读取指定长度内容 func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) { if cLen > len(buf) || cLen <= 0 { return 0, errors.New("长度错误" + strconv.Itoa(cLen)) } if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen { return n, errors.New("Error reading specified length " + err.Error()) } return cLen, nil } func (s *Conn) GetLen() (int, error) { var l int32 err := binary.Read(s, binary.LittleEndian, &l) return int(l), err } func (s *Conn) WriteLenContent(buf []byte) (err error) { var b []byte if b, err = GetLenBytes(buf); err != nil { return } return binary.Write(s.Conn, binary.LittleEndian, b) } //read flag func (s *Conn) ReadFlag() (string, error) { buf := make([]byte, 4) return string(buf), binary.Read(s, binary.LittleEndian, &buf) } //set alive func (s *Conn) SetAlive(tp string) { switch s.Conn.(type) { case *kcp.UDPSession: s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Time{}) case *net.TCPConn: conn := s.Conn.(*net.TCPConn) conn.SetReadDeadline(time.Time{}) //conn.SetKeepAlive(false) //conn.SetKeepAlivePeriod(time.Duration(2 * time.Second)) case *pmux.PortConn: s.Conn.(*pmux.PortConn).SetReadDeadline(time.Time{}) } } //set read deadline func (s *Conn) SetReadDeadlineBySecond(t time.Duration) { switch s.Conn.(type) { case *kcp.UDPSession: s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second)) case *net.TCPConn: s.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second)) case *pmux.PortConn: s.Conn.(*pmux.PortConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second)) } } //get link info from conn func (s *Conn) GetLinkInfo() (lk *Link, err error) { err = s.getInfo(&lk) return } //send info for link func (s *Conn) SendHealthInfo(info, status string) (int, error) { raw := bytes.NewBuffer([]byte{}) common.BinaryWrite(raw, info, status) return s.Write(raw.Bytes()) } //get health info from conn func (s *Conn) GetHealthInfo() (info string, status bool, err error) { var l int buf := common.BufPoolMax.Get().([]byte) defer common.PutBufPoolMax(buf) if l, err = s.GetLen(); err != nil { return } else if _, err = s.ReadLen(l, buf); err != nil { return } else { arr := strings.Split(string(buf[:l]), common.CONN_DATA_SEQ) if len(arr) >= 2 { return arr[0], common.GetBoolByStr(arr[1]), nil } } return "", false, errors.New("receive health info error") } //get task info func (s *Conn) GetHostInfo() (h *file.Host, err error) { err = s.getInfo(&h) h.Id = int(file.GetDb().JsonDb.GetHostId()) h.Flow = new(file.Flow) h.NoStore = true return } //get task info func (s *Conn) GetConfigInfo() (c *file.Client, err error) { err = s.getInfo(&c) c.NoStore = true c.Status = true if c.Flow == nil { c.Flow = new(file.Flow) } c.NoDisplay = false return } //get task info func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) { err = s.getInfo(&t) t.Id = int(file.GetDb().JsonDb.GetTaskId()) t.NoStore = true t.Flow = new(file.Flow) return } //send info func (s *Conn) SendInfo(t interface{}, flag string) (int, error) { /* The task info is formed as follows: +----+-----+---------+ |type| len | content | +----+---------------+ | 4 | 4 | ... | +----+---------------+ */ raw := bytes.NewBuffer([]byte{}) if flag != "" { binary.Write(raw, binary.LittleEndian, []byte(flag)) } b, err := json.Marshal(t) if err != nil { return 0, err } lenBytes, err := GetLenBytes(b) if err != nil { return 0, err } binary.Write(raw, binary.LittleEndian, lenBytes) return s.Write(raw.Bytes()) } //get task info func (s *Conn) getInfo(t interface{}) (err error) { var l int buf := common.BufPoolMax.Get().([]byte) defer common.PutBufPoolMax(buf) if l, err = s.GetLen(); err != nil { return } else if _, err = s.ReadLen(l, buf); err != nil { return } else { json.Unmarshal(buf[:l], &t) } return } //close func (s *Conn) Close() error { return s.Conn.Close() } //write func (s *Conn) Write(b []byte) (int, error) { return s.Conn.Write(b) } //read func (s *Conn) Read(b []byte) (n int, err error) { if s.Rb != nil { //if the rb is not nil ,read rb first if len(s.Rb) > 0 { n = copy(b, s.Rb) s.Rb = s.Rb[n:] return } s.Rb = nil } return s.Conn.Read(b) } //write sign flag func (s *Conn) WriteClose() (int, error) { return s.Write([]byte(common.RES_CLOSE)) } //write main func (s *Conn) WriteMain() (int, error) { return s.Write([]byte(common.WORK_MAIN)) } //write main func (s *Conn) WriteConfig() (int, error) { return s.Write([]byte(common.WORK_CONFIG)) } //write chan func (s *Conn) WriteChan() (int, error) { return s.Write([]byte(common.WORK_CHAN)) } //get task or host result of add func (s *Conn) GetAddStatus() (b bool) { binary.Read(s.Conn, binary.LittleEndian, &b) return } func (s *Conn) WriteAddOk() error { return binary.Write(s.Conn, binary.LittleEndian, true) } func (s *Conn) WriteAddFail() error { defer s.Close() return binary.Write(s.Conn, binary.LittleEndian, false) } func (s *Conn) LocalAddr() net.Addr { return s.Conn.LocalAddr() } func (s *Conn) RemoteAddr() net.Addr { return s.Conn.RemoteAddr() } func (s *Conn) SetDeadline(t time.Time) error { return s.Conn.SetDeadline(t) } func (s *Conn) SetWriteDeadline(t time.Time) error { return s.Conn.SetWriteDeadline(t) } func (s *Conn) SetReadDeadline(t time.Time) error { return s.Conn.SetReadDeadline(t) } //get the assembled amount data(len 4 and content) func GetLenBytes(buf []byte) (b []byte, err error) { raw := bytes.NewBuffer([]byte{}) if err = binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil { return } if err = binary.Write(raw, binary.LittleEndian, buf); err != nil { return } b = raw.Bytes() return } //udp connection setting func SetUdpSession(sess *kcp.UDPSession) { sess.SetStreamMode(true) sess.SetWindowSize(1024, 1024) sess.SetReadBuffer(64 * 1024) sess.SetWriteBuffer(64 * 1024) sess.SetNoDelay(1, 10, 2, 1) sess.SetMtu(1600) sess.SetACKNoDelay(true) sess.SetWriteDelay(false) } //conn1 mux conn func CopyWaitGroup(conn1, conn2 net.Conn, crypt bool, snappy bool, rate *rate.Rate, flow *file.Flow, isServer bool, rb []byte) { //var in, out int64 //var wg sync.WaitGroup connHandle := GetConn(conn1, crypt, snappy, rate, isServer) if rb != nil { connHandle.Write(rb) } //go func(in *int64) { // wg.Add(1) // *in, _ = common.CopyBuffer(connHandle, conn2) // connHandle.Close() // conn2.Close() // wg.Done() //}(&in) //out, _ = common.CopyBuffer(conn2, connHandle) //connHandle.Close() //conn2.Close() //wg.Wait() //if flow != nil { // flow.Add(in, out) //} wg := new(sync.WaitGroup) wg.Add(1) err := goroutine.CopyConnsPool.Invoke(goroutine.NewConns(connHandle, conn2, flow, wg)) wg.Wait() if err != nil { logs.Error(err) } } //get crypt or snappy conn func GetConn(conn net.Conn, cpt, snappy bool, rt *rate.Rate, isServer bool) io.ReadWriteCloser { if cpt { if isServer { return rate.NewRateConn(crypt.NewTlsServerConn(conn), rt) } return rate.NewRateConn(crypt.NewTlsClientConn(conn), rt) } else if snappy { return rate.NewRateConn(NewSnappyConn(conn), rt) } return rate.NewRateConn(conn, rt) } type LenConn struct { conn io.Writer Len int } func NewLenConn(conn io.Writer) *LenConn { return &LenConn{conn: conn} } func (c *LenConn) Write(p []byte) (n int, err error) { n, err = c.conn.Write(p) c.Len += n return } ================================================ FILE: lib/conn/link.go ================================================ package conn import "time" type Secret struct { Password string Conn *Conn } func NewSecret(p string, conn *Conn) *Secret { return &Secret{ Password: p, Conn: conn, } } type Link struct { ConnType string //连接类型 Host string //目标 Crypt bool //加密 Compress bool LocalProxy bool RemoteAddr string Option Options } type Option func(*Options) type Options struct { Timeout time.Duration } var defaultTimeOut = time.Second * 5 func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool, opts ...Option) *Link { options := newOptions(opts...) return &Link{ RemoteAddr: remoteAddr, ConnType: connType, Host: host, Crypt: crypt, Compress: compress, LocalProxy: localProxy, Option: options, } } func newOptions(opts ...Option) Options { opt := Options{ Timeout: defaultTimeOut, } for _, o := range opts { o(&opt) } return opt } func LinkTimeout(t time.Duration) Option { return func(opt *Options) { opt.Timeout = t } } ================================================ FILE: lib/conn/listener.go ================================================ package conn import ( "net" "strings" "github.com/astaxie/beego/logs" "github.com/xtaci/kcp-go" ) func NewTcpListenerAndProcess(addr string, f func(c net.Conn), listener *net.Listener) error { var err error *listener, err = net.Listen("tcp", addr) if err != nil { return err } Accept(*listener, f) return nil } func NewKcpListenerAndProcess(addr string, f func(c net.Conn)) error { kcpListener, err := kcp.ListenWithOptions(addr, nil, 150, 3) if err != nil { logs.Error(err) return err } for { c, err := kcpListener.AcceptKCP() SetUdpSession(c) if err != nil { logs.Warn(err) continue } go f(c) } return nil } func Accept(l net.Listener, f func(c net.Conn)) { for { c, err := l.Accept() if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { break } if strings.Contains(err.Error(), "the mux has closed") { break } logs.Warn(err) continue } if c == nil { logs.Warn("nil connection") break } go f(c) } } ================================================ FILE: lib/conn/snappy.go ================================================ package conn import ( "errors" "io" "github.com/golang/snappy" ) type SnappyConn struct { w *snappy.Writer r *snappy.Reader c io.Closer } func NewSnappyConn(conn io.ReadWriteCloser) *SnappyConn { c := new(SnappyConn) c.w = snappy.NewBufferedWriter(conn) c.r = snappy.NewReader(conn) c.c = conn.(io.Closer) return c } //snappy压缩写 func (s *SnappyConn) Write(b []byte) (n int, err error) { if n, err = s.w.Write(b); err != nil { return } if err = s.w.Flush(); err != nil { return } return } //snappy压缩读 func (s *SnappyConn) Read(b []byte) (n int, err error) { return s.r.Read(b) } func (s *SnappyConn) Close() error { err := s.w.Close() err2 := s.c.Close() if err != nil && err2 == nil { return err } if err == nil && err2 != nil { return err2 } if err != nil && err2 != nil { return errors.New(err.Error() + err2.Error()) } return nil } ================================================ FILE: lib/crypt/clientHello.go ================================================ package crypt import ( "strings" ) type CurveID uint16 type SignatureScheme uint16 const ( statusTypeOCSP uint8 = 1 extensionServerName uint16 = 0 extensionStatusRequest uint16 = 5 extensionSupportedCurves uint16 = 10 extensionSupportedPoints uint16 = 11 extensionSignatureAlgorithms uint16 = 13 extensionALPN uint16 = 16 extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6 extensionSessionTicket uint16 = 35 extensionNextProtoNeg uint16 = 13172 // not IANA assigned extensionRenegotiationInfo uint16 = 0xff01 scsvRenegotiation uint16 = 0x00ff ) type ClientHelloMsg struct { raw []byte vers uint16 random []byte sessionId []byte cipherSuites []uint16 compressionMethods []uint8 nextProtoNeg bool serverName string ocspStapling bool scts bool supportedCurves []CurveID supportedPoints []uint8 ticketSupported bool sessionTicket []uint8 supportedSignatureAlgorithms []SignatureScheme secureRenegotiation []byte secureRenegotiationSupported bool alpnProtocols []string } func (m *ClientHelloMsg) GetServerName() string { return m.serverName } func (m *ClientHelloMsg) Unmarshal(data []byte) bool { if len(data) < 42 { return false } m.raw = data m.vers = uint16(data[4])<<8 | uint16(data[5]) m.random = data[6:38] sessionIdLen := int(data[38]) if sessionIdLen > 32 || len(data) < 39+sessionIdLen { return false } m.sessionId = data[39 : 39+sessionIdLen] data = data[39+sessionIdLen:] if len(data) < 2 { return false } // cipherSuiteLen is the number of bytes of cipher suite numbers. Since // they are uint16s, the number must be even. cipherSuiteLen := int(data[0])<<8 | int(data[1]) if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen { return false } numCipherSuites := cipherSuiteLen / 2 m.cipherSuites = make([]uint16, numCipherSuites) for i := 0; i < numCipherSuites; i++ { m.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i]) if m.cipherSuites[i] == scsvRenegotiation { m.secureRenegotiationSupported = true } } data = data[2+cipherSuiteLen:] if len(data) < 1 { return false } compressionMethodsLen := int(data[0]) if len(data) < 1+compressionMethodsLen { return false } m.compressionMethods = data[1 : 1+compressionMethodsLen] data = data[1+compressionMethodsLen:] m.nextProtoNeg = false m.serverName = "" m.ocspStapling = false m.ticketSupported = false m.sessionTicket = nil m.supportedSignatureAlgorithms = nil m.alpnProtocols = nil m.scts = false if len(data) == 0 { // ClientHello is optionally followed by extension data return true } if len(data) < 2 { return false } extensionsLength := int(data[0])<<8 | int(data[1]) data = data[2:] if extensionsLength != len(data) { return false } for len(data) != 0 { if len(data) < 4 { return false } extension := uint16(data[0])<<8 | uint16(data[1]) length := int(data[2])<<8 | int(data[3]) data = data[4:] if len(data) < length { return false } switch extension { case extensionServerName: d := data[:length] if len(d) < 2 { return false } namesLen := int(d[0])<<8 | int(d[1]) d = d[2:] if len(d) != namesLen { return false } for len(d) > 0 { if len(d) < 3 { return false } nameType := d[0] nameLen := int(d[1])<<8 | int(d[2]) d = d[3:] if len(d) < nameLen { return false } if nameType == 0 { m.serverName = string(d[:nameLen]) // An SNI value may not include a // trailing dot. See // https://tools.ietf.org/html/rfc6066#section-3. if strings.HasSuffix(m.serverName, ".") { return false } break } d = d[nameLen:] } case extensionNextProtoNeg: if length > 0 { return false } m.nextProtoNeg = true case extensionStatusRequest: m.ocspStapling = length > 0 && data[0] == statusTypeOCSP case extensionSupportedCurves: // https://tools.ietf.org/html/rfc4492#section-5.5.1 if length < 2 { return false } l := int(data[0])<<8 | int(data[1]) if l%2 == 1 || length != l+2 { return false } numCurves := l / 2 m.supportedCurves = make([]CurveID, numCurves) d := data[2:] for i := 0; i < numCurves; i++ { m.supportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1]) d = d[2:] } case extensionSupportedPoints: // https://tools.ietf.org/html/rfc4492#section-5.5.2 if length < 1 { return false } l := int(data[0]) if length != l+1 { return false } m.supportedPoints = make([]uint8, l) copy(m.supportedPoints, data[1:]) case extensionSessionTicket: // https://tools.ietf.org/html/rfc5077#section-3.2 m.ticketSupported = true m.sessionTicket = data[:length] case extensionSignatureAlgorithms: // https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 if length < 2 || length&1 != 0 { return false } l := int(data[0])<<8 | int(data[1]) if l != length-2 { return false } n := l / 2 d := data[2:] m.supportedSignatureAlgorithms = make([]SignatureScheme, n) for i := range m.supportedSignatureAlgorithms { m.supportedSignatureAlgorithms[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1]) d = d[2:] } case extensionRenegotiationInfo: if length == 0 { return false } d := data[:length] l := int(d[0]) d = d[1:] if l != len(d) { return false } m.secureRenegotiation = d m.secureRenegotiationSupported = true case extensionALPN: if length < 2 { return false } l := int(data[0])<<8 | int(data[1]) if l != length-2 { return false } d := data[2:length] for len(d) != 0 { stringLen := int(d[0]) d = d[1:] if stringLen == 0 || stringLen > len(d) { return false } m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen])) d = d[stringLen:] } case extensionSCT: m.scts = true if length != 0 { return false } } data = data[length:] } return true } ================================================ FILE: lib/crypt/crypt.go ================================================ package crypt import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/md5" "encoding/hex" "errors" "math/rand" "time" ) //en func AesEncrypt(origData, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() origData = PKCS5Padding(origData, blockSize) blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) crypted := make([]byte, len(origData)) blockMode.CryptBlocks(crypted, origData) return crypted, nil } //de func AesDecrypt(crypted, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) origData := make([]byte, len(crypted)) blockMode.CryptBlocks(origData, crypted) err, origData = PKCS5UnPadding(origData) return origData, err } //Completion when the length is insufficient func PKCS5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } //Remove excess func PKCS5UnPadding(origData []byte) (error, []byte) { length := len(origData) unpadding := int(origData[length-1]) if (length - unpadding) < 0 { return errors.New("len error"), nil } return nil, origData[:(length - unpadding)] } //Generate 32-bit MD5 strings func Md5(s string) string { h := md5.New() h.Write([]byte(s)) return hex.EncodeToString(h.Sum(nil)) } //Generating Random Verification Key func GetRandomString(l int) string { str := "0123456789abcdefghijklmnopqrstuvwxyz" bytes := []byte(str) result := []byte{} r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < l; i++ { result = append(result, bytes[r.Intn(len(bytes))]) } return string(result) } ================================================ FILE: lib/crypt/tls.go ================================================ package crypt import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "log" "math/big" "net" "os" "time" "github.com/astaxie/beego/logs" ) var ( cert tls.Certificate ) func InitTls() { c, k, err := generateKeyPair("NPS Org") if err == nil { cert, err = tls.X509KeyPair(c, k) } if err != nil { log.Fatalln("Error initializing crypto certs", err) } } func NewTlsServerConn(conn net.Conn) net.Conn { var err error if err != nil { logs.Error(err) os.Exit(0) return nil } config := &tls.Config{Certificates: []tls.Certificate{cert}} return tls.Server(conn, config) } func NewTlsClientConn(conn net.Conn) net.Conn { conf := &tls.Config{ InsecureSkipVerify: true, } return tls.Client(conn, conf) } func generateKeyPair(CommonName string) (rawCert, rawKey []byte, err error) { // Create private key and self-signed certificate // Adapted from https://golang.org/src/crypto/tls/generate_cert.go priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return } validFor := time.Hour * 24 * 365 * 10 // ten years notBefore := time.Now() notAfter := notBefore.Add(validFor) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"My Company Name LTD."}, CommonName: CommonName, Country: []string{"US"}, }, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return } rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) return } ================================================ FILE: lib/daemon/daemon.go ================================================ package daemon import ( "io/ioutil" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "ehang.io/nps/lib/common" ) func InitDaemon(f string, runPath string, pidPath string) { if len(os.Args) < 2 { return } var args []string args = append(args, os.Args[0]) if len(os.Args) >= 2 { args = append(args, os.Args[2:]...) } args = append(args, "-log=file") switch os.Args[1] { case "start": start(args, f, pidPath, runPath) os.Exit(0) case "stop": stop(f, args[0], pidPath) os.Exit(0) case "restart": stop(f, args[0], pidPath) start(args, f, pidPath, runPath) os.Exit(0) case "status": if status(f, pidPath) { log.Printf("%s is running", f) } else { log.Printf("%s is not running", f) } os.Exit(0) case "reload": reload(f, pidPath) os.Exit(0) } } func reload(f string, pidPath string) { if f == "nps" && !common.IsWindows() && !status(f, pidPath) { log.Println("reload fail") return } var c *exec.Cmd var err error b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid")) if err == nil { c = exec.Command("/bin/bash", "-c", `kill -30 `+string(b)) } else { log.Fatalln("reload error,pid file does not exist") } if c.Run() == nil { log.Println("reload success") } else { log.Println("reload fail") } } func status(f string, pidPath string) bool { var cmd *exec.Cmd b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid")) if err == nil { if !common.IsWindows() { cmd = exec.Command("/bin/sh", "-c", "ps -ax | awk '{ print $1 }' | grep "+string(b)) } else { cmd = exec.Command("tasklist") } out, _ := cmd.Output() if strings.Index(string(out), string(b)) > -1 { return true } } return false } func start(osArgs []string, f string, pidPath, runPath string) { if status(f, pidPath) { log.Printf(" %s is running", f) return } cmd := exec.Command(osArgs[0], osArgs[1:]...) cmd.Start() if cmd.Process.Pid > 0 { log.Println("start ok , pid:", cmd.Process.Pid, "config path:", runPath) d1 := []byte(strconv.Itoa(cmd.Process.Pid)) ioutil.WriteFile(filepath.Join(pidPath, f+".pid"), d1, 0600) } else { log.Println("start error") } } func stop(f string, p string, pidPath string) { if !status(f, pidPath) { log.Printf(" %s is not running", f) return } var c *exec.Cmd var err error if common.IsWindows() { p := strings.Split(p, `\`) c = exec.Command("taskkill", "/F", "/IM", p[len(p)-1]) } else { b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid")) if err == nil { c = exec.Command("/bin/bash", "-c", `kill -9 `+string(b)) } else { log.Fatalln("stop error,pid file does not exist") } } err = c.Run() if err != nil { log.Println("stop error,", err) } else { log.Println("stop ok") } } ================================================ FILE: lib/daemon/reload.go ================================================ // +build !windows package daemon import ( "os" "os/signal" "path/filepath" "syscall" "ehang.io/nps/lib/common" "github.com/astaxie/beego" ) func init() { s := make(chan os.Signal, 1) signal.Notify(s, syscall.SIGUSR1) go func() { for { <-s beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf")) } }() } ================================================ FILE: lib/file/db.go ================================================ package file import ( "errors" "fmt" "net/http" "sort" "strings" "sync" "ehang.io/nps/lib/common" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/rate" ) type DbUtils struct { JsonDb *JsonDb } var ( Db *DbUtils once sync.Once ) //init csv from file func GetDb() *DbUtils { once.Do(func() { jsonDb := NewJsonDb(common.GetRunPath()) jsonDb.LoadClientFromJsonFile() jsonDb.LoadTaskFromJsonFile() jsonDb.LoadHostFromJsonFile() Db = &DbUtils{JsonDb: jsonDb} }) return Db } func GetMapKeys(m sync.Map, isSort bool, sortKey, order string) (keys []int) { if sortKey != "" && isSort { return sortClientByKey(m, sortKey, order) } m.Range(func(key, value interface{}) bool { keys = append(keys, key.(int)) return true }) sort.Ints(keys) return } func (s *DbUtils) GetClientList(start, length int, search, sort, order string, clientId int) ([]*Client, int) { list := make([]*Client, 0) var cnt int keys := GetMapKeys(s.JsonDb.Clients, true, sort, order) for _, key := range keys { if value, ok := s.JsonDb.Clients.Load(key); ok { v := value.(*Client) if v.NoDisplay { continue } if clientId != 0 && clientId != v.Id { continue } if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.VerifyKey, search) || strings.Contains(v.Remark, search)) { continue } cnt++ if start--; start < 0 { if length--; length >= 0 { list = append(list, v) } } } } return list, cnt } func (s *DbUtils) GetIdByVerifyKey(vKey string, addr string) (id int, err error) { var exist bool s.JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*Client) if common.Getverifyval(v.VerifyKey) == vKey && v.Status { v.Addr = common.GetIpByAddr(addr) id = v.Id exist = true return false } return true }) if exist { return } return 0, errors.New("not found") } func (s *DbUtils) NewTask(t *Tunnel) (err error) { s.JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*Tunnel) if (v.Mode == "secret" || v.Mode == "p2p") && v.Password == t.Password { err = errors.New(fmt.Sprintf("secret mode keys %s must be unique", t.Password)) return false } return true }) if err != nil { return } t.Flow = new(Flow) s.JsonDb.Tasks.Store(t.Id, t) s.JsonDb.StoreTasksToJsonFile() return } func (s *DbUtils) UpdateTask(t *Tunnel) error { s.JsonDb.Tasks.Store(t.Id, t) s.JsonDb.StoreTasksToJsonFile() return nil } func (s *DbUtils) DelTask(id int) error { s.JsonDb.Tasks.Delete(id) s.JsonDb.StoreTasksToJsonFile() return nil } //md5 password func (s *DbUtils) GetTaskByMd5Password(p string) (t *Tunnel) { s.JsonDb.Tasks.Range(func(key, value interface{}) bool { if crypt.Md5(value.(*Tunnel).Password) == p { t = value.(*Tunnel) return false } return true }) return } func (s *DbUtils) GetTask(id int) (t *Tunnel, err error) { if v, ok := s.JsonDb.Tasks.Load(id); ok { t = v.(*Tunnel) return } err = errors.New("not found") return } func (s *DbUtils) DelHost(id int) error { s.JsonDb.Hosts.Delete(id) s.JsonDb.StoreHostToJsonFile() return nil } func (s *DbUtils) IsHostExist(h *Host) bool { var exist bool s.JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*Host) if v.Id != h.Id && v.Host == h.Host && h.Location == v.Location && (v.Scheme == "all" || v.Scheme == h.Scheme) { exist = true return false } return true }) return exist } func (s *DbUtils) NewHost(t *Host) error { if t.Location == "" { t.Location = "/" } if s.IsHostExist(t) { return errors.New("host has exist") } t.Flow = new(Flow) s.JsonDb.Hosts.Store(t.Id, t) s.JsonDb.StoreHostToJsonFile() return nil } func (s *DbUtils) GetHost(start, length int, id int, search string) ([]*Host, int) { list := make([]*Host, 0) var cnt int keys := GetMapKeys(s.JsonDb.Hosts, false, "", "") for _, key := range keys { if value, ok := s.JsonDb.Hosts.Load(key); ok { v := value.(*Host) if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.Host, search) || strings.Contains(v.Remark, search)) { continue } if id == 0 || v.Client.Id == id { cnt++ if start--; start < 0 { if length--; length >= 0 { list = append(list, v) } } } } } return list, cnt } func (s *DbUtils) DelClient(id int) error { s.JsonDb.Clients.Delete(id) s.JsonDb.StoreClientsToJsonFile() return nil } func (s *DbUtils) NewClient(c *Client) error { var isNotSet bool if c.WebUserName != "" && !s.VerifyUserName(c.WebUserName, c.Id) { return errors.New("web login username duplicate, please reset") } reset: if c.VerifyKey == "" || isNotSet { isNotSet = true c.VerifyKey = crypt.GetRandomString(16) } if c.RateLimit == 0 { c.Rate = rate.NewRate(int64(2 << 23)) } else if c.Rate == nil { c.Rate = rate.NewRate(int64(c.RateLimit * 1024)) } c.Rate.Start() if !s.VerifyVkey(c.VerifyKey, c.Id) { if isNotSet { goto reset } return errors.New("Vkey duplicate, please reset") } if c.Id == 0 { c.Id = int(s.JsonDb.GetClientId()) } if c.Flow == nil { c.Flow = new(Flow) } s.JsonDb.Clients.Store(c.Id, c) s.JsonDb.StoreClientsToJsonFile() return nil } func (s *DbUtils) VerifyVkey(vkey string, id int) (res bool) { res = true s.JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*Client) if v.VerifyKey == vkey && v.Id != id { res = false return false } return true }) return res } func (s *DbUtils) VerifyUserName(username string, id int) (res bool) { res = true s.JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*Client) if v.WebUserName == username && v.Id != id { res = false return false } return true }) return res } func (s *DbUtils) UpdateClient(t *Client) error { s.JsonDb.Clients.Store(t.Id, t) if t.RateLimit == 0 { t.Rate = rate.NewRate(int64(2 << 23)) t.Rate.Start() } return nil } func (s *DbUtils) IsPubClient(id int) bool { client, err := s.GetClient(id) if err == nil { return client.NoDisplay } return false } func (s *DbUtils) GetClient(id int) (c *Client, err error) { if v, ok := s.JsonDb.Clients.Load(id); ok { c = v.(*Client) return } err = errors.New("未找到客户端") return } func (s *DbUtils) GetClientIdByVkey(vkey string) (id int, err error) { var exist bool s.JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*Client) if crypt.Md5(v.VerifyKey) == vkey { exist = true id = v.Id return false } return true }) if exist { return } err = errors.New("未找到客户端") return } func (s *DbUtils) GetHostById(id int) (h *Host, err error) { if v, ok := s.JsonDb.Hosts.Load(id); ok { h = v.(*Host) return } err = errors.New("The host could not be parsed") return } //get key by host from x func (s *DbUtils) GetInfoByHost(host string, r *http.Request) (h *Host, err error) { var hosts []*Host //Handling Ported Access host = common.GetIpByAddr(host) s.JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*Host) if v.IsClose { return true } //Remove http(s) http(s)://a.proxy.com //*.proxy.com *.a.proxy.com Do some pan-parsing if v.Scheme != "all" && v.Scheme != r.URL.Scheme { return true } tmpHost := v.Host if strings.Contains(tmpHost, "*") { tmpHost = strings.Replace(tmpHost, "*", "", -1) if strings.Contains(host, tmpHost) { hosts = append(hosts, v) } } else if v.Host == host { hosts = append(hosts, v) } return true }) for _, v := range hosts { //If not set, default matches all if v.Location == "" { v.Location = "/" } if strings.Index(r.RequestURI, v.Location) == 0 { if h == nil || (len(v.Location) > len(h.Location)) { h = v } } } if h != nil { return } err = errors.New("The host could not be parsed") return } ================================================ FILE: lib/file/file.go ================================================ package file import ( "encoding/json" "errors" "github.com/astaxie/beego/logs" "os" "path/filepath" "strings" "sync" "sync/atomic" "ehang.io/nps/lib/common" "ehang.io/nps/lib/rate" ) func NewJsonDb(runPath string) *JsonDb { return &JsonDb{ RunPath: runPath, TaskFilePath: filepath.Join(runPath, "conf", "tasks.json"), HostFilePath: filepath.Join(runPath, "conf", "hosts.json"), ClientFilePath: filepath.Join(runPath, "conf", "clients.json"), } } type JsonDb struct { Tasks sync.Map Hosts sync.Map HostsTmp sync.Map Clients sync.Map RunPath string ClientIncreaseId int32 //client increased id TaskIncreaseId int32 //task increased id HostIncreaseId int32 //host increased id TaskFilePath string //task file path HostFilePath string //host file path ClientFilePath string //client file path } func (s *JsonDb) LoadTaskFromJsonFile() { loadSyncMapFromFile(s.TaskFilePath, func(v string) { var err error post := new(Tunnel) if json.Unmarshal([]byte(v), &post) != nil { return } if post.Client, err = s.GetClient(post.Client.Id); err != nil { return } s.Tasks.Store(post.Id, post) if post.Id > int(s.TaskIncreaseId) { s.TaskIncreaseId = int32(post.Id) } }) } func (s *JsonDb) LoadClientFromJsonFile() { loadSyncMapFromFile(s.ClientFilePath, func(v string) { post := new(Client) if json.Unmarshal([]byte(v), &post) != nil { return } if post.RateLimit > 0 { post.Rate = rate.NewRate(int64(post.RateLimit * 1024)) } else { post.Rate = rate.NewRate(int64(2 << 23)) } post.Rate.Start() post.NowConn = 0 s.Clients.Store(post.Id, post) if post.Id > int(s.ClientIncreaseId) { s.ClientIncreaseId = int32(post.Id) } }) } func (s *JsonDb) LoadHostFromJsonFile() { loadSyncMapFromFile(s.HostFilePath, func(v string) { var err error post := new(Host) if json.Unmarshal([]byte(v), &post) != nil { return } if post.Client, err = s.GetClient(post.Client.Id); err != nil { return } s.Hosts.Store(post.Id, post) if post.Id > int(s.HostIncreaseId) { s.HostIncreaseId = int32(post.Id) } }) } func (s *JsonDb) GetClient(id int) (c *Client, err error) { if v, ok := s.Clients.Load(id); ok { c = v.(*Client) return } err = errors.New("未找到客户端") return } var hostLock sync.Mutex func (s *JsonDb) StoreHostToJsonFile() { hostLock.Lock() storeSyncMapToFile(s.Hosts, s.HostFilePath) hostLock.Unlock() } var taskLock sync.Mutex func (s *JsonDb) StoreTasksToJsonFile() { taskLock.Lock() storeSyncMapToFile(s.Tasks, s.TaskFilePath) taskLock.Unlock() } var clientLock sync.Mutex func (s *JsonDb) StoreClientsToJsonFile() { clientLock.Lock() storeSyncMapToFile(s.Clients, s.ClientFilePath) clientLock.Unlock() } func (s *JsonDb) GetClientId() int32 { return atomic.AddInt32(&s.ClientIncreaseId, 1) } func (s *JsonDb) GetTaskId() int32 { return atomic.AddInt32(&s.TaskIncreaseId, 1) } func (s *JsonDb) GetHostId() int32 { return atomic.AddInt32(&s.HostIncreaseId, 1) } func loadSyncMapFromFile(filePath string, f func(value string)) { b, err := common.ReadAllFromFile(filePath) if err != nil { panic(err) } for _, v := range strings.Split(string(b), "\n"+common.CONN_DATA_SEQ) { f(v) } } func storeSyncMapToFile(m sync.Map, filePath string) { file, err := os.Create(filePath + ".tmp") // first create a temporary file to store if err != nil { panic(err) } m.Range(func(key, value interface{}) bool { var b []byte var err error switch value.(type) { case *Tunnel: obj := value.(*Tunnel) if obj.NoStore { return true } b, err = json.Marshal(obj) case *Host: obj := value.(*Host) if obj.NoStore { return true } b, err = json.Marshal(obj) case *Client: obj := value.(*Client) if obj.NoStore { return true } b, err = json.Marshal(obj) default: return true } if err != nil { return true } _, err = file.Write(b) if err != nil { panic(err) } _, err = file.Write([]byte("\n" + common.CONN_DATA_SEQ)) if err != nil { panic(err) } return true }) _ = file.Sync() _ = file.Close() // must close file first, then rename it err = os.Rename(filePath+".tmp", filePath) if err != nil { logs.Error(err, "store to file err, data will lost") } // replace the file, maybe provides atomic operation } ================================================ FILE: lib/file/obj.go ================================================ package file import ( "strings" "sync" "sync/atomic" "time" "ehang.io/nps/lib/rate" "github.com/pkg/errors" ) type Flow struct { ExportFlow int64 InletFlow int64 FlowLimit int64 sync.RWMutex } func (s *Flow) Add(in, out int64) { s.Lock() defer s.Unlock() s.InletFlow += int64(in) s.ExportFlow += int64(out) } type Config struct { U string P string Compress bool Crypt bool } type Client struct { Cnf *Config Id int //id VerifyKey string //verify key Addr string //the ip of client Remark string //remark Status bool //is allow connect IsConnect bool //is the client connect RateLimit int //rate /kb Flow *Flow //flow setting Rate *rate.Rate //rate limit NoStore bool //no store to file NoDisplay bool //no display on web MaxConn int //the max connection num of client allow NowConn int32 //the connection num of now WebUserName string //the username of web login WebPassword string //the password of web login ConfigConnAllow bool //is allow connected by config file MaxTunnelNum int Version string sync.RWMutex } func NewClient(vKey string, noStore bool, noDisplay bool) *Client { return &Client{ Cnf: new(Config), Id: 0, VerifyKey: vKey, Addr: "", Remark: "", Status: true, IsConnect: false, RateLimit: 0, Flow: new(Flow), Rate: nil, NoStore: noStore, RWMutex: sync.RWMutex{}, NoDisplay: noDisplay, } } func (s *Client) CutConn() { atomic.AddInt32(&s.NowConn, 1) } func (s *Client) AddConn() { atomic.AddInt32(&s.NowConn, -1) } func (s *Client) GetConn() bool { if s.MaxConn == 0 || int(s.NowConn) < s.MaxConn { s.CutConn() return true } return false } func (s *Client) HasTunnel(t *Tunnel) (exist bool) { GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*Tunnel) if v.Client.Id == s.Id && v.Port == t.Port && t.Port != 0 { exist = true return false } return true }) return } func (s *Client) GetTunnelNum() (num int) { GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*Tunnel) if v.Client.Id == s.Id { num++ } return true }) return } func (s *Client) HasHost(h *Host) bool { var has bool GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*Host) if v.Client.Id == s.Id && v.Host == h.Host && h.Location == v.Location { has = true return false } return true }) return has } type Tunnel struct { Id int Port int ServerIp string Mode string Status bool RunStatus bool Client *Client Ports string Flow *Flow Password string Remark string TargetAddr string NoStore bool LocalPath string StripPre string Target *Target MultiAccount *MultiAccount Health sync.RWMutex } type Health struct { HealthCheckTimeout int HealthMaxFail int HealthCheckInterval int HealthNextTime time.Time HealthMap map[string]int HttpHealthUrl string HealthRemoveArr []string HealthCheckType string HealthCheckTarget string sync.RWMutex } type Host struct { Id int Host string //host HeaderChange string //header change HostChange string //host change Location string //url router Remark string //remark Scheme string //http https all CertFilePath string KeyFilePath string NoStore bool IsClose bool Flow *Flow Client *Client Target *Target //目标 Health `json:"-"` sync.RWMutex } type Target struct { nowIndex int TargetStr string TargetArr []string LocalProxy bool sync.RWMutex } type MultiAccount struct { AccountMap map[string]string // multi account and pwd } func (s *Target) GetRandomTarget() (string, error) { if s.TargetArr == nil { s.TargetArr = strings.Split(s.TargetStr, "\n") } if len(s.TargetArr) == 1 { return s.TargetArr[0], nil } if len(s.TargetArr) == 0 { return "", errors.New("all inward-bending targets are offline") } s.Lock() defer s.Unlock() if s.nowIndex >= len(s.TargetArr)-1 { s.nowIndex = -1 } s.nowIndex++ return s.TargetArr[s.nowIndex], nil } ================================================ FILE: lib/file/sort.go ================================================ package file import ( "reflect" "sort" "sync" ) // A data structure to hold a key/value pair. type Pair struct { key string //sort key cId int order string clientFlow *Flow } // A slice of Pairs that implements sort.Interface to sort by Value. type PairList []*Pair func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p PairList) Len() int { return len(p) } func (p PairList) Less(i, j int) bool { if p[i].order == "desc" { return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() < reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int() } return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() > reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int() } // A function to turn a map into a PairList, then sort and return it. func sortClientByKey(m sync.Map, sortKey, order string) (res []int) { p := make(PairList, 0) m.Range(func(key, value interface{}) bool { p = append(p, &Pair{sortKey, value.(*Client).Id, order, value.(*Client).Flow}) return true }) sort.Sort(p) for _, v := range p { res = append(res, v.cId) } return } ================================================ FILE: lib/goroutine/pool.go ================================================ package goroutine import ( "ehang.io/nps/lib/common" "ehang.io/nps/lib/file" "github.com/panjf2000/ants/v2" "io" "net" "sync" ) type connGroup struct { src io.ReadWriteCloser dst io.ReadWriteCloser wg *sync.WaitGroup n *int64 } func newConnGroup(dst, src io.ReadWriteCloser, wg *sync.WaitGroup, n *int64) connGroup { return connGroup{ src: src, dst: dst, wg: wg, n: n, } } func copyConnGroup(group interface{}) { cg, ok := group.(connGroup) if !ok { return } var err error *cg.n, err = common.CopyBuffer(cg.dst, cg.src) if err != nil { cg.src.Close() cg.dst.Close() //logs.Warn("close npc by copy from nps", err, c.connId) } cg.wg.Done() } type Conns struct { conn1 io.ReadWriteCloser // mux connection conn2 net.Conn // outside connection flow *file.Flow wg *sync.WaitGroup } func NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow, wg *sync.WaitGroup) Conns { return Conns{ conn1: c1, conn2: c2, flow: flow, wg: wg, } } func copyConns(group interface{}) { conns := group.(Conns) wg := new(sync.WaitGroup) wg.Add(2) var in, out int64 _ = connCopyPool.Invoke(newConnGroup(conns.conn1, conns.conn2, wg, &in)) // outside to mux : incoming _ = connCopyPool.Invoke(newConnGroup(conns.conn2, conns.conn1, wg, &out)) // mux to outside : outgoing wg.Wait() if conns.flow != nil { conns.flow.Add(in, out) } conns.wg.Done() } var connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false)) var CopyConnsPool, _ = ants.NewPoolWithFunc(100000, copyConns, ants.WithNonblocking(false)) ================================================ FILE: lib/install/install.go ================================================ package install import ( "ehang.io/nps/lib/common" "encoding/json" "errors" "fmt" "github.com/c4milo/unpackit" "io" "io/ioutil" "log" "net/http" "os" "path/filepath" "runtime" "strings" ) // Keep it in sync with the template from service_sysv_linux.go file // Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt const SysvScript = `#!/bin/sh # For RedHat and cousins: # chkconfig: - 99 01 # description: {{.Description}} # processname: {{.Path}} ### BEGIN INIT INFO # Provides: {{.Path}} # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: {{.DisplayName}} # Description: {{.Description}} ### END INIT INFO cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}" name=$(basename $(readlink -f $0)) pid_file="/var/run/$name.pid" stdout_log="/var/log/$name.log" stderr_log="/var/log/$name.err" [ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name get_pid() { cat "$pid_file" } is_running() { [ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1 } case "$1" in start) if is_running; then echo "Already started" else echo "Starting $name" {{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}} $cmd >> "$stdout_log" 2>> "$stderr_log" & echo $! > "$pid_file" if ! is_running; then echo "Unable to start, see $stdout_log and $stderr_log" exit 1 fi fi ;; stop) if is_running; then echo -n "Stopping $name.." kill $(get_pid) for i in $(seq 1 10) do if ! is_running; then break fi echo -n "." sleep 1 done echo if is_running; then echo "Not stopped; may still be shutting down or shutdown may have failed" exit 1 else echo "Stopped" if [ -f "$pid_file" ]; then rm "$pid_file" fi fi else echo "Not running" fi ;; restart) $0 stop if is_running; then echo "Unable to stop, will not attempt to start" exit 1 fi $0 start ;; status) if is_running; then echo "Running" else echo "Stopped" exit 1 fi ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0 ` const SystemdScript = `[Unit] Description={{.Description}} ConditionFileIsExecutable={{.Path|cmdEscape}} {{range $i, $dep := .Dependencies}} {{$dep}} {{end}} [Service] LimitNOFILE=65536 StartLimitInterval=5 StartLimitBurst=10 ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}} {{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}} {{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}} {{if .UserName}}User={{.UserName}}{{end}} {{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}} {{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}} {{if and .LogOutput .HasOutputFileSupport -}} StandardOutput=file:/var/log/{{.Name}}.out StandardError=file:/var/log/{{.Name}}.err {{- end}} Restart=always RestartSec=120 [Install] WantedBy=multi-user.target ` func UpdateNps() { destPath := downloadLatest("server") //复制文件到对应目录 copyStaticFile(destPath, "nps") fmt.Println("Update completed, please restart") } func UpdateNpc() { destPath := downloadLatest("client") //复制文件到对应目录 copyStaticFile(destPath, "npc") fmt.Println("Update completed, please restart") } type release struct { TagName string `json:"tag_name"` } func downloadLatest(bin string) string { // get version data, err := http.Get("https://api.github.com/repos/ehang-io/nps/releases/latest") if err != nil { log.Fatal(err.Error()) } b, err := ioutil.ReadAll(data.Body) if err != nil { log.Fatal(err) } rl := new(release) json.Unmarshal(b, &rl) version := rl.TagName fmt.Println("the latest version is", version) filename := runtime.GOOS + "_" + runtime.GOARCH + "_" + bin + ".tar.gz" // download latest package downloadUrl := fmt.Sprintf("https://ehang.io/nps/releases/download/%s/%s", version, filename) fmt.Println("download package from ", downloadUrl) resp, err := http.Get(downloadUrl) if err != nil { log.Fatal(err.Error()) } destPath, err := unpackit.Unpack(resp.Body, "") if err != nil { log.Fatal(err) } if bin == "server" { destPath = strings.Replace(destPath, "/web", "", -1) destPath = strings.Replace(destPath, `\web`, "", -1) destPath = strings.Replace(destPath, "/views", "", -1) destPath = strings.Replace(destPath, `\views`, "", -1) } else { destPath = strings.Replace(destPath, `\conf`, "", -1) destPath = strings.Replace(destPath, "/conf", "", -1) } return destPath } func copyStaticFile(srcPath, bin string) string { path := common.GetInstallPath() if bin == "nps" { //复制文件到对应目录 if err := CopyDir(filepath.Join(srcPath, "web", "views"), filepath.Join(path, "web", "views")); err != nil { log.Fatalln(err) } chMod(filepath.Join(path, "web", "views"), 0766) if err := CopyDir(filepath.Join(srcPath, "web", "static"), filepath.Join(path, "web", "static")); err != nil { log.Fatalln(err) } chMod(filepath.Join(path, "web", "static"), 0766) } binPath, _ := filepath.Abs(os.Args[0]) if !common.IsWindows() { if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin); err != nil { if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin); err != nil { log.Fatalln(err) } else { copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin+"-update") chMod("/usr/local/bin/"+bin+"-update", 0755) binPath = "/usr/local/bin/" + bin } } else { copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin+"-update") chMod("/usr/bin/"+bin+"-update", 0755) binPath = "/usr/bin/" + bin } } else { copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+"-update.exe")) copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+".exe")) } chMod(binPath, 0755) return binPath } func InstallNpc() { path := common.GetInstallPath() if !common.FileExists(path) { err := os.Mkdir(path, 0755) if err != nil { log.Fatal(err) } } copyStaticFile(common.GetAppPath(), "npc") } func InstallNps() string { path := common.GetInstallPath() if common.FileExists(path) { MkidrDirAll(path, "web/static", "web/views") } else { MkidrDirAll(path, "conf", "web/static", "web/views") // not copy config if the config file is exist if err := CopyDir(filepath.Join(common.GetAppPath(), "conf"), filepath.Join(path, "conf")); err != nil { log.Fatalln(err) } chMod(filepath.Join(path, "conf"), 0766) } binPath := copyStaticFile(common.GetAppPath(), "nps") log.Println("install ok!") log.Println("Static files and configuration files in the current directory will be useless") log.Println("The new configuration file is located in", path, "you can edit them") if !common.IsWindows() { log.Println(`You can start with: nps start|stop|restart|uninstall|update or nps-update update anywhere!`) } else { log.Println(`You can copy executable files to any directory and start working with: nps.exe start|stop|restart|uninstall|update or nps-update.exe update now!`) } chMod(common.GetLogPath(), 0777) return binPath } func MkidrDirAll(path string, v ...string) { for _, item := range v { if err := os.MkdirAll(filepath.Join(path, item), 0755); err != nil { log.Fatalf("Failed to create directory %s error:%s", path, err.Error()) } } } func CopyDir(srcPath string, destPath string) error { //检测目录正确性 if srcInfo, err := os.Stat(srcPath); err != nil { fmt.Println(err.Error()) return err } else { if !srcInfo.IsDir() { e := errors.New("SrcPath is not the right directory!") return e } } if destInfo, err := os.Stat(destPath); err != nil { return err } else { if !destInfo.IsDir() { e := errors.New("DestInfo is not the right directory!") return e } } err := filepath.Walk(srcPath, func(path string, f os.FileInfo, err error) error { if f == nil { return err } if !f.IsDir() { destNewPath := strings.Replace(path, srcPath, destPath, -1) log.Println("copy file ::" + path + " to " + destNewPath) copyFile(path, destNewPath) if !common.IsWindows() { chMod(destNewPath, 0766) } } return nil }) return err } //生成目录并拷贝文件 func copyFile(src, dest string) (w int64, err error) { srcFile, err := os.Open(src) if err != nil { return } defer srcFile.Close() //分割path目录 destSplitPathDirs := strings.Split(dest, string(filepath.Separator)) //检测时候存在目录 destSplitPath := "" for index, dir := range destSplitPathDirs { if index < len(destSplitPathDirs)-1 { destSplitPath = destSplitPath + dir + string(filepath.Separator) b, _ := pathExists(destSplitPath) if b == false { log.Println("mkdir:" + destSplitPath) //创建目录 err := os.Mkdir(destSplitPath, os.ModePerm) if err != nil { log.Fatalln(err) } } } } dstFile, err := os.Create(dest) if err != nil { return } defer dstFile.Close() return io.Copy(dstFile, srcFile) } //检测文件夹路径时候存在 func pathExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } func chMod(name string, mode os.FileMode) { if !common.IsWindows() { os.Chmod(name, mode) } } ================================================ FILE: lib/pmux/pconn.go ================================================ package pmux import ( "net" "time" ) type PortConn struct { Conn net.Conn rs []byte readMore bool start int } func newPortConn(conn net.Conn, rs []byte, readMore bool) *PortConn { return &PortConn{ Conn: conn, rs: rs, readMore: readMore, } } func (pConn *PortConn) Read(b []byte) (n int, err error) { if len(b) < len(pConn.rs)-pConn.start { defer func() { pConn.start = pConn.start + len(b) }() return copy(b, pConn.rs), nil } if pConn.start < len(pConn.rs) { defer func() { pConn.start = len(pConn.rs) }() n = copy(b, pConn.rs[pConn.start:]) if !pConn.readMore { return } } var n2 = 0 n2, err = pConn.Conn.Read(b[n:]) n = n + n2 return } func (pConn *PortConn) Write(b []byte) (n int, err error) { return pConn.Conn.Write(b) } func (pConn *PortConn) Close() error { return pConn.Conn.Close() } func (pConn *PortConn) LocalAddr() net.Addr { return pConn.Conn.LocalAddr() } func (pConn *PortConn) RemoteAddr() net.Addr { return pConn.Conn.RemoteAddr() } func (pConn *PortConn) SetDeadline(t time.Time) error { return pConn.Conn.SetDeadline(t) } func (pConn *PortConn) SetReadDeadline(t time.Time) error { return pConn.Conn.SetReadDeadline(t) } func (pConn *PortConn) SetWriteDeadline(t time.Time) error { return pConn.Conn.SetWriteDeadline(t) } ================================================ FILE: lib/pmux/plistener.go ================================================ package pmux import ( "errors" "net" ) type PortListener struct { net.Listener connCh chan *PortConn addr net.Addr isClose bool } func NewPortListener(connCh chan *PortConn, addr net.Addr) *PortListener { return &PortListener{ connCh: connCh, addr: addr, } } func (pListener *PortListener) Accept() (net.Conn, error) { if pListener.isClose { return nil, errors.New("the listener has closed") } conn := <-pListener.connCh if conn != nil { return conn, nil } return nil, errors.New("the listener has closed") } func (pListener *PortListener) Close() error { //close if pListener.isClose { return errors.New("the listener has closed") } pListener.isClose = true return nil } func (pListener *PortListener) Addr() net.Addr { return pListener.addr } ================================================ FILE: lib/pmux/pmux.go ================================================ // This module is used for port reuse // Distinguish client, web manager , HTTP and HTTPS according to the difference of protocol package pmux import ( "bufio" "bytes" "io" "net" "os" "strconv" "strings" "time" "ehang.io/nps/lib/common" "github.com/astaxie/beego/logs" "github.com/pkg/errors" ) const ( HTTP_GET = 716984 HTTP_POST = 807983 HTTP_HEAD = 726965 HTTP_PUT = 808585 HTTP_DELETE = 686976 HTTP_CONNECT = 677978 HTTP_OPTIONS = 798084 HTTP_TRACE = 848265 CLIENT = 848384 ACCEPT_TIME_OUT = 10 ) type PortMux struct { net.Listener port int isClose bool managerHost string clientConn chan *PortConn httpConn chan *PortConn httpsConn chan *PortConn managerConn chan *PortConn } func NewPortMux(port int, managerHost string) *PortMux { pMux := &PortMux{ managerHost: managerHost, port: port, clientConn: make(chan *PortConn), httpConn: make(chan *PortConn), httpsConn: make(chan *PortConn), managerConn: make(chan *PortConn), } pMux.Start() return pMux } func (pMux *PortMux) Start() error { // Port multiplexing is based on TCP only tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+strconv.Itoa(pMux.port)) if err != nil { return err } pMux.Listener, err = net.ListenTCP("tcp", tcpAddr) if err != nil { logs.Error(err) os.Exit(0) } go func() { for { conn, err := pMux.Listener.Accept() if err != nil { logs.Warn(err) //close pMux.Close() } go pMux.process(conn) } }() return nil } func (pMux *PortMux) process(conn net.Conn) { // Recognition according to different signs // read 3 byte buf := make([]byte, 3) if n, err := io.ReadFull(conn, buf); err != nil || n != 3 { return } var ch chan *PortConn var rs []byte var buffer bytes.Buffer var readMore = false switch common.BytesToNum(buf) { case HTTP_CONNECT, HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_OPTIONS, HTTP_POST, HTTP_PUT, HTTP_TRACE: //http and manager buffer.Reset() r := bufio.NewReader(conn) buffer.Write(buf) for { b, _, err := r.ReadLine() if err != nil { logs.Warn("read line error", err.Error()) conn.Close() break } buffer.Write(b) buffer.Write([]byte("\r\n")) if strings.Index(string(b), "Host:") == 0 || strings.Index(string(b), "host:") == 0 { // Remove host and space effects str := strings.Replace(string(b), "Host:", "", -1) str = strings.Replace(str, "host:", "", -1) str = strings.TrimSpace(str) // Determine whether it is the same as the manager domain name if common.GetIpByAddr(str) == pMux.managerHost { ch = pMux.managerConn } else { ch = pMux.httpConn } b, _ := r.Peek(r.Buffered()) buffer.Write(b) rs = buffer.Bytes() break } } case CLIENT: // client connection ch = pMux.clientConn default: // https readMore = true ch = pMux.httpsConn } if len(rs) == 0 { rs = buf } timer := time.NewTimer(ACCEPT_TIME_OUT) select { case <-timer.C: case ch <- newPortConn(conn, rs, readMore): } } func (pMux *PortMux) Close() error { if pMux.isClose { return errors.New("the port pmux has closed") } pMux.isClose = true close(pMux.clientConn) close(pMux.httpsConn) close(pMux.httpConn) close(pMux.managerConn) return pMux.Listener.Close() } func (pMux *PortMux) GetClientListener() net.Listener { return NewPortListener(pMux.clientConn, pMux.Listener.Addr()) } func (pMux *PortMux) GetHttpListener() net.Listener { return NewPortListener(pMux.httpConn, pMux.Listener.Addr()) } func (pMux *PortMux) GetHttpsListener() net.Listener { return NewPortListener(pMux.httpsConn, pMux.Listener.Addr()) } func (pMux *PortMux) GetManagerListener() net.Listener { return NewPortListener(pMux.managerConn, pMux.Listener.Addr()) } ================================================ FILE: lib/pmux/pmux_test.go ================================================ package pmux import ( "testing" "time" "github.com/astaxie/beego/logs" ) func TestPortMux_Close(t *testing.T) { logs.Reset() logs.EnableFuncCallDepth(true) logs.SetLogFuncCallDepth(3) pMux := NewPortMux(8888, "Ds") go func() { if pMux.Start() != nil { logs.Warn("Error") } }() time.Sleep(time.Second * 3) go func() { l := pMux.GetHttpListener() conn, err := l.Accept() logs.Warn(conn, err) }() go func() { l := pMux.GetHttpListener() conn, err := l.Accept() logs.Warn(conn, err) }() go func() { l := pMux.GetHttpListener() conn, err := l.Accept() logs.Warn(conn, err) }() l := pMux.GetHttpListener() conn, err := l.Accept() logs.Warn(conn, err) } ================================================ FILE: lib/rate/conn.go ================================================ package rate import ( "io" ) type rateConn struct { conn io.ReadWriteCloser rate *Rate } func NewRateConn(conn io.ReadWriteCloser, rate *Rate) io.ReadWriteCloser { return &rateConn{ conn: conn, rate: rate, } } func (s *rateConn) Read(b []byte) (n int, err error) { n, err = s.conn.Read(b) if s.rate != nil { s.rate.Get(int64(n)) } return } func (s *rateConn) Write(b []byte) (n int, err error) { n, err = s.conn.Write(b) if s.rate != nil { s.rate.Get(int64(n)) } return } func (s *rateConn) Close() error { return s.conn.Close() } ================================================ FILE: lib/rate/rate.go ================================================ package rate import ( "sync/atomic" "time" ) type Rate struct { bucketSize int64 bucketSurplusSize int64 bucketAddSize int64 stopChan chan bool NowRate int64 } func NewRate(addSize int64) *Rate { return &Rate{ bucketSize: addSize * 2, bucketSurplusSize: 0, bucketAddSize: addSize, stopChan: make(chan bool), } } func (s *Rate) Start() { go s.session() } func (s *Rate) add(size int64) { if res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize { atomic.AddInt64(&s.bucketSurplusSize, res) return } atomic.AddInt64(&s.bucketSurplusSize, size) } //回桶 func (s *Rate) ReturnBucket(size int64) { s.add(size) } //停止 func (s *Rate) Stop() { s.stopChan <- true } func (s *Rate) Get(size int64) { if s.bucketSurplusSize >= size { atomic.AddInt64(&s.bucketSurplusSize, -size) return } ticker := time.NewTicker(time.Millisecond * 100) for { select { case <-ticker.C: if s.bucketSurplusSize >= size { atomic.AddInt64(&s.bucketSurplusSize, -size) ticker.Stop() return } } } } func (s *Rate) session() { ticker := time.NewTicker(time.Second * 1) for { select { case <-ticker.C: if rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 { s.NowRate = rs } else { s.NowRate = s.bucketSize - s.bucketSurplusSize } s.add(s.bucketAddSize) case <-s.stopChan: ticker.Stop() return } } } ================================================ FILE: lib/sheap/heap.go ================================================ package sheap type IntHeap []int64 func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *IntHeap) Push(x interface{}) { // Push and Pop use pointer receivers because they modify the slice's length, // not just its contents. *h = append(*h, x.(int64)) } func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } ================================================ FILE: lib/version/version.go ================================================ package version const VERSION = "0.26.10" // Compulsory minimum version, Minimum downward compatibility to this version func GetVersion() string { return "0.26.0" } ================================================ FILE: server/connection/connection.go ================================================ package connection import ( "net" "os" "strconv" "ehang.io/nps/lib/pmux" "github.com/astaxie/beego" "github.com/astaxie/beego/logs" ) var pMux *pmux.PortMux var bridgePort string var httpsPort string var httpPort string var webPort string func InitConnectionService() { bridgePort = beego.AppConfig.String("bridge_port") httpsPort = beego.AppConfig.String("https_proxy_port") httpPort = beego.AppConfig.String("http_proxy_port") webPort = beego.AppConfig.String("web_port") if httpPort == bridgePort || httpsPort == bridgePort || webPort == bridgePort { port, err := strconv.Atoi(bridgePort) if err != nil { logs.Error(err) os.Exit(0) } pMux = pmux.NewPortMux(port, beego.AppConfig.String("web_host")) } } func GetBridgeListener(tp string) (net.Listener, error) { logs.Info("server start, the bridge type is %s, the bridge port is %s", tp, bridgePort) var p int var err error if p, err = strconv.Atoi(bridgePort); err != nil { return nil, err } if pMux != nil { return pMux.GetClientListener(), nil } return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(beego.AppConfig.String("bridge_ip")), p, ""}) } func GetHttpListener() (net.Listener, error) { if pMux != nil && httpPort == bridgePort { logs.Info("start http listener, port is", bridgePort) return pMux.GetHttpListener(), nil } logs.Info("start http listener, port is", httpPort) return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpPort) } func GetHttpsListener() (net.Listener, error) { if pMux != nil && httpsPort == bridgePort { logs.Info("start https listener, port is", bridgePort) return pMux.GetHttpsListener(), nil } logs.Info("start https listener, port is", httpsPort) return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpsPort) } func GetWebManagerListener() (net.Listener, error) { if pMux != nil && webPort == bridgePort { logs.Info("Web management start, access port is", bridgePort) return pMux.GetManagerListener(), nil } logs.Info("web management start, access port is", webPort) return getTcpListener(beego.AppConfig.String("web_ip"), webPort) } func getTcpListener(ip, p string) (net.Listener, error) { port, err := strconv.Atoi(p) if err != nil { logs.Error(err) os.Exit(0) } if ip == "" { ip = "0.0.0.0" } return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(ip), port, ""}) } ================================================ FILE: server/proxy/base.go ================================================ package proxy import ( "errors" "net" "net/http" "sync" "ehang.io/nps/bridge" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/file" "github.com/astaxie/beego/logs" ) type Service interface { Start() error Close() error } type NetBridge interface { SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) } //BaseServer struct type BaseServer struct { id int bridge NetBridge task *file.Tunnel errorContent []byte sync.Mutex } func NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer { return &BaseServer{ bridge: bridge, task: task, errorContent: nil, Mutex: sync.Mutex{}, } } //add the flow func (s *BaseServer) FlowAdd(in, out int64) { s.Lock() defer s.Unlock() s.task.Flow.ExportFlow += out s.task.Flow.InletFlow += in } //change the flow func (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) { s.Lock() defer s.Unlock() host.Flow.ExportFlow += out host.Flow.InletFlow += in } //write fail bytes to the connection func (s *BaseServer) writeConnFail(c net.Conn) { c.Write([]byte(common.ConnectionFailBytes)) c.Write(s.errorContent) } //auth check func (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error { if u != "" && p != "" && !common.CheckAuth(r, u, p) { c.Write([]byte(common.UnauthorizedBytes)) c.Close() return errors.New("401 Unauthorized") } return nil } //check flow limit of the client ,and decrease the allow num of client func (s *BaseServer) CheckFlowAndConnNum(client *file.Client) error { if client.Flow.FlowLimit > 0 && (client.Flow.FlowLimit<<20) < (client.Flow.ExportFlow+client.Flow.InletFlow) { return errors.New("Traffic exceeded") } if !client.GetConn() { return errors.New("Connections exceed the current client limit") } return nil } //create a new connection and start bytes copying func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, rb []byte, tp string, f func(), flow *file.Flow, localProxy bool) error { link := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String(), localProxy) if target, err := s.bridge.SendLinkInfo(client.Id, link, s.task); err != nil { logs.Warn("get connection from client id %d error %s", client.Id, err.Error()) c.Close() return err } else { if f != nil { f() } conn.CopyWaitGroup(target, c.Conn, link.Crypt, link.Compress, client.Rate, flow, true, rb) } return nil } ================================================ FILE: server/proxy/http.go ================================================ package proxy import ( "bufio" "crypto/tls" "io" "net" "net/http" "net/http/httputil" "os" "path/filepath" "strconv" "strings" "sync" "ehang.io/nps/bridge" "ehang.io/nps/lib/cache" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/file" "ehang.io/nps/server/connection" "github.com/astaxie/beego/logs" ) type httpServer struct { BaseServer httpPort int httpsPort int httpServer *http.Server httpsServer *http.Server httpsListener net.Listener useCache bool addOrigin bool cache *cache.Cache cacheLen int } func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int, addOrigin bool) *httpServer { httpServer := &httpServer{ BaseServer: BaseServer{ task: c, bridge: bridge, Mutex: sync.Mutex{}, }, httpPort: httpPort, httpsPort: httpsPort, useCache: useCache, cacheLen: cacheLen, addOrigin: addOrigin, } if useCache { httpServer.cache = cache.New(cacheLen) } return httpServer } func (s *httpServer) Start() error { var err error if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil { s.errorContent = []byte("nps 404") } if s.httpPort > 0 { s.httpServer = s.NewServer(s.httpPort, "http") go func() { l, err := connection.GetHttpListener() if err != nil { logs.Error(err) os.Exit(0) } err = s.httpServer.Serve(l) if err != nil { logs.Error(err) os.Exit(0) } }() } if s.httpsPort > 0 { s.httpsServer = s.NewServer(s.httpsPort, "https") go func() { s.httpsListener, err = connection.GetHttpsListener() if err != nil { logs.Error(err) os.Exit(0) } logs.Error(NewHttpsServer(s.httpsListener, s.bridge, s.useCache, s.cacheLen).Start()) }() } return nil } func (s *httpServer) Close() error { if s.httpsListener != nil { s.httpsListener.Close() } if s.httpsServer != nil { s.httpsServer.Close() } if s.httpServer != nil { s.httpServer.Close() } return nil } func (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) { hijacker, ok := w.(http.Hijacker) if !ok { http.Error(w, "Hijacking not supported", http.StatusInternalServerError) return } c, _, err := hijacker.Hijack() if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) } s.handleHttp(conn.NewConn(c), r) } func (s *httpServer) handleHttp(c *conn.Conn, r *http.Request) { var ( host *file.Host target net.Conn err error connClient io.ReadWriteCloser scheme = r.URL.Scheme lk *conn.Link targetAddr string lenConn *conn.LenConn isReset bool wg sync.WaitGroup ) defer func() { if connClient != nil { connClient.Close() } else { s.writeConnFail(c.Conn) } c.Close() }() reset: if isReset { host.Client.AddConn() } if host, err = file.GetDb().GetInfoByHost(r.Host, r); err != nil { logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI) return } if err := s.CheckFlowAndConnNum(host.Client); err != nil { logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error()) return } if !isReset { defer host.Client.AddConn() } if err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil { logs.Warn("auth error", err, r.RemoteAddr) return } if targetAddr, err = host.Target.GetRandomTarget(); err != nil { logs.Warn(err.Error()) return } lk = conn.NewLink("http", targetAddr, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr, host.Target.LocalProxy) if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, nil); err != nil { logs.Notice("connect to target %s error %s", lk.Host, err) return } connClient = conn.GetConn(target, lk.Crypt, lk.Compress, host.Client.Rate, true) //read from inc-client go func() { wg.Add(1) isReset = false defer connClient.Close() defer func() { wg.Done() if !isReset { c.Close() } }() for { if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil || r == nil { // if there got broken pipe, http.ReadResponse will get a nil return } else { //if the cache is start and the response is in the extension,store the response to the cache list if s.useCache && r.URL != nil && strings.Contains(r.URL.Path, ".") { b, err := httputil.DumpResponse(resp, true) if err != nil { return } c.Write(b) host.Flow.Add(0, int64(len(b))) s.cache.Add(filepath.Join(host.Host, r.URL.Path), b) } else { lenConn := conn.NewLenConn(c) if err := resp.Write(lenConn); err != nil { logs.Error(err) return } host.Flow.Add(0, int64(lenConn.Len)) } } } }() for { //if the cache start and the request is in the cache list, return the cache if s.useCache { if v, ok := s.cache.Get(filepath.Join(host.Host, r.URL.Path)); ok { n, err := c.Write(v.([]byte)) if err != nil { break } logs.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()) host.Flow.Add(0, int64(n)) //if return cache and does not create a new conn with client and Connection is not set or close, close the connection. if strings.ToLower(r.Header.Get("Connection")) == "close" || strings.ToLower(r.Header.Get("Connection")) == "" { break } goto readReq } } //change the host and header and set proxy setting common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String(), s.addOrigin) logs.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) //write lenConn = conn.NewLenConn(connClient) if err := r.Write(lenConn); err != nil { logs.Error(err) break } host.Flow.Add(int64(lenConn.Len), 0) readReq: //read req from connection if r, err = http.ReadRequest(bufio.NewReader(c)); err != nil { break } r.URL.Scheme = scheme //What happened ,Why one character less??? r.Method = resetReqMethod(r.Method) if hostTmp, err := file.GetDb().GetInfoByHost(r.Host, r); err != nil { logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI) break } else if host != hostTmp { host = hostTmp isReset = true connClient.Close() goto reset } } wg.Wait() } func resetReqMethod(method string) string { if method == "ET" { return "GET" } if method == "OST" { return "POST" } return method } func (s *httpServer) NewServer(port int, scheme string) *http.Server { return &http.Server{ Addr: ":" + strconv.Itoa(port), Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { r.URL.Scheme = scheme s.handleTunneling(w, r) }), // Disable HTTP/2. TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } } ================================================ FILE: server/proxy/https.go ================================================ package proxy import ( "net" "net/http" "net/url" "sync" "ehang.io/nps/lib/cache" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/file" "github.com/astaxie/beego" "github.com/astaxie/beego/logs" "github.com/pkg/errors" ) type HttpsServer struct { httpServer listener net.Listener httpsListenerMap sync.Map } func NewHttpsServer(l net.Listener, bridge NetBridge, useCache bool, cacheLen int) *HttpsServer { https := &HttpsServer{listener: l} https.bridge = bridge https.useCache = useCache if useCache { https.cache = cache.New(cacheLen) } return https } //start https server func (https *HttpsServer) Start() error { if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b { conn.Accept(https.listener, func(c net.Conn) { https.handleHttps(c) }) } else { //start the default listener certFile := beego.AppConfig.String("https_default_cert_file") keyFile := beego.AppConfig.String("https_default_key_file") if common.FileExists(certFile) && common.FileExists(keyFile) { l := NewHttpsListener(https.listener) https.NewHttps(l, certFile, keyFile) https.httpsListenerMap.Store("default", l) } conn.Accept(https.listener, func(c net.Conn) { serverName, rb := GetServerNameFromClientHello(c) //if the clientHello does not contains sni ,use the default ssl certificate if serverName == "" { serverName = "default" } var l *HttpsListener if v, ok := https.httpsListenerMap.Load(serverName); ok { l = v.(*HttpsListener) } else { r := buildHttpsRequest(serverName) if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil { c.Close() logs.Notice("the url %s can't be parsed!,remote addr %s", serverName, c.RemoteAddr().String()) return } else { if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) { //if the host cert file or key file is not set ,use the default file if v, ok := https.httpsListenerMap.Load("default"); ok { l = v.(*HttpsListener) } else { c.Close() logs.Error("the key %s cert %s file is not exist", host.KeyFilePath, host.CertFilePath) return } } else { l = NewHttpsListener(https.listener) https.NewHttps(l, host.CertFilePath, host.KeyFilePath) https.httpsListenerMap.Store(serverName, l) } } } acceptConn := conn.NewConn(c) acceptConn.Rb = rb l.acceptConn <- acceptConn }) } return nil } // close func (https *HttpsServer) Close() error { return https.listener.Close() } // new https server by cert and key file func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) { go func() { logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile)) }() } //handle the https which is just proxy to other client func (https *HttpsServer) handleHttps(c net.Conn) { hostName, rb := GetServerNameFromClientHello(c) var targetAddr string r := buildHttpsRequest(hostName) var host *file.Host var err error if host, err = file.GetDb().GetInfoByHost(hostName, r); err != nil { c.Close() logs.Notice("the url %s can't be parsed!", hostName) return } if err := https.CheckFlowAndConnNum(host.Client); err != nil { logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error()) c.Close() return } defer host.Client.AddConn() if err = https.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil { logs.Warn("auth error", err, r.RemoteAddr) return } if targetAddr, err = host.Target.GetRandomTarget(); err != nil { logs.Warn(err.Error()) } logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String()) https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy) } type HttpsListener struct { acceptConn chan *conn.Conn parentListener net.Listener } // https listener func NewHttpsListener(l net.Listener) *HttpsListener { return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)} } // accept func (httpsListener *HttpsListener) Accept() (net.Conn, error) { httpsConn := <-httpsListener.acceptConn if httpsConn == nil { return nil, errors.New("get connection error") } return httpsConn, nil } // close func (httpsListener *HttpsListener) Close() error { return nil } // addr func (httpsListener *HttpsListener) Addr() net.Addr { return httpsListener.parentListener.Addr() } // get server name from connection by read client hello bytes func GetServerNameFromClientHello(c net.Conn) (string, []byte) { buf := make([]byte, 4096) data := make([]byte, 4096) n, err := c.Read(buf) if err != nil { return "", nil } if n < 42 { return "", nil } copy(data, buf[:n]) clientHello := new(crypt.ClientHelloMsg) clientHello.Unmarshal(data[5:n]) return clientHello.GetServerName(), buf[:n] } // build https request func buildHttpsRequest(hostName string) *http.Request { r := new(http.Request) r.RequestURI = "/" r.URL = new(url.URL) r.URL.Scheme = "https" r.Host = hostName return r } ================================================ FILE: server/proxy/p2p.go ================================================ package proxy import ( "net" "strings" "time" "ehang.io/nps/lib/common" "github.com/astaxie/beego/logs" ) type P2PServer struct { BaseServer p2pPort int p2p map[string]*p2p listener *net.UDPConn } type p2p struct { visitorAddr *net.UDPAddr providerAddr *net.UDPAddr } func NewP2PServer(p2pPort int) *P2PServer { return &P2PServer{ p2pPort: p2pPort, p2p: make(map[string]*p2p), } } func (s *P2PServer) Start() error { logs.Info("start p2p server port", s.p2pPort) var err error s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), s.p2pPort, ""}) if err != nil { return err } for { buf := common.BufPoolUdp.Get().([]byte) n, addr, err := s.listener.ReadFromUDP(buf) if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { break } continue } go s.handleP2P(addr, string(buf[:n])) } return nil } func (s *P2PServer) handleP2P(addr *net.UDPAddr, str string) { var ( v *p2p ok bool ) arr := strings.Split(str, common.CONN_DATA_SEQ) if len(arr) < 2 { return } if v, ok = s.p2p[arr[0]]; !ok { v = new(p2p) s.p2p[arr[0]] = v } logs.Trace("new p2p connection ,role %s , password %s ,local address %s", arr[1], arr[0], addr.String()) if arr[1] == common.WORK_P2P_VISITOR { v.visitorAddr = addr for i := 20; i > 0; i-- { if v.providerAddr != nil { s.listener.WriteTo([]byte(v.providerAddr.String()), v.visitorAddr) s.listener.WriteTo([]byte(v.visitorAddr.String()), v.providerAddr) break } time.Sleep(time.Second) } delete(s.p2p, arr[0]) } else { v.providerAddr = addr } } ================================================ FILE: server/proxy/socks5.go ================================================ package proxy import ( "encoding/binary" "errors" "io" "net" "strconv" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/file" "github.com/astaxie/beego/logs" ) const ( ipV4 = 1 domainName = 3 ipV6 = 4 connectMethod = 1 bindMethod = 2 associateMethod = 3 // The maximum packet size of any udp Associate packet, based on ethernet's max size, // minus the IP and UDP headers. IPv4 has a 20 byte header, UDP adds an // additional 4 bytes. This is a total overhead of 24 bytes. Ethernet's // max packet size is 1500 bytes, 1500 - 24 = 1476. maxUDPPacketSize = 1476 ) const ( succeeded uint8 = iota serverFailure notAllowed networkUnreachable hostUnreachable connectionRefused ttlExpired commandNotSupported addrTypeNotSupported ) const ( UserPassAuth = uint8(2) userAuthVersion = uint8(1) authSuccess = uint8(0) authFailure = uint8(1) ) type Sock5ModeServer struct { BaseServer listener net.Listener } //req func (s *Sock5ModeServer) handleRequest(c net.Conn) { /* The SOCKS request is formed as follows: +----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ */ header := make([]byte, 3) _, err := io.ReadFull(c, header) if err != nil { logs.Warn("illegal request", err) c.Close() return } switch header[1] { case connectMethod: s.handleConnect(c) case bindMethod: s.handleBind(c) case associateMethod: s.handleUDP(c) default: s.sendReply(c, commandNotSupported) c.Close() } } //reply func (s *Sock5ModeServer) sendReply(c net.Conn, rep uint8) { reply := []byte{ 5, rep, 0, 1, } localAddr := c.LocalAddr().String() localHost, localPort, _ := net.SplitHostPort(localAddr) ipBytes := net.ParseIP(localHost).To4() nPort, _ := strconv.Atoi(localPort) reply = append(reply, ipBytes...) portBytes := make([]byte, 2) binary.BigEndian.PutUint16(portBytes, uint16(nPort)) reply = append(reply, portBytes...) c.Write(reply) } //do conn func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) { addrType := make([]byte, 1) c.Read(addrType) var host string switch addrType[0] { case ipV4: ipv4 := make(net.IP, net.IPv4len) c.Read(ipv4) host = ipv4.String() case ipV6: ipv6 := make(net.IP, net.IPv6len) c.Read(ipv6) host = ipv6.String() case domainName: var domainLen uint8 binary.Read(c, binary.BigEndian, &domainLen) domain := make([]byte, domainLen) c.Read(domain) host = string(domain) default: s.sendReply(c, addrTypeNotSupported) return } var port uint16 binary.Read(c, binary.BigEndian, &port) // connect to host addr := net.JoinHostPort(host, strconv.Itoa(int(port))) var ltype string if command == associateMethod { ltype = common.CONN_UDP } else { ltype = common.CONN_TCP } s.DealClient(conn.NewConn(c), s.task.Client, addr, nil, ltype, func() { s.sendReply(c, succeeded) }, s.task.Flow, s.task.Target.LocalProxy) return } //conn func (s *Sock5ModeServer) handleConnect(c net.Conn) { s.doConnect(c, connectMethod) } // passive mode func (s *Sock5ModeServer) handleBind(c net.Conn) { } func (s *Sock5ModeServer) sendUdpReply(writeConn net.Conn, c net.Conn, rep uint8, serverIp string) { reply := []byte{ 5, rep, 0, 1, } localHost, localPort, _ := net.SplitHostPort(c.LocalAddr().String()) localHost = serverIp ipBytes := net.ParseIP(localHost).To4() nPort, _ := strconv.Atoi(localPort) reply = append(reply, ipBytes...) portBytes := make([]byte, 2) binary.BigEndian.PutUint16(portBytes, uint16(nPort)) reply = append(reply, portBytes...) writeConn.Write(reply) } func (s *Sock5ModeServer) handleUDP(c net.Conn) { defer c.Close() addrType := make([]byte, 1) c.Read(addrType) var host string switch addrType[0] { case ipV4: ipv4 := make(net.IP, net.IPv4len) c.Read(ipv4) host = ipv4.String() case ipV6: ipv6 := make(net.IP, net.IPv6len) c.Read(ipv6) host = ipv6.String() case domainName: var domainLen uint8 binary.Read(c, binary.BigEndian, &domainLen) domain := make([]byte, domainLen) c.Read(domain) host = string(domain) default: s.sendReply(c, addrTypeNotSupported) return } //读取端口 var port uint16 binary.Read(c, binary.BigEndian, &port) logs.Warn(host, string(port)) replyAddr, err := net.ResolveUDPAddr("udp", s.task.ServerIp+":0") if err != nil { logs.Error("build local reply addr error", err) return } reply, err := net.ListenUDP("udp", replyAddr) if err != nil { s.sendReply(c, addrTypeNotSupported) logs.Error("listen local reply udp port error") return } // reply the local addr s.sendUdpReply(c, reply, succeeded, common.GetServerIpByClientIp(c.RemoteAddr().(*net.TCPAddr).IP)) defer reply.Close() // new a tunnel to client link := conn.NewLink("udp5", "", s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String(), false) target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task) if err != nil { logs.Warn("get connection from client id %d error %s", s.task.Client.Id, err.Error()) return } var clientAddr net.Addr // copy buffer go func() { b := common.BufPoolUdp.Get().([]byte) defer common.BufPoolUdp.Put(b) defer c.Close() for { n, laddr, err := reply.ReadFrom(b) if err != nil { logs.Error("read data from %s err %s", reply.LocalAddr().String(), err.Error()) return } if clientAddr == nil { clientAddr = laddr } if _, err := target.Write(b[:n]); err != nil { logs.Error("write data to client error", err.Error()) return } } }() go func() { var l int32 b := common.BufPoolUdp.Get().([]byte) defer common.BufPoolUdp.Put(b) defer c.Close() for { if err := binary.Read(target, binary.LittleEndian, &l); err != nil || l >= common.PoolSizeUdp || l <= 0 { logs.Warn("read len bytes error", err.Error()) return } binary.Read(target, binary.LittleEndian, b[:l]) if err != nil { logs.Warn("read data form client error", err.Error()) return } if _, err := reply.WriteTo(b[:l], clientAddr); err != nil { logs.Warn("write data to user ", err.Error()) return } } }() b := common.BufPoolUdp.Get().([]byte) defer common.BufPoolUdp.Put(b) defer target.Close() for { _, err := c.Read(b) if err != nil { c.Close() return } } } //new conn func (s *Sock5ModeServer) handleConn(c net.Conn) { buf := make([]byte, 2) if _, err := io.ReadFull(c, buf); err != nil { logs.Warn("negotiation err", err) c.Close() return } if version := buf[0]; version != 5 { logs.Warn("only support socks5, request from: ", c.RemoteAddr()) c.Close() return } nMethods := buf[1] methods := make([]byte, nMethods) if len, err := c.Read(methods); len != int(nMethods) || err != nil { logs.Warn("wrong method") c.Close() return } if (s.task.Client.Cnf.U != "" && s.task.Client.Cnf.P != "") || (s.task.MultiAccount != nil && len(s.task.MultiAccount.AccountMap) > 0) { buf[1] = UserPassAuth c.Write(buf) if err := s.Auth(c); err != nil { c.Close() logs.Warn("Validation failed:", err) return } } else { buf[1] = 0 c.Write(buf) } s.handleRequest(c) } //socks5 auth func (s *Sock5ModeServer) Auth(c net.Conn) error { header := []byte{0, 0} if _, err := io.ReadAtLeast(c, header, 2); err != nil { return err } if header[0] != userAuthVersion { return errors.New("验证方式不被支持") } userLen := int(header[1]) user := make([]byte, userLen) if _, err := io.ReadAtLeast(c, user, userLen); err != nil { return err } if _, err := c.Read(header[:1]); err != nil { return errors.New("密码长度获取错误") } passLen := int(header[0]) pass := make([]byte, passLen) if _, err := io.ReadAtLeast(c, pass, passLen); err != nil { return err } var U, P string if s.task.MultiAccount != nil { // enable multi user auth U = string(user) var ok bool P, ok = s.task.MultiAccount.AccountMap[U] if !ok { return errors.New("验证不通过") } } else { U = s.task.Client.Cnf.U P = s.task.Client.Cnf.P } if string(user) == U && string(pass) == P { if _, err := c.Write([]byte{userAuthVersion, authSuccess}); err != nil { return err } return nil } else { if _, err := c.Write([]byte{userAuthVersion, authFailure}); err != nil { return err } return errors.New("验证不通过") } } //start func (s *Sock5ModeServer) Start() error { return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) { if err := s.CheckFlowAndConnNum(s.task.Client); err != nil { logs.Warn("client id %d, task id %d, error %s, when socks5 connection", s.task.Client.Id, s.task.Id, err.Error()) c.Close() return } logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr()) s.handleConn(c) s.task.Client.AddConn() }, &s.listener) } //new func NewSock5ModeServer(bridge NetBridge, task *file.Tunnel) *Sock5ModeServer { s := new(Sock5ModeServer) s.bridge = bridge s.task = task return s } //close func (s *Sock5ModeServer) Close() error { return s.listener.Close() } ================================================ FILE: server/proxy/tcp.go ================================================ package proxy import ( "errors" "net" "net/http" "path/filepath" "strconv" "ehang.io/nps/bridge" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/file" "ehang.io/nps/server/connection" "github.com/astaxie/beego" "github.com/astaxie/beego/logs" ) type TunnelModeServer struct { BaseServer process process listener net.Listener } //tcp|http|host func NewTunnelModeServer(process process, bridge NetBridge, task *file.Tunnel) *TunnelModeServer { s := new(TunnelModeServer) s.bridge = bridge s.process = process s.task = task return s } //开始 func (s *TunnelModeServer) Start() error { return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) { if err := s.CheckFlowAndConnNum(s.task.Client); err != nil { logs.Warn("client id %d, task id %d,error %s, when tcp connection", s.task.Client.Id, s.task.Id, err.Error()) c.Close() return } logs.Trace("new tcp connection,local port %d,client %d,remote address %s", s.task.Port, s.task.Client.Id, c.RemoteAddr()) s.process(conn.NewConn(c), s) s.task.Client.AddConn() }, &s.listener) } //close func (s *TunnelModeServer) Close() error { return s.listener.Close() } //web管理方式 type WebServer struct { BaseServer } //开始 func (s *WebServer) Start() error { p, _ := beego.AppConfig.Int("web_port") if p == 0 { stop := make(chan struct{}) <-stop } beego.BConfig.WebConfig.Session.SessionOn = true beego.SetStaticPath(beego.AppConfig.String("web_base_url")+"/static", filepath.Join(common.GetRunPath(), "web", "static")) beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views")) err := errors.New("Web management startup failure ") var l net.Listener if l, err = connection.GetWebManagerListener(); err == nil { beego.InitBeforeHTTPRun() if beego.AppConfig.String("web_open_ssl") == "true" { keyPath := beego.AppConfig.String("web_key_file") certPath := beego.AppConfig.String("web_cert_file") err = http.ServeTLS(l, beego.BeeApp.Handlers, certPath, keyPath) } else { err = http.Serve(l, beego.BeeApp.Handlers) } } else { logs.Error(err) } return err } func (s *WebServer) Close() error { return nil } //new func NewWebServer(bridge *bridge.Bridge) *WebServer { s := new(WebServer) s.bridge = bridge return s } type process func(c *conn.Conn, s *TunnelModeServer) error //tcp proxy func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error { targetAddr, err := s.task.Target.GetRandomTarget() if err != nil { c.Close() logs.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()) return err } return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy) } //http proxy func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error { _, addr, rb, err, r := c.GetHost() if err != nil { c.Close() logs.Info(err) return err } if r.Method == "CONNECT" { c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) rb = nil } if err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil { return err } return s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy) } ================================================ FILE: server/proxy/transport.go ================================================ // +build !windows package proxy import ( "net" "strconv" "syscall" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" ) func HandleTrans(c *conn.Conn, s *TunnelModeServer) error { if addr, err := getAddress(c.Conn); err != nil { return err } else { return s.DealClient(c, s.task.Client, addr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy) } } const SO_ORIGINAL_DST = 80 func getAddress(conn net.Conn) (string, error) { sysrawconn, f := conn.(syscall.Conn) if !f { return "", nil } rawConn, err := sysrawconn.SyscallConn() if err != nil { return "", nil } var ip string var port uint16 err = rawConn.Control(func(fd uintptr) { addr, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST) if err != nil { return } ip = net.IP(addr.Multiaddr[4:8]).String() port = uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3]) }) return ip + ":" + strconv.Itoa(int(port)), nil } ================================================ FILE: server/proxy/transport_windows.go ================================================ // +build windows package proxy import ( "ehang.io/nps/lib/conn" ) func HandleTrans(c *conn.Conn, s *TunnelModeServer) error { return nil } ================================================ FILE: server/proxy/udp.go ================================================ package proxy import ( "io" "net" "strings" "sync" "time" "ehang.io/nps/bridge" "ehang.io/nps/lib/common" "ehang.io/nps/lib/conn" "ehang.io/nps/lib/file" "github.com/astaxie/beego/logs" ) type UdpModeServer struct { BaseServer addrMap sync.Map listener *net.UDPConn } func NewUdpModeServer(bridge *bridge.Bridge, task *file.Tunnel) *UdpModeServer { s := new(UdpModeServer) s.bridge = bridge s.task = task return s } //开始 func (s *UdpModeServer) Start() error { var err error if s.task.ServerIp == "" { s.task.ServerIp = "0.0.0.0" } s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP(s.task.ServerIp), s.task.Port, ""}) if err != nil { return err } for { buf := common.BufPoolUdp.Get().([]byte) n, addr, err := s.listener.ReadFromUDP(buf) if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { break } continue } logs.Trace("New udp connection,client %d,remote address %s", s.task.Client.Id, addr) go s.process(addr, buf[:n]) } return nil } func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) { if v, ok := s.addrMap.Load(addr.String()); ok { clientConn, ok := v.(io.ReadWriteCloser) if ok { clientConn.Write(data) s.task.Flow.Add(int64(len(data)), 0) } } else { if err := s.CheckFlowAndConnNum(s.task.Client); err != nil { logs.Warn("client id %d, task id %d,error %s, when udp connection", s.task.Client.Id, s.task.Id, err.Error()) return } defer s.task.Client.AddConn() link := 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) if clientConn, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task); err != nil { return } else { target := conn.GetConn(clientConn, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, nil, true) s.addrMap.Store(addr.String(), target) defer target.Close() target.Write(data) buf := common.BufPoolUdp.Get().([]byte) defer common.BufPoolUdp.Put(buf) s.task.Flow.Add(int64(len(data)), 0) for { clientConn.SetReadDeadline(time.Now().Add(time.Minute * 10)) if n, err := target.Read(buf); err != nil { s.addrMap.Delete(addr.String()) logs.Warn(err) return } else { s.listener.WriteTo(buf[:n], addr) s.task.Flow.Add(0, int64(n)) } } } } } func (s *UdpModeServer) Close() error { return s.listener.Close() } ================================================ FILE: server/server.go ================================================ package server import ( "ehang.io/nps/lib/version" "errors" "math" "os" "strconv" "strings" "sync" "time" "ehang.io/nps/bridge" "ehang.io/nps/lib/common" "ehang.io/nps/lib/file" "ehang.io/nps/server/proxy" "ehang.io/nps/server/tool" "github.com/astaxie/beego" "github.com/astaxie/beego/logs" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/load" "github.com/shirou/gopsutil/v3/mem" "github.com/shirou/gopsutil/v3/net" ) var ( Bridge *bridge.Bridge RunList sync.Map //map[int]interface{} ) func init() { RunList = sync.Map{} } //init task from db func InitFromCsv() { //Add a public password if vkey := beego.AppConfig.String("public_vkey"); vkey != "" { c := file.NewClient(vkey, true, true) file.GetDb().NewClient(c) RunList.Store(c.Id, nil) //RunList[c.Id] = nil } //Initialize services in server-side files file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { if value.(*file.Tunnel).Status { AddTask(value.(*file.Tunnel)) } return true }) } //get bridge command func DealBridgeTask() { for { select { case t := <-Bridge.OpenTask: AddTask(t) case t := <-Bridge.CloseTask: StopServer(t.Id) case id := <-Bridge.CloseClient: DelTunnelAndHostByClientId(id, true) if v, ok := file.GetDb().JsonDb.Clients.Load(id); ok { if v.(*file.Client).NoStore { file.GetDb().DelClient(id) } } case tunnel := <-Bridge.OpenTask: StartTask(tunnel.Id) case s := <-Bridge.SecretChan: logs.Trace("New secret connection, addr", s.Conn.Conn.RemoteAddr()) if t := file.GetDb().GetTaskByMd5Password(s.Password); t != nil { if t.Status { go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target.TargetStr, nil, common.CONN_TCP, nil, t.Flow, t.Target.LocalProxy) } else { s.Conn.Close() logs.Trace("This key %s cannot be processed,status is close", s.Password) } } else { logs.Trace("This key %s cannot be processed", s.Password) s.Conn.Close() } } } } //start a new server func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string, bridgeDisconnect int) { Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ip_limit")), RunList, bridgeDisconnect) go func() { if err := Bridge.StartTunnel(); err != nil { logs.Error("start server bridge error", err) os.Exit(0) } }() if p, err := beego.AppConfig.Int("p2p_port"); err == nil { go proxy.NewP2PServer(p).Start() go proxy.NewP2PServer(p + 1).Start() go proxy.NewP2PServer(p + 2).Start() } go DealBridgeTask() go dealClientFlow() if svr := NewMode(Bridge, cnf); svr != nil { if err := svr.Start(); err != nil { logs.Error(err) } RunList.Store(cnf.Id, svr) //RunList[cnf.Id] = svr } else { logs.Error("Incorrect startup mode %s", cnf.Mode) } } func dealClientFlow() { ticker := time.NewTicker(time.Minute) defer ticker.Stop() for { select { case <-ticker.C: dealClientData() } } } //new a server by mode name func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service { var service proxy.Service switch c.Mode { case "tcp", "file": service = proxy.NewTunnelModeServer(proxy.ProcessTunnel, Bridge, c) case "socks5": service = proxy.NewSock5ModeServer(Bridge, c) case "httpProxy": service = proxy.NewTunnelModeServer(proxy.ProcessHttp, Bridge, c) case "tcpTrans": service = proxy.NewTunnelModeServer(proxy.HandleTrans, Bridge, c) case "udp": service = proxy.NewUdpModeServer(Bridge, c) case "webServer": InitFromCsv() t := &file.Tunnel{ Port: 0, Mode: "httpHostServer", Status: true, } AddTask(t) service = proxy.NewWebServer(Bridge) case "httpHostServer": httpPort, _ := beego.AppConfig.Int("http_proxy_port") httpsPort, _ := beego.AppConfig.Int("https_proxy_port") useCache, _ := beego.AppConfig.Bool("http_cache") cacheLen, _ := beego.AppConfig.Int("http_cache_length") addOrigin, _ := beego.AppConfig.Bool("http_add_origin_header") service = proxy.NewHttp(Bridge, c, httpPort, httpsPort, useCache, cacheLen, addOrigin) } return service } //stop server func StopServer(id int) error { //if v, ok := RunList[id]; ok { if v, ok := RunList.Load(id); ok { if svr, ok := v.(proxy.Service); ok { if err := svr.Close(); err != nil { return err } logs.Info("stop server id %d", id) } else { logs.Warn("stop server id %d error", id) } if t, err := file.GetDb().GetTask(id); err != nil { return err } else { t.Status = false file.GetDb().UpdateTask(t) } //delete(RunList, id) RunList.Delete(id) return nil } return errors.New("task is not running") } //add task func AddTask(t *file.Tunnel) error { if t.Mode == "secret" || t.Mode == "p2p" { logs.Info("secret task %s start ", t.Remark) //RunList[t.Id] = nil RunList.Store(t.Id, nil) return nil } if b := tool.TestServerPort(t.Port, t.Mode); !b && t.Mode != "httpHostServer" { logs.Error("taskId %d start error port %d open failed", t.Id, t.Port) return errors.New("the port open error") } if minute, err := beego.AppConfig.Int("flow_store_interval"); err == nil && minute > 0 { go flowSession(time.Minute * time.Duration(minute)) } if svr := NewMode(Bridge, t); svr != nil { logs.Info("tunnel task %s start mode:%s port %d", t.Remark, t.Mode, t.Port) //RunList[t.Id] = svr RunList.Store(t.Id, svr) go func() { if err := svr.Start(); err != nil { logs.Error("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err) //delete(RunList, t.Id) RunList.Delete(t.Id) return } }() } else { return errors.New("the mode is not correct") } return nil } //start task func StartTask(id int) error { if t, err := file.GetDb().GetTask(id); err != nil { return err } else { AddTask(t) t.Status = true file.GetDb().UpdateTask(t) } return nil } //delete task func DelTask(id int) error { //if _, ok := RunList[id]; ok { if _, ok := RunList.Load(id); ok { if err := StopServer(id); err != nil { return err } } return file.GetDb().DelTask(id) } //get task list by page num func GetTunnel(start, length int, typeVal string, clientId int, search string) ([]*file.Tunnel, int) { list := make([]*file.Tunnel, 0) var cnt int keys := file.GetMapKeys(file.GetDb().JsonDb.Tasks, false, "", "") for _, key := range keys { if value, ok := file.GetDb().JsonDb.Tasks.Load(key); ok { v := value.(*file.Tunnel) if (typeVal != "" && v.Mode != typeVal || (clientId != 0 && v.Client.Id != clientId)) || (typeVal == "" && clientId != v.Client.Id) { continue } if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || v.Port == common.GetIntNoErrByStr(search) || strings.Contains(v.Password, search) || strings.Contains(v.Remark, search)) { continue } cnt++ if _, ok := Bridge.Client.Load(v.Client.Id); ok { v.Client.IsConnect = true } else { v.Client.IsConnect = false } if start--; start < 0 { if length--; length >= 0 { //if _, ok := RunList[v.Id]; ok { if _, ok := RunList.Load(v.Id); ok { v.RunStatus = true } else { v.RunStatus = false } list = append(list, v) } } } } return list, cnt } //get client list func GetClientList(start, length int, search, sort, order string, clientId int) (list []*file.Client, cnt int) { list, cnt = file.GetDb().GetClientList(start, length, search, sort, order, clientId) dealClientData() return } func dealClientData() { file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*file.Client) if vv, ok := Bridge.Client.Load(v.Id); ok { v.IsConnect = true v.Version = vv.(*bridge.Client).Version } else { v.IsConnect = false } v.Flow.InletFlow = 0 v.Flow.ExportFlow = 0 file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { h := value.(*file.Host) if h.Client.Id == v.Id { v.Flow.InletFlow += h.Flow.InletFlow v.Flow.ExportFlow += h.Flow.ExportFlow } return true }) file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { t := value.(*file.Tunnel) if t.Client.Id == v.Id { v.Flow.InletFlow += t.Flow.InletFlow v.Flow.ExportFlow += t.Flow.ExportFlow } return true }) return true }) return } //delete all host and tasks by client id func DelTunnelAndHostByClientId(clientId int, justDelNoStore bool) { var ids []int file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*file.Tunnel) if justDelNoStore && !v.NoStore { return true } if v.Client.Id == clientId { ids = append(ids, v.Id) } return true }) for _, id := range ids { DelTask(id) } ids = ids[:0] file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool { v := value.(*file.Host) if justDelNoStore && !v.NoStore { return true } if v.Client.Id == clientId { ids = append(ids, v.Id) } return true }) for _, id := range ids { file.GetDb().DelHost(id) } } //close the client func DelClientConnect(clientId int) { Bridge.DelClient(clientId) } func GetDashboardData() map[string]interface{} { data := make(map[string]interface{}) data["version"] = version.VERSION data["hostCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Hosts) data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients) if beego.AppConfig.String("public_vkey") != "" { //remove public vkey data["clientCount"] = data["clientCount"].(int) - 1 } dealClientData() c := 0 var in, out int64 file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*file.Client) if v.IsConnect { c += 1 } in += v.Flow.InletFlow out += v.Flow.ExportFlow return true }) data["clientOnlineCount"] = c data["inletFlowCount"] = int(in) data["exportFlowCount"] = int(out) var tcp, udp, secret, socks5, p2p, http int file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { switch value.(*file.Tunnel).Mode { case "tcp": tcp += 1 case "socks5": socks5 += 1 case "httpProxy": http += 1 case "udp": udp += 1 case "p2p": p2p += 1 case "secret": secret += 1 } return true }) data["tcpC"] = tcp data["udpCount"] = udp data["socks5Count"] = socks5 data["httpProxyCount"] = http data["secretCount"] = secret data["p2pCount"] = p2p data["bridgeType"] = beego.AppConfig.String("bridge_type") data["httpProxyPort"] = beego.AppConfig.String("http_proxy_port") data["httpsProxyPort"] = beego.AppConfig.String("https_proxy_port") data["ipLimit"] = beego.AppConfig.String("ip_limit") data["flowStoreInterval"] = beego.AppConfig.String("flow_store_interval") data["serverIp"] = beego.AppConfig.String("p2p_ip") data["p2pPort"] = beego.AppConfig.String("p2p_port") data["logLevel"] = beego.AppConfig.String("log_level") tcpCount := 0 file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool { tcpCount += int(value.(*file.Client).NowConn) return true }) data["tcpCount"] = tcpCount cpuPercet, _ := cpu.Percent(0, true) var cpuAll float64 for _, v := range cpuPercet { cpuAll += v } loads, _ := load.Avg() data["load"] = loads.String() data["cpu"] = math.Round(cpuAll / float64(len(cpuPercet))) swap, _ := mem.SwapMemory() data["swap_mem"] = math.Round(swap.UsedPercent) vir, _ := mem.VirtualMemory() data["virtual_mem"] = math.Round(vir.UsedPercent) conn, _ := net.ProtoCounters(nil) io1, _ := net.IOCounters(false) time.Sleep(time.Millisecond * 500) io2, _ := net.IOCounters(false) if len(io2) > 0 && len(io1) > 0 { data["io_send"] = (io2[0].BytesSent - io1[0].BytesSent) * 2 data["io_recv"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2 } for _, v := range conn { data[v.Protocol] = v.Stats["CurrEstab"] } //chart var fg int if len(tool.ServerStatus) >= 10 { fg = len(tool.ServerStatus) / 10 for i := 0; i <= 9; i++ { data["sys"+strconv.Itoa(i+1)] = tool.ServerStatus[i*fg] } } return data } func flowSession(m time.Duration) { ticker := time.NewTicker(m) defer ticker.Stop() for { select { case <-ticker.C: file.GetDb().JsonDb.StoreHostToJsonFile() file.GetDb().JsonDb.StoreTasksToJsonFile() file.GetDb().JsonDb.StoreClientsToJsonFile() } } } ================================================ FILE: server/test/test.go ================================================ package test import ( "log" "path/filepath" "strconv" "ehang.io/nps/lib/common" "ehang.io/nps/lib/file" "github.com/astaxie/beego" ) func TestServerConfig() { var postTcpArr []int var postUdpArr []int file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { v := value.(*file.Tunnel) if v.Mode == "udp" { isInArr(&postUdpArr, v.Port, v.Remark, "udp") } else if v.Port != 0 { isInArr(&postTcpArr, v.Port, v.Remark, "tcp") } return true }) p, err := beego.AppConfig.Int("web_port") if err != nil { log.Fatalln("Getting web management port error :", err) } else { isInArr(&postTcpArr, p, "Web Management port", "tcp") } if p := beego.AppConfig.String("bridge_port"); p != "" { if port, err := strconv.Atoi(p); err != nil { log.Fatalln("get Server and client communication portserror:", err) } else if beego.AppConfig.String("bridge_type") == "kcp" { isInArr(&postUdpArr, port, "Server and client communication ports", "udp") } else { isInArr(&postTcpArr, port, "Server and client communication ports", "tcp") } } if p := beego.AppConfig.String("httpProxyPort"); p != "" { if port, err := strconv.Atoi(p); err != nil { log.Fatalln("get http port error:", err) } else { isInArr(&postTcpArr, port, "https port", "tcp") } } if p := beego.AppConfig.String("https_proxy_port"); p != "" { if b, err := beego.AppConfig.Bool("https_just_proxy"); !(err == nil && b) { if port, err := strconv.Atoi(p); err != nil { log.Fatalln("get https port error", err) } else { if beego.AppConfig.String("pemPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("pemPath"))) { log.Fatalf("ssl certFile %s is not exist", beego.AppConfig.String("pemPath")) } if beego.AppConfig.String("keyPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("keyPath"))) { log.Fatalf("ssl keyFile %s is not exist", beego.AppConfig.String("pemPath")) } isInArr(&postTcpArr, port, "http port", "tcp") } } } } func isInArr(arr *[]int, val int, remark string, tp string) { for _, v := range *arr { if v == val { log.Fatalf("the port %d is reused,remark: %s", val, remark) } } if tp == "tcp" { if !common.TestTcpPort(val) { log.Fatalf("open the %d port error ,remark: %s", val, remark) } } else { if !common.TestUdpPort(val) { log.Fatalf("open the %d port error ,remark: %s", val, remark) } } *arr = append(*arr, val) return } ================================================ FILE: server/tool/utils.go ================================================ package tool import ( "math" "strconv" "time" "ehang.io/nps/lib/common" "github.com/astaxie/beego" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/load" "github.com/shirou/gopsutil/v3/mem" "github.com/shirou/gopsutil/v3/net" ) var ( ports []int ServerStatus []map[string]interface{} ) func StartSystemInfo() { if b, err := beego.AppConfig.Bool("system_info_display"); err == nil && b { ServerStatus = make([]map[string]interface{}, 0, 1500) go getSeverStatus() } } func InitAllowPort() { p := beego.AppConfig.String("allow_ports") ports = common.GetPorts(p) } func TestServerPort(p int, m string) (b bool) { if m == "p2p" || m == "secret" { return true } if p > 65535 || p < 0 { return false } if len(ports) != 0 { if !common.InIntArr(ports, p) { return false } } if m == "udp" { b = common.TestUdpPort(p) } else { b = common.TestTcpPort(p) } return } func getSeverStatus() { for { if len(ServerStatus) < 10 { time.Sleep(time.Second) } else { time.Sleep(time.Minute) } cpuPercet, _ := cpu.Percent(0, true) var cpuAll float64 for _, v := range cpuPercet { cpuAll += v } m := make(map[string]interface{}) loads, _ := load.Avg() m["load1"] = loads.Load1 m["load5"] = loads.Load5 m["load15"] = loads.Load15 m["cpu"] = math.Round(cpuAll / float64(len(cpuPercet))) swap, _ := mem.SwapMemory() m["swap_mem"] = math.Round(swap.UsedPercent) vir, _ := mem.VirtualMemory() m["virtual_mem"] = math.Round(vir.UsedPercent) conn, _ := net.ProtoCounters(nil) io1, _ := net.IOCounters(false) time.Sleep(time.Millisecond * 500) io2, _ := net.IOCounters(false) if len(io2) > 0 && len(io1) > 0 { m["io_send"] = (io2[0].BytesSent - io1[0].BytesSent) * 2 m["io_recv"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2 } t := time.Now() m["time"] = strconv.Itoa(t.Hour()) + ":" + strconv.Itoa(t.Minute()) + ":" + strconv.Itoa(t.Second()) for _, v := range conn { m[v.Protocol] = v.Stats["CurrEstab"] } if len(ServerStatus) >= 1440 { ServerStatus = ServerStatus[1:] } ServerStatus = append(ServerStatus, m) } } ================================================ FILE: web/controllers/auth.go ================================================ package controllers import ( "encoding/hex" "time" "ehang.io/nps/lib/crypt" "github.com/astaxie/beego" ) type AuthController struct { beego.Controller } func (s *AuthController) GetAuthKey() { m := make(map[string]interface{}) defer func() { s.Data["json"] = m s.ServeJSON() }() if cryptKey := beego.AppConfig.String("auth_crypt_key"); len(cryptKey) != 16 { m["status"] = 0 return } else { b, err := crypt.AesEncrypt([]byte(beego.AppConfig.String("auth_key")), []byte(cryptKey)) if err != nil { m["status"] = 0 return } m["status"] = 1 m["crypt_auth_key"] = hex.EncodeToString(b) m["crypt_type"] = "aes cbc" return } } func (s *AuthController) GetTime() { m := make(map[string]interface{}) m["time"] = time.Now().Unix() s.Data["json"] = m s.ServeJSON() } ================================================ FILE: web/controllers/base.go ================================================ package controllers import ( "html" "math" "strconv" "strings" "time" "ehang.io/nps/lib/common" "ehang.io/nps/lib/crypt" "ehang.io/nps/lib/file" "ehang.io/nps/server" "github.com/astaxie/beego" ) type BaseController struct { beego.Controller controllerName string actionName string } //初始化参数 func (s *BaseController) Prepare() { s.Data["web_base_url"] = beego.AppConfig.String("web_base_url") controllerName, actionName := s.GetControllerAndAction() s.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10]) s.actionName = strings.ToLower(actionName) // web api verify // param 1 is md5(authKey+Current timestamp) // param 2 is timestamp (It's limited to 20 seconds.) md5Key := s.getEscapeString("auth_key") timestamp := s.GetIntNoErr("timestamp") configKey := beego.AppConfig.String("auth_key") timeNowUnix := time.Now().Unix() if !(md5Key != "" && (math.Abs(float64(timeNowUnix-int64(timestamp))) <= 20) && (crypt.Md5(configKey+strconv.Itoa(timestamp)) == md5Key)) { if s.GetSession("auth") != true { s.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302) } } else { s.SetSession("isAdmin", true) s.Data["isAdmin"] = true } if s.GetSession("isAdmin") != nil && !s.GetSession("isAdmin").(bool) { s.Ctx.Input.SetData("client_id", s.GetSession("clientId").(int)) s.Ctx.Input.SetParam("client_id", strconv.Itoa(s.GetSession("clientId").(int))) s.Data["isAdmin"] = false s.Data["username"] = s.GetSession("username") s.CheckUserAuth() } else { s.Data["isAdmin"] = true } s.Data["https_just_proxy"], _ = beego.AppConfig.Bool("https_just_proxy") s.Data["allow_user_login"], _ = beego.AppConfig.Bool("allow_user_login") s.Data["allow_flow_limit"], _ = beego.AppConfig.Bool("allow_flow_limit") s.Data["allow_rate_limit"], _ = beego.AppConfig.Bool("allow_rate_limit") s.Data["allow_connection_num_limit"], _ = beego.AppConfig.Bool("allow_connection_num_limit") s.Data["allow_multi_ip"], _ = beego.AppConfig.Bool("allow_multi_ip") s.Data["system_info_display"], _ = beego.AppConfig.Bool("system_info_display") s.Data["allow_tunnel_num_limit"], _ = beego.AppConfig.Bool("allow_tunnel_num_limit") s.Data["allow_local_proxy"], _ = beego.AppConfig.Bool("allow_local_proxy") s.Data["allow_user_change_username"], _ = beego.AppConfig.Bool("allow_user_change_username") } //加载模板 func (s *BaseController) display(tpl ...string) { s.Data["web_base_url"] = beego.AppConfig.String("web_base_url") var tplname string if s.Data["menu"] == nil { s.Data["menu"] = s.actionName } if len(tpl) > 0 { tplname = strings.Join([]string{tpl[0], "html"}, ".") } else { tplname = s.controllerName + "/" + s.actionName + ".html" } ip := s.Ctx.Request.Host s.Data["ip"] = common.GetIpByAddr(ip) s.Data["bridgeType"] = beego.AppConfig.String("bridge_type") if common.IsWindows() { s.Data["win"] = ".exe" } s.Data["p"] = server.Bridge.TunnelPort s.Data["proxyPort"] = beego.AppConfig.String("hostPort") s.Layout = "public/layout.html" s.TplName = tplname } //错误 func (s *BaseController) error() { s.Data["web_base_url"] = beego.AppConfig.String("web_base_url") s.Layout = "public/layout.html" s.TplName = "public/error.html" } //getEscapeString func (s *BaseController) getEscapeString(key string) string { return html.EscapeString(s.GetString(key)) } //去掉没有err返回值的int func (s *BaseController) GetIntNoErr(key string, def ...int) int { strv := s.Ctx.Input.Query(key) if len(strv) == 0 && len(def) > 0 { return def[0] } val, _ := strconv.Atoi(strv) return val } //获取去掉错误的bool值 func (s *BaseController) GetBoolNoErr(key string, def ...bool) bool { strv := s.Ctx.Input.Query(key) if len(strv) == 0 && len(def) > 0 { return def[0] } val, _ := strconv.ParseBool(strv) return val } //ajax正确返回 func (s *BaseController) AjaxOk(str string) { s.Data["json"] = ajax(str, 1) s.ServeJSON() s.StopRun() } //ajax错误返回 func (s *BaseController) AjaxErr(str string) { s.Data["json"] = ajax(str, 0) s.ServeJSON() s.StopRun() } //组装ajax func ajax(str string, status int) map[string]interface{} { json := make(map[string]interface{}) json["status"] = status json["msg"] = str return json } //ajax table返回 func (s *BaseController) AjaxTable(list interface{}, cnt int, recordsTotal int, kwargs map[string]interface{}) { json := make(map[string]interface{}) json["rows"] = list json["total"] = recordsTotal if kwargs != nil { for k, v := range kwargs { if v != nil { json[k] = v } } } s.Data["json"] = json s.ServeJSON() s.StopRun() } //ajax table参数 func (s *BaseController) GetAjaxParams() (start, limit int) { return s.GetIntNoErr("offset"), s.GetIntNoErr("limit") } func (s *BaseController) SetInfo(name string) { s.Data["name"] = name } func (s *BaseController) SetType(name string) { s.Data["type"] = name } func (s *BaseController) CheckUserAuth() { if s.controllerName == "client" { if s.actionName == "add" { s.StopRun() return } if id := s.GetIntNoErr("id"); id != 0 { if id != s.GetSession("clientId").(int) { s.StopRun() return } } } if s.controllerName == "index" { if id := s.GetIntNoErr("id"); id != 0 { belong := false if strings.Contains(s.actionName, "h") { if v, ok := file.GetDb().JsonDb.Hosts.Load(id); ok { if v.(*file.Host).Client.Id == s.GetSession("clientId").(int) { belong = true } } } else { if v, ok := file.GetDb().JsonDb.Tasks.Load(id); ok { if v.(*file.Tunnel).Client.Id == s.GetSession("clientId").(int) { belong = true } } } if !belong { s.StopRun() } } } } ================================================ FILE: web/controllers/client.go ================================================ package controllers import ( "ehang.io/nps/lib/common" "ehang.io/nps/lib/file" "ehang.io/nps/lib/rate" "ehang.io/nps/server" "github.com/astaxie/beego" ) type ClientController struct { BaseController } func (s *ClientController) List() { if s.Ctx.Request.Method == "GET" { s.Data["menu"] = "client" s.SetInfo("client") s.display("client/list") return } start, length := s.GetAjaxParams() clientIdSession := s.GetSession("clientId") var clientId int if clientIdSession == nil { clientId = 0 } else { clientId = clientIdSession.(int) } list, cnt := server.GetClientList(start, length, s.getEscapeString("search"), s.getEscapeString("sort"), s.getEscapeString("order"), clientId) cmd := make(map[string]interface{}) ip := s.Ctx.Request.Host cmd["ip"] = common.GetIpByAddr(ip) cmd["bridgeType"] = beego.AppConfig.String("bridge_type") cmd["bridgePort"] = server.Bridge.TunnelPort s.AjaxTable(list, cnt, cnt, cmd) } //添加客户端 func (s *ClientController) Add() { if s.Ctx.Request.Method == "GET" { s.Data["menu"] = "client" s.SetInfo("add client") s.display() } else { t := &file.Client{ VerifyKey: s.getEscapeString("vkey"), Id: int(file.GetDb().JsonDb.GetClientId()), Status: true, Remark: s.getEscapeString("remark"), Cnf: &file.Config{ U: s.getEscapeString("u"), P: s.getEscapeString("p"), Compress: common.GetBoolByStr(s.getEscapeString("compress")), Crypt: s.GetBoolNoErr("crypt"), }, ConfigConnAllow: s.GetBoolNoErr("config_conn_allow"), RateLimit: s.GetIntNoErr("rate_limit"), MaxConn: s.GetIntNoErr("max_conn"), WebUserName: s.getEscapeString("web_username"), WebPassword: s.getEscapeString("web_password"), MaxTunnelNum: s.GetIntNoErr("max_tunnel"), Flow: &file.Flow{ ExportFlow: 0, InletFlow: 0, FlowLimit: int64(s.GetIntNoErr("flow_limit")), }, } if err := file.GetDb().NewClient(t); err != nil { s.AjaxErr(err.Error()) } s.AjaxOk("add success") } } func (s *ClientController) GetClient() { if s.Ctx.Request.Method == "POST" { id := s.GetIntNoErr("id") data := make(map[string]interface{}) if c, err := file.GetDb().GetClient(id); err != nil { data["code"] = 0 } else { data["code"] = 1 data["data"] = c } s.Data["json"] = data s.ServeJSON() } } //修改客户端 func (s *ClientController) Edit() { id := s.GetIntNoErr("id") if s.Ctx.Request.Method == "GET" { s.Data["menu"] = "client" if c, err := file.GetDb().GetClient(id); err != nil { s.error() } else { s.Data["c"] = c } s.SetInfo("edit client") s.display() } else { if c, err := file.GetDb().GetClient(id); err != nil { s.error() s.AjaxErr("client ID not found") return } else { if s.getEscapeString("web_username") != "" { if s.getEscapeString("web_username") == beego.AppConfig.String("web_username") || !file.GetDb().VerifyUserName(s.getEscapeString("web_username"), c.Id) { s.AjaxErr("web login username duplicate, please reset") return } } if s.GetSession("isAdmin").(bool) { if !file.GetDb().VerifyVkey(s.getEscapeString("vkey"), c.Id) { s.AjaxErr("Vkey duplicate, please reset") return } c.VerifyKey = s.getEscapeString("vkey") c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit")) c.RateLimit = s.GetIntNoErr("rate_limit") c.MaxConn = s.GetIntNoErr("max_conn") c.MaxTunnelNum = s.GetIntNoErr("max_tunnel") } c.Remark = s.getEscapeString("remark") c.Cnf.U = s.getEscapeString("u") c.Cnf.P = s.getEscapeString("p") c.Cnf.Compress = common.GetBoolByStr(s.getEscapeString("compress")) c.Cnf.Crypt = s.GetBoolNoErr("crypt") b, err := beego.AppConfig.Bool("allow_user_change_username") if s.GetSession("isAdmin").(bool) || (err == nil && b) { c.WebUserName = s.getEscapeString("web_username") } c.WebPassword = s.getEscapeString("web_password") c.ConfigConnAllow = s.GetBoolNoErr("config_conn_allow") if c.Rate != nil { c.Rate.Stop() } if c.RateLimit > 0 { c.Rate = rate.NewRate(int64(c.RateLimit * 1024)) c.Rate.Start() } else { c.Rate = rate.NewRate(int64(2 << 23)) c.Rate.Start() } file.GetDb().JsonDb.StoreClientsToJsonFile() } s.AjaxOk("save success") } } //更改状态 func (s *ClientController) ChangeStatus() { id := s.GetIntNoErr("id") if client, err := file.GetDb().GetClient(id); err == nil { client.Status = s.GetBoolNoErr("status") if client.Status == false { server.DelClientConnect(client.Id) } s.AjaxOk("modified success") } s.AjaxErr("modified fail") } //删除客户端 func (s *ClientController) Del() { id := s.GetIntNoErr("id") if err := file.GetDb().DelClient(id); err != nil { s.AjaxErr("delete error") } server.DelTunnelAndHostByClientId(id, false) server.DelClientConnect(id) s.AjaxOk("delete success") } ================================================ FILE: web/controllers/index.go ================================================ package controllers import ( "ehang.io/nps/lib/file" "ehang.io/nps/server" "ehang.io/nps/server/tool" "github.com/astaxie/beego" ) type IndexController struct { BaseController } func (s *IndexController) Index() { s.Data["web_base_url"] = beego.AppConfig.String("web_base_url") s.Data["data"] = server.GetDashboardData() s.SetInfo("dashboard") s.display("index/index") } func (s *IndexController) Help() { s.SetInfo("about") s.display("index/help") } func (s *IndexController) Tcp() { s.SetInfo("tcp") s.SetType("tcp") s.display("index/list") } func (s *IndexController) Udp() { s.SetInfo("udp") s.SetType("udp") s.display("index/list") } func (s *IndexController) Socks5() { s.SetInfo("socks5") s.SetType("socks5") s.display("index/list") } func (s *IndexController) Http() { s.SetInfo("http proxy") s.SetType("httpProxy") s.display("index/list") } func (s *IndexController) File() { s.SetInfo("file server") s.SetType("file") s.display("index/list") } func (s *IndexController) Secret() { s.SetInfo("secret") s.SetType("secret") s.display("index/list") } func (s *IndexController) P2p() { s.SetInfo("p2p") s.SetType("p2p") s.display("index/list") } func (s *IndexController) Host() { s.SetInfo("host") s.SetType("hostServer") s.display("index/list") } func (s *IndexController) All() { s.Data["menu"] = "client" clientId := s.getEscapeString("client_id") s.Data["client_id"] = clientId s.SetInfo("client id:" + clientId) s.display("index/list") } func (s *IndexController) GetTunnel() { start, length := s.GetAjaxParams() taskType := s.getEscapeString("type") clientId := s.GetIntNoErr("client_id") list, cnt := server.GetTunnel(start, length, taskType, clientId, s.getEscapeString("search")) s.AjaxTable(list, cnt, cnt, nil) } func (s *IndexController) Add() { if s.Ctx.Request.Method == "GET" { s.Data["type"] = s.getEscapeString("type") s.Data["client_id"] = s.getEscapeString("client_id") s.SetInfo("add tunnel") s.display() } else { t := &file.Tunnel{ Port: s.GetIntNoErr("port"), ServerIp: s.getEscapeString("server_ip"), Mode: s.getEscapeString("type"), Target: &file.Target{TargetStr: s.getEscapeString("target"), LocalProxy: s.GetBoolNoErr("local_proxy")}, Id: int(file.GetDb().JsonDb.GetTaskId()), Status: true, Remark: s.getEscapeString("remark"), Password: s.getEscapeString("password"), LocalPath: s.getEscapeString("local_path"), StripPre: s.getEscapeString("strip_pre"), Flow: &file.Flow{}, } if !tool.TestServerPort(t.Port, t.Mode) { s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.") } var err error if t.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr(err.Error()) } if t.Client.MaxTunnelNum != 0 && t.Client.GetTunnelNum() >= t.Client.MaxTunnelNum { s.AjaxErr("The number of tunnels exceeds the limit") } if err := file.GetDb().NewTask(t); err != nil { s.AjaxErr(err.Error()) } if err := server.AddTask(t); err != nil { s.AjaxErr(err.Error()) } else { s.AjaxOk("add success") } } } func (s *IndexController) GetOneTunnel() { id := s.GetIntNoErr("id") data := make(map[string]interface{}) if t, err := file.GetDb().GetTask(id); err != nil { data["code"] = 0 } else { data["code"] = 1 data["data"] = t } s.Data["json"] = data s.ServeJSON() } func (s *IndexController) Edit() { id := s.GetIntNoErr("id") if s.Ctx.Request.Method == "GET" { if t, err := file.GetDb().GetTask(id); err != nil { s.error() } else { s.Data["t"] = t } s.SetInfo("edit tunnel") s.display() } else { if t, err := file.GetDb().GetTask(id); err != nil { s.error() } else { if client, err := file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr("modified error,the client is not exist") return } else { t.Client = client } if s.GetIntNoErr("port") != t.Port { if !tool.TestServerPort(s.GetIntNoErr("port"), t.Mode) { s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.") return } t.Port = s.GetIntNoErr("port") } t.ServerIp = s.getEscapeString("server_ip") t.Mode = s.getEscapeString("type") t.Target = &file.Target{TargetStr: s.getEscapeString("target")} t.Password = s.getEscapeString("password") t.Id = id t.LocalPath = s.getEscapeString("local_path") t.StripPre = s.getEscapeString("strip_pre") t.Remark = s.getEscapeString("remark") t.Target.LocalProxy = s.GetBoolNoErr("local_proxy") file.GetDb().UpdateTask(t) server.StopServer(t.Id) server.StartTask(t.Id) } s.AjaxOk("modified success") } } func (s *IndexController) Stop() { id := s.GetIntNoErr("id") if err := server.StopServer(id); err != nil { s.AjaxErr("stop error") } s.AjaxOk("stop success") } func (s *IndexController) Del() { id := s.GetIntNoErr("id") if err := server.DelTask(id); err != nil { s.AjaxErr("delete error") } s.AjaxOk("delete success") } func (s *IndexController) Start() { id := s.GetIntNoErr("id") if err := server.StartTask(id); err != nil { s.AjaxErr("start error") } s.AjaxOk("start success") } func (s *IndexController) HostList() { if s.Ctx.Request.Method == "GET" { s.Data["client_id"] = s.getEscapeString("client_id") s.Data["menu"] = "host" s.SetInfo("host list") s.display("index/hlist") } else { start, length := s.GetAjaxParams() clientId := s.GetIntNoErr("client_id") list, cnt := file.GetDb().GetHost(start, length, clientId, s.getEscapeString("search")) s.AjaxTable(list, cnt, cnt, nil) } } func (s *IndexController) GetHost() { if s.Ctx.Request.Method == "POST" { data := make(map[string]interface{}) if h, err := file.GetDb().GetHostById(s.GetIntNoErr("id")); err != nil { data["code"] = 0 } else { data["data"] = h data["code"] = 1 } s.Data["json"] = data s.ServeJSON() } } func (s *IndexController) DelHost() { id := s.GetIntNoErr("id") if err := file.GetDb().DelHost(id); err != nil { s.AjaxErr("delete error") } s.AjaxOk("delete success") } func (s *IndexController) AddHost() { if s.Ctx.Request.Method == "GET" { s.Data["client_id"] = s.getEscapeString("client_id") s.Data["menu"] = "host" s.SetInfo("add host") s.display("index/hadd") } else { h := &file.Host{ Id: int(file.GetDb().JsonDb.GetHostId()), Host: s.getEscapeString("host"), Target: &file.Target{TargetStr: s.getEscapeString("target"), LocalProxy: s.GetBoolNoErr("local_proxy")}, HeaderChange: s.getEscapeString("header"), HostChange: s.getEscapeString("hostchange"), Remark: s.getEscapeString("remark"), Location: s.getEscapeString("location"), Flow: &file.Flow{}, Scheme: s.getEscapeString("scheme"), KeyFilePath: s.getEscapeString("key_file_path"), CertFilePath: s.getEscapeString("cert_file_path"), } var err error if h.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr("add error the client can not be found") } if err := file.GetDb().NewHost(h); err != nil { s.AjaxErr("add fail" + err.Error()) } s.AjaxOk("add success") } } func (s *IndexController) EditHost() { id := s.GetIntNoErr("id") if s.Ctx.Request.Method == "GET" { s.Data["menu"] = "host" if h, err := file.GetDb().GetHostById(id); err != nil { s.error() } else { s.Data["h"] = h } s.SetInfo("edit") s.display("index/hedit") } else { if h, err := file.GetDb().GetHostById(id); err != nil { s.error() } else { if h.Host != s.getEscapeString("host") { tmpHost := new(file.Host) tmpHost.Host = s.getEscapeString("host") tmpHost.Location = s.getEscapeString("location") tmpHost.Scheme = s.getEscapeString("scheme") if file.GetDb().IsHostExist(tmpHost) { s.AjaxErr("host has exist") return } } if client, err := file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil { s.AjaxErr("modified error,the client is not exist") } else { h.Client = client } h.Host = s.getEscapeString("host") h.Target = &file.Target{TargetStr: s.getEscapeString("target")} h.HeaderChange = s.getEscapeString("header") h.HostChange = s.getEscapeString("hostchange") h.Remark = s.getEscapeString("remark") h.Location = s.getEscapeString("location") h.Scheme = s.getEscapeString("scheme") h.KeyFilePath = s.getEscapeString("key_file_path") h.CertFilePath = s.getEscapeString("cert_file_path") h.Target.LocalProxy = s.GetBoolNoErr("local_proxy") file.GetDb().JsonDb.StoreHostToJsonFile() } s.AjaxOk("modified success") } } ================================================ FILE: web/controllers/login.go ================================================ package controllers import ( "math/rand" "net" "sync" "time" "ehang.io/nps/lib/common" "ehang.io/nps/lib/file" "ehang.io/nps/server" "github.com/astaxie/beego" ) type LoginController struct { beego.Controller } var ipRecord sync.Map type record struct { hasLoginFailTimes int lastLoginTime time.Time } func (self *LoginController) Index() { // Try login implicitly, will succeed if it's configured as no-auth(empty username&password). webBaseUrl := beego.AppConfig.String("web_base_url") if self.doLogin("", "", false) { self.Redirect(webBaseUrl+"/index/index", 302) } self.Data["web_base_url"] = webBaseUrl self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register") self.TplName = "login/index.html" } func (self *LoginController) Verify() { username := self.GetString("username") password := self.GetString("password") if self.doLogin(username, password, true) { self.Data["json"] = map[string]interface{}{"status": 1, "msg": "login success"} } else { self.Data["json"] = map[string]interface{}{"status": 0, "msg": "username or password incorrect"} } self.ServeJSON() } func (self *LoginController) doLogin(username, password string, explicit bool) bool { clearIprecord() ip, _, _ := net.SplitHostPort(self.Ctx.Request.RemoteAddr) if v, ok := ipRecord.Load(ip); ok { vv := v.(*record) if (time.Now().Unix() - vv.lastLoginTime.Unix()) >= 60 { vv.hasLoginFailTimes = 0 } if vv.hasLoginFailTimes >= 10 { return false } } var auth bool if password == beego.AppConfig.String("web_password") && username == beego.AppConfig.String("web_username") { self.SetSession("isAdmin", true) self.DelSession("clientId") self.DelSession("username") auth = true server.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Input.IP()), time.Now().Add(time.Hour*time.Duration(2))) } b, err := beego.AppConfig.Bool("allow_user_login") if err == nil && b && !auth { file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool { v := value.(*file.Client) if !v.Status || v.NoDisplay { return true } if v.WebUserName == "" && v.WebPassword == "" { if username != "user" || v.VerifyKey != password { return true } else { auth = true } } if !auth && v.WebPassword == password && v.WebUserName == username { auth = true } if auth { self.SetSession("isAdmin", false) self.SetSession("clientId", v.Id) self.SetSession("username", v.WebUserName) return false } return true }) } if auth { self.SetSession("auth", true) ipRecord.Delete(ip) return true } if v, load := ipRecord.LoadOrStore(ip, &record{hasLoginFailTimes: 1, lastLoginTime: time.Now()}); load && explicit { vv := v.(*record) vv.lastLoginTime = time.Now() vv.hasLoginFailTimes += 1 ipRecord.Store(ip, vv) } return false } func (self *LoginController) Register() { if self.Ctx.Request.Method == "GET" { self.Data["web_base_url"] = beego.AppConfig.String("web_base_url") self.TplName = "login/register.html" } else { if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b { self.Data["json"] = map[string]interface{}{"status": 0, "msg": "register is not allow"} self.ServeJSON() return } if self.GetString("username") == "" || self.GetString("password") == "" || self.GetString("username") == beego.AppConfig.String("web_username") { self.Data["json"] = map[string]interface{}{"status": 0, "msg": "please check your input"} self.ServeJSON() return } t := &file.Client{ Id: int(file.GetDb().JsonDb.GetClientId()), Status: true, Cnf: &file.Config{}, WebUserName: self.GetString("username"), WebPassword: self.GetString("password"), Flow: &file.Flow{}, } if err := file.GetDb().NewClient(t); err != nil { self.Data["json"] = map[string]interface{}{"status": 0, "msg": err.Error()} } else { self.Data["json"] = map[string]interface{}{"status": 1, "msg": "register success"} } self.ServeJSON() } } func (self *LoginController) Out() { self.SetSession("auth", false) self.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302) } func clearIprecord() { rand.Seed(time.Now().UnixNano()) x := rand.Intn(100) if x == 1 { ipRecord.Range(func(key, value interface{}) bool { v := value.(*record) if time.Now().Unix()-v.lastLoginTime.Unix() >= 60 { ipRecord.Delete(key) } return true }) } } ================================================ FILE: web/routers/router.go ================================================ package routers import ( "ehang.io/nps/web/controllers" "github.com/astaxie/beego" ) func Init() { web_base_url := beego.AppConfig.String("web_base_url") if len(web_base_url) > 0 { ns := beego.NewNamespace(web_base_url, beego.NSRouter("/", &controllers.IndexController{}, "*:Index"), beego.NSAutoRouter(&controllers.IndexController{}), beego.NSAutoRouter(&controllers.LoginController{}), beego.NSAutoRouter(&controllers.ClientController{}), beego.NSAutoRouter(&controllers.AuthController{}), ) beego.AddNamespace(ns) } else { beego.Router("/", &controllers.IndexController{}, "*:Index") beego.AutoRouter(&controllers.IndexController{}) beego.AutoRouter(&controllers.LoginController{}) beego.AutoRouter(&controllers.ClientController{}) beego.AutoRouter(&controllers.AuthController{}) } } ================================================ FILE: web/static/css/datatables.css ================================================ /* * This combined file was created by the DataTables downloader builder: * https://datatables.net/download * * To rebuild or modify this file with the latest versions of the included * software please visit: * https://datatables.net/download/#dt/dt-1.10.20 * * Included libraries: * DataTables 1.10.20 */ /* * Table styles */ table.dataTable { width: 100%; margin: 0 auto; clear: both; border-collapse: separate; border-spacing: 0; /* * Header and footer styles */ /* * Body styles */ } table.dataTable thead th, table.dataTable tfoot th { font-weight: bold; } table.dataTable thead th, table.dataTable thead td { padding: 10px 18px; border-bottom: 1px solid #111; } table.dataTable thead th:active, table.dataTable thead td:active { outline: none; } table.dataTable tfoot th, table.dataTable tfoot td { padding: 10px 18px 6px 18px; border-top: 1px solid #111; } table.dataTable thead .sorting, table.dataTable thead .sorting_asc, table.dataTable thead .sorting_desc, table.dataTable thead .sorting_asc_disabled, table.dataTable thead .sorting_desc_disabled { cursor: pointer; *cursor: hand; background-repeat: no-repeat; background-position: center right; } table.dataTable thead .sorting { background-image: url("DataTables-1.10.20/images/sort_both.png"); } table.dataTable thead .sorting_asc { background-image: url("DataTables-1.10.20/images/sort_asc.png"); } table.dataTable thead .sorting_desc { background-image: url("DataTables-1.10.20/images/sort_desc.png"); } table.dataTable thead .sorting_asc_disabled { background-image: url("DataTables-1.10.20/images/sort_asc_disabled.png"); } table.dataTable thead .sorting_desc_disabled { background-image: url("DataTables-1.10.20/images/sort_desc_disabled.png"); } table.dataTable tbody tr { background-color: #ffffff; } table.dataTable tbody tr.selected { background-color: #B0BED9; } table.dataTable tbody th, table.dataTable tbody td { padding: 8px 10px; } table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { border-top: 1px solid #ddd; } table.dataTable.row-border tbody tr:first-child th, table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, table.dataTable.display tbody tr:first-child td { border-top: none; } table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { border-top: 1px solid #ddd; border-right: 1px solid #ddd; } table.dataTable.cell-border tbody tr th:first-child, table.dataTable.cell-border tbody tr td:first-child { border-left: 1px solid #ddd; } table.dataTable.cell-border tbody tr:first-child th, table.dataTable.cell-border tbody tr:first-child td { border-top: none; } table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { background-color: #f9f9f9; } table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { background-color: #acbad4; } table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover { background-color: #f6f6f6; } table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected { background-color: #aab7d1; } table.dataTable.order-column tbody tr > .sorting_1, table.dataTable.order-column tbody tr > .sorting_2, table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, table.dataTable.display tbody tr > .sorting_2, table.dataTable.display tbody tr > .sorting_3 { background-color: #fafafa; } table.dataTable.order-column tbody tr.selected > .sorting_1, table.dataTable.order-column tbody tr.selected > .sorting_2, table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, table.dataTable.display tbody tr.selected > .sorting_2, table.dataTable.display tbody tr.selected > .sorting_3 { background-color: #acbad5; } table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { background-color: #f1f1f1; } table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { background-color: #f3f3f3; } table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { background-color: whitesmoke; } table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { background-color: #a6b4cd; } table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { background-color: #a8b5cf; } table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { background-color: #a9b7d1; } table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { background-color: #fafafa; } table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { background-color: #fcfcfc; } table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { background-color: #fefefe; } table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { background-color: #acbad5; } table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { background-color: #aebcd6; } table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { background-color: #afbdd8; } table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { background-color: #eaeaea; } table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { background-color: #ececec; } table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { background-color: #efefef; } table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { background-color: #a2aec7; } table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { background-color: #a3b0c9; } table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { background-color: #a5b2cb; } table.dataTable.no-footer { border-bottom: 1px solid #111; } table.dataTable.nowrap th, table.dataTable.nowrap td { white-space: nowrap; } table.dataTable.compact thead th, table.dataTable.compact thead td { padding: 4px 17px 4px 4px; } table.dataTable.compact tfoot th, table.dataTable.compact tfoot td { padding: 4px; } table.dataTable.compact tbody th, table.dataTable.compact tbody td { padding: 4px; } table.dataTable th.dt-left, table.dataTable td.dt-left { text-align: left; } table.dataTable th.dt-center, table.dataTable td.dt-center, table.dataTable td.dataTables_empty { text-align: center; } table.dataTable th.dt-right, table.dataTable td.dt-right { text-align: right; } table.dataTable th.dt-justify, table.dataTable td.dt-justify { text-align: justify; } table.dataTable th.dt-nowrap, table.dataTable td.dt-nowrap { white-space: nowrap; } table.dataTable thead th.dt-head-left, table.dataTable thead td.dt-head-left, table.dataTable tfoot th.dt-head-left, table.dataTable tfoot td.dt-head-left { text-align: left; } table.dataTable thead th.dt-head-center, table.dataTable thead td.dt-head-center, table.dataTable tfoot th.dt-head-center, table.dataTable tfoot td.dt-head-center { text-align: center; } table.dataTable thead th.dt-head-right, table.dataTable thead td.dt-head-right, table.dataTable tfoot th.dt-head-right, table.dataTable tfoot td.dt-head-right { text-align: right; } table.dataTable thead th.dt-head-justify, table.dataTable thead td.dt-head-justify, table.dataTable tfoot th.dt-head-justify, table.dataTable tfoot td.dt-head-justify { text-align: justify; } table.dataTable thead th.dt-head-nowrap, table.dataTable thead td.dt-head-nowrap, table.dataTable tfoot th.dt-head-nowrap, table.dataTable tfoot td.dt-head-nowrap { white-space: nowrap; } table.dataTable tbody th.dt-body-left, table.dataTable tbody td.dt-body-left { text-align: left; } table.dataTable tbody th.dt-body-center, table.dataTable tbody td.dt-body-center { text-align: center; } table.dataTable tbody th.dt-body-right, table.dataTable tbody td.dt-body-right { text-align: right; } table.dataTable tbody th.dt-body-justify, table.dataTable tbody td.dt-body-justify { text-align: justify; } table.dataTable tbody th.dt-body-nowrap, table.dataTable tbody td.dt-body-nowrap { white-space: nowrap; } table.dataTable, table.dataTable th, table.dataTable td { box-sizing: content-box; } /* * Control feature layout */ .dataTables_wrapper { position: relative; clear: both; *zoom: 1; zoom: 1; } .dataTables_wrapper .dataTables_length { float: left; } .dataTables_wrapper .dataTables_filter { float: right; text-align: right; } .dataTables_wrapper .dataTables_filter input { margin-left: 0.5em; } .dataTables_wrapper .dataTables_info { clear: both; float: left; padding-top: 0.755em; } .dataTables_wrapper .dataTables_paginate { float: right; text-align: right; padding-top: 0.25em; } .dataTables_wrapper .dataTables_paginate .paginate_button { box-sizing: border-box; display: inline-block; min-width: 1.5em; padding: 0.5em 1em; margin-left: 2px; text-align: center; text-decoration: none !important; cursor: pointer; *cursor: hand; color: #333 !important; border: 1px solid transparent; border-radius: 2px; } .dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { color: #333 !important; border: 1px solid #979797; background-color: white; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%); /* Chrome10+,Safari5.1+ */ background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%); /* FF3.6+ */ background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%); /* IE10+ */ background: -o-linear-gradient(top, white 0%, #dcdcdc 100%); /* Opera 11.10+ */ background: linear-gradient(to bottom, white 0%, #dcdcdc 100%); /* W3C */ } .dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { cursor: default; color: #666 !important; border: 1px solid transparent; background: transparent; box-shadow: none; } .dataTables_wrapper .dataTables_paginate .paginate_button:hover { color: white !important; border: 1px solid #111; background-color: #585858; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #585858 0%, #111 100%); /* Chrome10+,Safari5.1+ */ background: -moz-linear-gradient(top, #585858 0%, #111 100%); /* FF3.6+ */ background: -ms-linear-gradient(top, #585858 0%, #111 100%); /* IE10+ */ background: -o-linear-gradient(top, #585858 0%, #111 100%); /* Opera 11.10+ */ background: linear-gradient(to bottom, #585858 0%, #111 100%); /* W3C */ } .dataTables_wrapper .dataTables_paginate .paginate_button:active { outline: none; background-color: #2b2b2b; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */ background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* FF3.6+ */ background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* IE10+ */ background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Opera 11.10+ */ background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); /* W3C */ box-shadow: inset 0 0 3px #111; } .dataTables_wrapper .dataTables_paginate .ellipsis { padding: 0 1em; } .dataTables_wrapper .dataTables_processing { position: absolute; top: 50%; left: 50%; width: 100%; height: 40px; margin-left: -50%; margin-top: -25px; padding-top: 20px; text-align: center; font-size: 1.2em; background-color: white; background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); } .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter, .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_processing, .dataTables_wrapper .dataTables_paginate { color: #333; } .dataTables_wrapper .dataTables_scroll { clear: both; } .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { *margin-top: -1px; -webkit-overflow-scrolling: touch; } .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { vertical-align: middle; } .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { height: 0; overflow: hidden; margin: 0 !important; padding: 0 !important; } .dataTables_wrapper.no-footer .dataTables_scrollBody { border-bottom: 1px solid #111; } .dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, .dataTables_wrapper.no-footer div.dataTables_scrollBody > table { border-bottom: none; } .dataTables_wrapper:after { visibility: hidden; display: block; content: ""; clear: both; height: 0; } @media screen and (max-width: 767px) { .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_paginate { float: none; text-align: center; } .dataTables_wrapper .dataTables_paginate { margin-top: 0.5em; } } @media screen and (max-width: 640px) { .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter { float: none; text-align: center; } .dataTables_wrapper .dataTables_filter { margin-top: 0.5em; } } ================================================ FILE: web/static/css/style.css ================================================ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700"); @import url("https://fonts.googleapis.com/css?family=Roboto:400,300,500,700"); /* * * INSPINIA - Responsive Admin Theme * version 2.9.3 * */ @font-face { font-family: 'Glyphicons Halflings'; src: url('../fonts/glyphicons-halflings-regular.eot'); 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'); } .glyphicon { position: relative; top: 1px; display: inline-block; font-family: 'Glyphicons Halflings'; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .glyphicon-asterisk:before { content: "\002a"; } .glyphicon-plus:before { content: "\002b"; } .glyphicon-euro:before, .glyphicon-eur:before { content: "\20ac"; } .glyphicon-minus:before { content: "\2212"; } .glyphicon-cloud:before { content: "\2601"; } .glyphicon-envelope:before { content: "\2709"; } .glyphicon-pencil:before { content: "\270f"; } .glyphicon-glass:before { content: "\e001"; } .glyphicon-music:before { content: "\e002"; } .glyphicon-search:before { content: "\e003"; } .glyphicon-heart:before { content: "\e005"; } .glyphicon-star:before { content: "\e006"; } .glyphicon-star-empty:before { content: "\e007"; } .glyphicon-user:before { content: "\e008"; } .glyphicon-film:before { content: "\e009"; } .glyphicon-th-large:before { content: "\e010"; } .glyphicon-th:before { content: "\e011"; } .glyphicon-th-list:before { content: "\e012"; } .glyphicon-ok:before { content: "\e013"; } .glyphicon-remove:before { content: "\e014"; } .glyphicon-zoom-in:before { content: "\e015"; } .glyphicon-zoom-out:before { content: "\e016"; } .glyphicon-off:before { content: "\e017"; } .glyphicon-signal:before { content: "\e018"; } .glyphicon-cog:before { content: "\e019"; } .glyphicon-trash:before { content: "\e020"; } .glyphicon-home:before { content: "\e021"; } .glyphicon-file:before { content: "\e022"; } .glyphicon-time:before { content: "\e023"; } .glyphicon-road:before { content: "\e024"; } .glyphicon-download-alt:before { content: "\e025"; } .glyphicon-download:before { content: "\e026"; } .glyphicon-upload:before { content: "\e027"; } .glyphicon-inbox:before { content: "\e028"; } .glyphicon-play-circle:before { content: "\e029"; } .glyphicon-repeat:before { content: "\e030"; } .glyphicon-refresh:before { content: "\e031"; } .glyphicon-list-alt:before { content: "\e032"; } .glyphicon-lock:before { content: "\e033"; } .glyphicon-flag:before { content: "\e034"; } .glyphicon-headphones:before { content: "\e035"; } .glyphicon-volume-off:before { content: "\e036"; } .glyphicon-volume-down:before { content: "\e037"; } .glyphicon-volume-up:before { content: "\e038"; } .glyphicon-qrcode:before { content: "\e039"; } .glyphicon-barcode:before { content: "\e040"; } .glyphicon-tag:before { content: "\e041"; } .glyphicon-tags:before { content: "\e042"; } .glyphicon-book:before { content: "\e043"; } .glyphicon-bookmark:before { content: "\e044"; } .glyphicon-print:before { content: "\e045"; } .glyphicon-camera:before { content: "\e046"; } .glyphicon-font:before { content: "\e047"; } .glyphicon-bold:before { content: "\e048"; } .glyphicon-italic:before { content: "\e049"; } .glyphicon-text-height:before { content: "\e050"; } .glyphicon-text-width:before { content: "\e051"; } .glyphicon-align-left:before { content: "\e052"; } .glyphicon-align-center:before { content: "\e053"; } .glyphicon-align-right:before { content: "\e054"; } .glyphicon-align-justify:before { content: "\e055"; } .glyphicon-list:before { content: "\e056"; } .glyphicon-indent-left:before { content: "\e057"; } .glyphicon-indent-right:before { content: "\e058"; } .glyphicon-facetime-video:before { content: "\e059"; } .glyphicon-picture:before { content: "\e060"; } .glyphicon-map-marker:before { content: "\e062"; } .glyphicon-adjust:before { content: "\e063"; } .glyphicon-tint:before { content: "\e064"; } .glyphicon-edit:before { content: "\e065"; } .glyphicon-share:before { content: "\e066"; } .glyphicon-check:before { content: "\e067"; } .glyphicon-move:before { content: "\e068"; } .glyphicon-step-backward:before { content: "\e069"; } .glyphicon-fast-backward:before { content: "\e070"; } .glyphicon-backward:before { content: "\e071"; } .glyphicon-play:before { content: "\e072"; } .glyphicon-pause:before { content: "\e073"; } .glyphicon-stop:before { content: "\e074"; } .glyphicon-forward:before { content: "\e075"; } .glyphicon-fast-forward:before { content: "\e076"; } .glyphicon-step-forward:before { content: "\e077"; } .glyphicon-eject:before { content: "\e078"; } .glyphicon-chevron-left:before { content: "\e079"; } .glyphicon-chevron-right:before { content: "\e080"; } .glyphicon-plus-sign:before { content: "\e081"; } .glyphicon-minus-sign:before { content: "\e082"; } .glyphicon-remove-sign:before { content: "\e083"; } .glyphicon-ok-sign:before { content: "\e084"; } .glyphicon-question-sign:before { content: "\e085"; } .glyphicon-info-sign:before { content: "\e086"; } .glyphicon-screenshot:before { content: "\e087"; } .glyphicon-remove-circle:before { content: "\e088"; } .glyphicon-ok-circle:before { content: "\e089"; } .glyphicon-ban-circle:before { content: "\e090"; } .glyphicon-arrow-left:before { content: "\e091"; } .glyphicon-arrow-right:before { content: "\e092"; } .glyphicon-arrow-up:before { content: "\e093"; } .glyphicon-arrow-down:before { content: "\e094"; } .glyphicon-share-alt:before { content: "\e095"; } .glyphicon-resize-full:before { content: "\e096"; } .glyphicon-resize-small:before { content: "\e097"; } .glyphicon-exclamation-sign:before { content: "\e101"; } .glyphicon-gift:before { content: "\e102"; } .glyphicon-leaf:before { content: "\e103"; } .glyphicon-fire:before { content: "\e104"; } .glyphicon-eye-open:before { content: "\e105"; } .glyphicon-eye-close:before { content: "\e106"; } .glyphicon-warning-sign:before { content: "\e107"; } .glyphicon-plane:before { content: "\e108"; } .glyphicon-calendar:before { content: "\e109"; } .glyphicon-random:before { content: "\e110"; } .glyphicon-comment:before { content: "\e111"; } .glyphicon-magnet:before { content: "\e112"; } .glyphicon-chevron-up:before { content: "\e113"; } .glyphicon-chevron-down:before { content: "\e114"; } .glyphicon-retweet:before { content: "\e115"; } .glyphicon-shopping-cart:before { content: "\e116"; } .glyphicon-folder-close:before { content: "\e117"; } .glyphicon-folder-open:before { content: "\e118"; } .glyphicon-resize-vertical:before { content: "\e119"; } .glyphicon-resize-horizontal:before { content: "\e120"; } .glyphicon-hdd:before { content: "\e121"; } .glyphicon-bullhorn:before { content: "\e122"; } .glyphicon-bell:before { content: "\e123"; } .glyphicon-certificate:before { content: "\e124"; } .glyphicon-thumbs-up:before { content: "\e125"; } .glyphicon-thumbs-down:before { content: "\e126"; } .glyphicon-hand-right:before { content: "\e127"; } .glyphicon-hand-left:before { content: "\e128"; } .glyphicon-hand-up:before { content: "\e129"; } .glyphicon-hand-down:before { content: "\e130"; } .glyphicon-circle-arrow-right:before { content: "\e131"; } .glyphicon-circle-arrow-left:before { content: "\e132"; } .glyphicon-circle-arrow-up:before { content: "\e133"; } .glyphicon-circle-arrow-down:before { content: "\e134"; } .glyphicon-globe:before { content: "\e135"; } .glyphicon-wrench:before { content: "\e136"; } .glyphicon-tasks:before { content: "\e137"; } .glyphicon-filter:before { content: "\e138"; } .glyphicon-briefcase:before { content: "\e139"; } .glyphicon-fullscreen:before { content: "\e140"; } .glyphicon-dashboard:before { content: "\e141"; } .glyphicon-paperclip:before { content: "\e142"; } .glyphicon-heart-empty:before { content: "\e143"; } .glyphicon-link:before { content: "\e144"; } .glyphicon-phone:before { content: "\e145"; } .glyphicon-pushpin:before { content: "\e146"; } .glyphicon-usd:before { content: "\e148"; } .glyphicon-gbp:before { content: "\e149"; } .glyphicon-sort:before { content: "\e150"; } .glyphicon-sort-by-alphabet:before { content: "\e151"; } .glyphicon-sort-by-alphabet-alt:before { content: "\e152"; } .glyphicon-sort-by-order:before { content: "\e153"; } .glyphicon-sort-by-order-alt:before { content: "\e154"; } .glyphicon-sort-by-attributes:before { content: "\e155"; } .glyphicon-sort-by-attributes-alt:before { content: "\e156"; } .glyphicon-unchecked:before { content: "\e157"; } .glyphicon-expand:before { content: "\e158"; } .glyphicon-collapse-down:before { content: "\e159"; } .glyphicon-collapse-up:before { content: "\e160"; } .glyphicon-log-in:before { content: "\e161"; } .glyphicon-flash:before { content: "\e162"; } .glyphicon-log-out:before { content: "\e163"; } .glyphicon-new-window:before { content: "\e164"; } .glyphicon-record:before { content: "\e165"; } .glyphicon-save:before { content: "\e166"; } .glyphicon-open:before { content: "\e167"; } .glyphicon-saved:before { content: "\e168"; } .glyphicon-import:before { content: "\e169"; } .glyphicon-export:before { content: "\e170"; } .glyphicon-send:before { content: "\e171"; } .glyphicon-floppy-disk:before { content: "\e172"; } .glyphicon-floppy-saved:before { content: "\e173"; } .glyphicon-floppy-remove:before { content: "\e174"; } .glyphicon-floppy-save:before { content: "\e175"; } .glyphicon-floppy-open:before { content: "\e176"; } .glyphicon-credit-card:before { content: "\e177"; } .glyphicon-transfer:before { content: "\e178"; } .glyphicon-cutlery:before { content: "\e179"; } .glyphicon-header:before { content: "\e180"; } .glyphicon-compressed:before { content: "\e181"; } .glyphicon-earphone:before { content: "\e182"; } .glyphicon-phone-alt:before { content: "\e183"; } .glyphicon-tower:before { content: "\e184"; } .glyphicon-stats:before { content: "\e185"; } .glyphicon-sd-video:before { content: "\e186"; } .glyphicon-hd-video:before { content: "\e187"; } .glyphicon-subtitles:before { content: "\e188"; } .glyphicon-sound-stereo:before { content: "\e189"; } .glyphicon-sound-dolby:before { content: "\e190"; } .glyphicon-sound-5-1:before { content: "\e191"; } .glyphicon-sound-6-1:before { content: "\e192"; } .glyphicon-sound-7-1:before { content: "\e193"; } .glyphicon-copyright-mark:before { content: "\e194"; } .glyphicon-registration-mark:before { content: "\e195"; } .glyphicon-cloud-download:before { content: "\e197"; } .glyphicon-cloud-upload:before { content: "\e198"; } .glyphicon-tree-conifer:before { content: "\e199"; } .glyphicon-tree-deciduous:before { content: "\e200"; } .glyphicon-cd:before { content: "\e201"; } .glyphicon-save-file:before { content: "\e202"; } .glyphicon-open-file:before { content: "\e203"; } .glyphicon-level-up:before { content: "\e204"; } .glyphicon-copy:before { content: "\e205"; } .glyphicon-paste:before { content: "\e206"; } .glyphicon-alert:before { content: "\e209"; } .glyphicon-equalizer:before { content: "\e210"; } .glyphicon-king:before { content: "\e211"; } .glyphicon-queen:before { content: "\e212"; } .glyphicon-pawn:before { content: "\e213"; } .glyphicon-bishop:before { content: "\e214"; } .glyphicon-knight:before { content: "\e215"; } .glyphicon-baby-formula:before { content: "\e216"; } .glyphicon-tent:before { content: "\26fa"; } .glyphicon-blackboard:before { content: "\e218"; } .glyphicon-bed:before { content: "\e219"; } .glyphicon-apple:before { content: "\f8ff"; } .glyphicon-erase:before { content: "\e221"; } .glyphicon-hourglass:before { content: "\231b"; } .glyphicon-lamp:before { content: "\e223"; } .glyphicon-duplicate:before { content: "\e224"; } .glyphicon-piggy-bank:before { content: "\e225"; } .glyphicon-scissors:before { content: "\e226"; } .glyphicon-bitcoin:before { content: "\e227"; } .glyphicon-btc:before { content: "\e227"; } .glyphicon-xbt:before { content: "\e227"; } .glyphicon-yen:before { content: "\00a5"; } .glyphicon-jpy:before { content: "\00a5"; } .glyphicon-ruble:before { content: "\20bd"; } .glyphicon-rub:before { content: "\20bd"; } .glyphicon-scale:before { content: "\e230"; } .glyphicon-ice-lolly:before { content: "\e231"; } .glyphicon-ice-lolly-tasted:before { content: "\e232"; } .glyphicon-education:before { content: "\e233"; } .glyphicon-option-horizontal:before { content: "\e234"; } .glyphicon-option-vertical:before { content: "\e235"; } .glyphicon-menu-hamburger:before { content: "\e236"; } .glyphicon-modal-window:before { content: "\e237"; } .glyphicon-oil:before { content: "\e238"; } .glyphicon-grain:before { content: "\e239"; } .glyphicon-sunglasses:before { content: "\e240"; } .glyphicon-text-size:before { content: "\e241"; } .glyphicon-text-color:before { content: "\e242"; } .glyphicon-text-background:before { content: "\e243"; } .glyphicon-object-align-top:before { content: "\e244"; } .glyphicon-object-align-bottom:before { content: "\e245"; } .glyphicon-object-align-horizontal:before { content: "\e246"; } .glyphicon-object-align-left:before { content: "\e247"; } .glyphicon-object-align-vertical:before { content: "\e248"; } .glyphicon-object-align-right:before { content: "\e249"; } .glyphicon-triangle-right:before { content: "\e250"; } .glyphicon-triangle-left:before { content: "\e251"; } .glyphicon-triangle-bottom:before { content: "\e252"; } .glyphicon-triangle-top:before { content: "\e253"; } .glyphicon-console:before { content: "\e254"; } .glyphicon-superscript:before { content: "\e255"; } .glyphicon-subscript:before { content: "\e256"; } .glyphicon-menu-left:before { content: "\e257"; } .glyphicon-menu-right:before { content: "\e258"; } .glyphicon-menu-down:before { content: "\e259"; } .glyphicon-menu-up:before { content: "\e260"; } h1, h2, h3, h4, h5, h6 { font-weight: 100; } .h1, .h2, .h3, h1, h2, h3 { margin-top: 20px; margin-bottom: 10px; } h1 { font-size: 30px; } h2 { font-size: 24px; } h3 { font-size: 16px; } h4 { font-size: 14px; } h5 { font-size: 12px; } h6 { font-size: 10px; } h3, h4, h5 { margin-top: 5px; font-weight: 600; } .nav > li > a { color: #a7b1c2; font-weight: 600; padding: 14px 20px 14px 25px; display: block; } .nav.metismenu > li { display: block; width: 100%; position: relative; } .nav.metismenu .dropdown-menu > li > a { padding: 3px 20px; display: block; } .nav.navbar-right > li > a { color: #999c9e; } .nav > li.active > a { color: #ffffff; } .navbar-default .nav > li > a:hover, .navbar-default .nav > li > a:focus { background-color: #293846; color: white; } .nav .open > a, .nav .open > a:hover, .nav .open > a:focus { background: #fff; } .nav.navbar-top-links > li > a:hover, .nav.navbar-top-links > li > a:focus { background-color: transparent; } .nav > li > a i { margin-right: 6px; } .navbar { border: 0; } .navbar-default { background-color: transparent; border-color: #2f4050; } .navbar-top-links li { display: inline-block; align-self: center; } .body-small .navbar-top-links li:last-child { margin-right: 0; } .navbar-top-links li a { padding: 20px 10px; min-height: 50px; } .dropdown-menu { border: medium none; border-radius: 3px; box-shadow: 0 0 3px rgba(86, 96, 117, 0.7); display: none; float: left; font-size: 12px; left: 0; list-style: none outside none; padding: 0; position: absolute; text-shadow: none; top: 100%; z-index: 1000; } .dropdown-menu > li > a { border-radius: 3px; color: inherit; line-height: 25px; margin: 4px; text-align: left; font-weight: normal; display: block; padding: 3px 20px; } .dropdown-menu > li > a:focus, .dropdown-menu > li > a:hover { color: #262626; text-decoration: none; background-color: #f5f5f5; } .dropdown-menu > .active > a, .dropdown-menu > .active > a:focus, .dropdown-menu > .active > a:hover { color: #fff; text-decoration: none; background-color: #1ab394; outline: 0; } .dropdown-menu > li > a.font-bold { font-weight: 600; } .navbar-top-links .dropdown-menu li { display: block; } .navbar-top-links .dropdown-menu li:last-child { margin-right: 0; } .navbar-top-links .dropdown-menu li a { padding: 3px 20px; min-height: 0; } .navbar-top-links .dropdown-menu li a div { white-space: normal; } .navbar-top-links .dropdown-messages, .navbar-top-links .dropdown-tasks, .navbar-top-links .dropdown-alerts { width: 310px; min-width: 0; } .navbar-top-links .dropdown-messages { margin-left: 5px; } .navbar-top-links .dropdown-tasks { margin-left: -59px; } .navbar-top-links .dropdown-alerts { margin-left: -123px; } .navbar-top-links .dropdown-user { right: 0; left: auto; } .dropdown-messages, .dropdown-alerts { padding: 10px 10px 10px 10px; } .dropdown-messages li a, .dropdown-alerts li a { font-size: 12px; } .dropdown-messages li em, .dropdown-alerts li em { font-size: 10px; } .nav.navbar-top-links .dropdown-alerts a { font-size: 12px; } .nav-header { padding: 33px 25px; background-color: #2f4050; // background-image: url("patterns/header-profile.png"); } /*.caret { display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle; border-top: 4px dashed; border-right: 4px solid transparent; border-left: 4px solid transparent; }*/ .profile-element .dropdown-toggle::after { display: none; } .pace-done .nav-header { transition: all 0.4s; } ul.nav-second-level { background: #293846; } .nav > li.active { border-left: 4px solid #19aa8d; background: #293846; } .nav.nav-second-level > li.active { border: none; } .nav.nav-second-level.collapse[style] { height: auto !important; } .nav-header a { color: #DFE4ED; } .nav-header .text-muted { color: #8095a8 !important; } .minimalize-styl-2 { padding: 4px 12px; margin: 14px 5px 5px 20px; font-size: 14px; float: left; } .navbar-form-custom { float: left; height: 50px; padding: 0; width: 200px; display: block; } .navbar-form-custom .form-group { margin-bottom: 0; } .nav.navbar-top-links a { font-size: 14px; } .navbar-form-custom .form-control { background: none repeat scroll 0 0 rgba(0, 0, 0, 0); border: medium none; font-size: 14px; height: 60px; margin: 0; z-index: 2000; } /*.nav.navbar-top-links .dropdown-toggle::after { display: none; } */ .navbar.navbar-static-top { padding: 0; width: 100%; align-items: inherit; } .navbar-static-top .dropdown-menu { right: 0; left: auto; } .count-info .label { line-height: 12px; padding: 2px 5px; position: absolute; right: 6px; top: 12px; } .arrow { float: right; } .fa.arrow:before { content: "\f104"; } .active > a > .fa.arrow:before { content: "\f107"; } .nav-second-level li, .nav-third-level li { border-bottom: none !important; } .nav.nav-third-level > li.active { border: none; } .nav-second-level li a { padding: 7px 10px 7px 10px; padding-left: 52px; } .fixed-sidebar.mini-navbar .nav-second-level.collapsing li a, .nav-second-level.collapsing li a { min-width: 220px; } .body-small .nav-second-level.collapsing li a, .mini-navbar .nav-second-level.collapsing li a { min-width: 140px; } .nav-third-level li a, .fixed-sidebar.mini-navbar .nav-second-level li .nav-third-level li a { padding-left: 62px; } .nav-second-level li:last-child { padding-bottom: 10px; } body:not(.fixed-sidebar):not(.canvas-menu).mini-navbar .nav li:hover > .nav-second-level, .mini-navbar .nav li:focus > .nav-second-level { display: block; border-radius: 0 2px 2px 0; min-width: 160px; height: auto; } body.mini-navbar .navbar-default .nav > li > .nav-second-level li a { font-size: 12px; border-radius: 3px; } .fixed-nav .slimScrollDiv #side-menu { padding-bottom: 60px; } .mini-navbar .nav-second-level li a { padding: 10px 10px 10px 15px; } .mini-navbar .nav .nav-second-level { position: absolute; left: 70px; top: 0; background-color: #2f4050; padding: 10px 10px 10px 10px; font-size: 12px; } .canvas-menu.mini-navbar .nav-second-level { background: #293846; } .mini-navbar li.active .nav-second-level { left: 65px; } .navbar-default .special_link a { background: #1ab394; color: white; } .navbar-default .special_link a:hover { background: #17987e !important; color: white; } .navbar-default .special_link a span.label { background: #fff; color: #1ab394; } .navbar-default .landing_link a { background: #1cc09f; color: white; } .navbar-default .landing_link a:hover { background: #1ab394 !important; color: white; } .navbar-default .landing_link a span.label { background: #fff; color: #1cc09f; } .logo-element { text-align: center; font-size: 18px; font-weight: 600; color: white; display: none; padding: 18px 0; } .navbar-static-side { transition: width 0s; } .footer { transition: margin 0s; } .pace-done .navbar-static-side, .pace-done .nav-header, .pace-done li.active, .pace-done #page-wrapper, .pace-done .footer { -webkit-transition: all 0.4s; -moz-transition: all 0.4s; -o-transition: all 0.4s; transition: all 0.4s; } .navbar-fixed-top { background: #fff; transition-duration: 0.4s; border-bottom: 1px solid #e7eaec !important; z-index: 2030; position: fixed; right: 0; left: 0; padding: 0; top: 0; } .navbar-fixed-top .navbar-form-custom .form-control { height: 50px; } .navbar-fixed-top, .navbar-static-top { background: #f3f3f4; } .fixed-nav #wrapper { margin-top: 0; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { -moz-border-bottom-colors: none; -moz-border-left-colors: none; -moz-border-right-colors: none; -moz-border-top-colors: none; background: none; border-color: #dddddd #dddddd rgba(0, 0, 0, 0); border-bottom: #f3f3f4; border-image: none; border-style: solid; border-width: 1px; color: #555555; cursor: default; } .nav.nav-tabs li { background: none; border: none; } body.fixed-nav #wrapper .navbar-static-side, body.fixed-nav #wrapper #page-wrapper { margin-top: 60px; } body.top-navigation.fixed-nav #wrapper #page-wrapper { margin-top: 0; } body.fixed-nav.fixed-nav-basic .navbar-fixed-top { left: 220px; } body.fixed-nav.fixed-nav-basic.mini-navbar .navbar-fixed-top { left: 70px; } body.fixed-nav.fixed-nav-basic.fixed-sidebar.mini-navbar .navbar-fixed-top { left: 0; } body.fixed-nav.fixed-nav-basic #wrapper .navbar-static-side { margin-top: 0; } body.fixed-nav.fixed-nav-basic.body-small .navbar-fixed-top { left: 0; } body.fixed-nav.fixed-nav-basic.fixed-sidebar.mini-navbar.body-small .navbar-fixed-top { left: 220px; } .fixed-nav .minimalize-styl-2 { margin: 10px 5px 5px 15px; } .body-small .navbar-fixed-top { margin-left: 0; } body.mini-navbar .navbar-static-side { width: 70px; } body.mini-navbar .profile-element, body.mini-navbar .nav-label, body.mini-navbar .navbar-default .nav li a span { display: none; } body.canvas-menu .profile-element { display: block; } body:not(.fixed-sidebar):not(.canvas-menu).mini-navbar .nav-second-level { display: none; } body.mini-navbar .navbar-default .nav > li > a { font-size: 16px; } body.mini-navbar .logo-element { display: block; } body.canvas-menu .logo-element { display: none; } body.mini-navbar .nav-header { padding: 0; background-color: #1ab394; } body.canvas-menu .nav-header { padding: 33px 25px; } body.canvas-menu .sidebar-collapse li { width: 100%; } body.mini-navbar #page-wrapper { width: calc(100% - 70px); } body.canvas-menu.mini-navbar #page-wrapper, body.canvas-menu.mini-navbar .footer { margin: 0; width: 100%; } body.fixed-sidebar .navbar-static-side, body.canvas-menu .navbar-static-side { width: 220px; z-index: 2001; height: 100vh; position: fixed; } body.fixed-sidebar.mini-navbar .navbar-static-side { width: 0; } body.fixed-sidebar #page-wrapper { margin: 0 0 0 220px; } body.fixed-sidebar.body-small #page-wrapper { margin: 0; } body.fixed-sidebar.mini-navbar #page-wrapper { margin: 0 0 0 0; width: 100%; } body.body-small.fixed-sidebar.mini-navbar #page-wrapper { margin: 0 0 0 220px; } body.body-small.fixed-sidebar.mini-navbar .navbar-static-side { width: 220px; } .fixed-sidebar.mini-navbar .nav li:focus > .nav-second-level, .canvas-menu.mini-navbar .nav li:focus > .nav-second-level { display: block; height: auto; } body.fixed-sidebar.mini-navbar .navbar-default .nav > li > .nav-second-level li a { font-size: 12px; border-radius: 3px; } body.canvas-menu.mini-navbar .navbar-default .nav > li > .nav-second-level li a { font-size: 13px; border-radius: 3px; } .fixed-sidebar.mini-navbar .nav-second-level li a, .canvas-menu.mini-navbar .nav-second-level li a { padding: 10px 10px 10px 15px; } .fixed-sidebar.mini-navbar .nav-second-level, .canvas-menu.mini-navbar .nav-second-level { position: relative; padding: 0; font-size: 13px; } .fixed-sidebar.mini-navbar li.active .nav-second-level, .canvas-menu.mini-navbar li.active .nav-second-level { left: 0; } body.fixed-sidebar.mini-navbar .navbar-default .nav > li > a, body.canvas-menu.mini-navbar .navbar-default .nav > li > a { font-size: 13px; } body.fixed-sidebar.mini-navbar .nav-label, body.fixed-sidebar.mini-navbar .navbar-default .nav li a span, body.canvas-menu.mini-navbar .nav-label, body.canvas-menu.mini-navbar .navbar-default .nav li a span { display: inline; } body.canvas-menu.mini-navbar .navbar-default .nav li .profile-element a span { display: block; } .canvas-menu.mini-navbar .nav-second-level li a, .fixed-sidebar.mini-navbar .nav-second-level li a { padding: 7px 10px 7px 52px; } .fixed-sidebar.mini-navbar .nav-second-level, .canvas-menu.mini-navbar .nav-second-level { left: 0; } body.canvas-menu nav.navbar-static-side { z-index: 2001; background: #2f4050; height: 100%; position: fixed; display: none; } body.canvas-menu.mini-navbar nav.navbar-static-side { display: block; width: 220px; } .top-navigation #page-wrapper { width: 100%; } .top-navigation .navbar-nav .dropdown-menu > .active > a { background: white; color: #1ab394; font-weight: bold; } .white-bg .navbar-fixed-top, .white-bg .navbar-static-top { background: #fff; } .top-navigation .navbar { margin-bottom: 0; } .top-navigation .nav > li > a { padding: 15px 20px; color: #676a6c; } .top-navigation .nav > li a:hover, .top-navigation .nav > li a:focus { background: #fff; color: #1ab394; } .top-navigation .navbar .nav > li.active { background: #fff; border: none; } .top-navigation .nav > li.active > a { color: #1ab394; } .top-navigation .navbar-right { margin-right: 10px; } .top-navigation .navbar-nav .dropdown-menu { box-shadow: none; border: 1px solid #e7eaec; } .top-navigation .dropdown-menu > li > a { margin: 0; padding: 7px 20px; } .navbar .dropdown-menu { margin-top: 0; } .top-navigation .navbar-brand { background: #1ab394; color: #fff; padding: 15px 25px; font-size: 18px; line-height: 20px; } .top-navigation .navbar-top-links li:last-child { margin-right: 0; } .top-navigation.mini-navbar #page-wrapper, .top-navigation.body-small.fixed-sidebar.mini-navbar #page-wrapper, .mini-navbar .top-navigation #page-wrapper, .body-small.fixed-sidebar.mini-navbar .top-navigation #page-wrapper, .canvas-menu #page-wrapper { margin: 0; width: 100%; } .top-navigation.fixed-nav #wrapper, .fixed-nav #wrapper.top-navigation { margin-top: 50px; } .top-navigation .footer.fixed { margin-left: 0 !important; } .top-navigation .wrapper.wrapper-content { padding: 40px; } .top-navigation.body-small .wrapper.wrapper-content, .body-small .top-navigation .wrapper.wrapper-content { padding: 40px 0 40px 0; } .navbar-toggler { background-color: #1ab394; color: #fff; padding: 6px 12px; font-size: 14px; margin: 8px; } .top-navigation .navbar-nav .open .dropdown-menu > li > a, .top-navigation .navbar-nav .open .dropdown-menu .dropdown-header { padding: 10px 15px 10px 20px; } @media (max-width: 768px) { .top-navigation .navbar-header { display: block; float: none; } } .menu-visible-lg, .menu-visible-md { display: none !important; } @media (min-width: 1200px) { .menu-visible-lg { display: block !important; } } @media (min-width: 992px) { .menu-visible-md { display: block !important; } } @media (max-width: 767px) { .menu-visible-md { display: block !important; } .menu-visible-lg { display: block !important; } } button:focus { outline: 0 !important; } .btn { border-radius: 3px; font-size: inherit; } .btn:focus { box-shadow: none; } .btn-xs { font-size: 0.7rem; padding: 0.2rem 0.4rem; } .btn-group-sm > .btn, .btn-sm { font-size: .8rem; } .float-e-margins .btn { margin-bottom: 5px; } .btn-w-m { min-width: 120px; } .btn-primary.btn-outline { color: #1ab394; } .btn-success.btn-outline { color: #1c84c6; } .btn-info.btn-outline { color: #23c6c8; } .btn-warning.btn-outline { color: #f8ac59; } .btn-danger.btn-outline { color: #ed5565; } .btn-primary.btn-outline:hover, .btn-success.btn-outline:hover, .btn-info.btn-outline:hover, .btn-warning.btn-outline:hover, .btn-danger.btn-outline:hover { color: #fff; } .btn.active, .btn:active { background-image: none; outline: 0; -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-primary { color: #fff; background-color: #1ab394; border-color: #1ab394; } .btn-primary:hover, .btn-primary:focus, .btn-primary.focus { background-color: #18a689; border-color: #18a689; color: #FFFFFF; } .btn-primary.disabled, .btn-primary:disabled { color: #fff; background-color: #18a689; border-color: #18a689; } .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { color: #fff; background-color: #18a689; border-color: #18a689; } .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-success { color: #fff; background-color: #1c84c6; border-color: #1c84c6; } .btn-success:hover, .btn-success:focus, .btn-success.focus { color: #fff; background-color: #1a7bb9; border-color: #1a7bb9; } .btn-success.disabled, .btn-success:disabled { color: #fff; background-color: #1a7bb9; border-color: #1a7bb9; } .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle { color: #fff; background-color: #1a7bb9; border-color: #1a7bb9; } .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-info { color: #fff; background-color: #23c6c8; border-color: #23c6c8; } .btn-info:hover, .btn-info:focus, .btn-info.focus { color: #fff; background-color: #21b9bb; border-color: #21b9bb; } .btn-info.disabled, .btn-info:disabled { color: #fff; background-color: #21b9bb; border-color: #21b9bb; } .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle { color: #fff; background-color: #21b9bb; border-color: #21b9bb; } .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-default { color: inherit; background: white; border: 1px solid #e7eaec; } .btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default, .btn-default:active:focus, .btn-default:active:hover, .btn-default.active:hover, .btn-default.active:focus { color: inherit; border: 1px solid #d2d2d2; } .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset; } .btn-default.disabled, .btn-default.disabled:hover, .btn-default.disabled:focus, .btn-default.disabled:active, .btn-default.disabled.active, .btn-default[disabled], .btn-default[disabled]:hover, .btn-default[disabled]:focus, .btn-default[disabled]:active, .btn-default.active[disabled], fieldset[disabled] .btn-default, fieldset[disabled] .btn-default:hover, fieldset[disabled] .btn-default:focus, fieldset[disabled] .btn-default:active, fieldset[disabled] .btn-default.active { color: #cacaca; } .btn-warning { color: #ffffff; background-color: #f8ac59; border-color: #f8ac59; } .btn-warning:hover, .btn-warning:focus, .btn-warning.focus { color: #ffffff; background-color: #f7a54a; border-color: #f7a54a; } .btn-warning.disabled, .btn-warning:disabled { color: #ffffff; background-color: #f7a54a; border-color: #f7a54a; } .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { color: #ffffff; background-color: #f7a54a; border-color: #f7a54a; } .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-danger { color: #fff; background-color: #ed5565; border-color: #ed5565; } .btn-danger:hover, .btn-danger:focus, .btn-danger.focus { color: #fff; background-color: #ec4758; border-color: #ec4758; } .btn-danger.disabled, .btn-danger:disabled { color: #fff; background-color: #ec4758; border-color: #ec4758; } .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle { color: #fff; background-color: #ec4758; border-color: #ec4758; } .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-link { color: inherit; } .btn-link:hover, .btn-link:focus, .btn-link:active, .btn-link.active, .open .dropdown-toggle.btn-link { color: #1ab394; text-decoration: none; } .btn-link:active, .btn-link.active, .open .dropdown-toggle.btn-link { background-image: none; box-shadow: none; } .btn-link.disabled, .btn-link.disabled:hover, .btn-link.disabled:focus, .btn-link.disabled:active, .btn-link.disabled.active, .btn-link[disabled], .btn-link[disabled]:hover, .btn-link[disabled]:focus, .btn-link[disabled]:active, .btn-link.active[disabled], fieldset[disabled] .btn-link, fieldset[disabled] .btn-link:hover, fieldset[disabled] .btn-link:focus, fieldset[disabled] .btn-link:active, fieldset[disabled] .btn-link.active { color: #cacaca; } .btn-white { color: inherit; background: white; border: 1px solid #e7eaec; } .btn-white:hover, .btn-white:focus, .btn-white:active, .btn-white.active, .open .dropdown-toggle.btn-white, .btn-white:active:focus, .btn-white:active:hover, .btn-white.active:hover, .btn-white.active:focus { color: inherit; border: 1px solid #d2d2d2; } .btn-white:active, .btn-white.active { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset; } .btn-white:active, .btn-white.active, .open .dropdown-toggle.btn-white { background-image: none; } .btn-white.disabled, .btn-white.disabled:hover, .btn-white.disabled:focus, .btn-white.disabled:active, .btn-white.disabled.active, .btn-white[disabled], .btn-white[disabled]:hover, .btn-white[disabled]:focus, .btn-white[disabled]:active, .btn-white.active[disabled], fieldset[disabled] .btn-white, fieldset[disabled] .btn-white:hover, fieldset[disabled] .btn-white:focus, fieldset[disabled] .btn-white:active, fieldset[disabled] .btn-white.active { color: #cacaca; } .form-control, .form-control:focus, .has-error .form-control:focus, .has-success .form-control:focus, .has-warning .form-control:focus, .navbar-collapse, .navbar-form, .navbar-form-custom .form-control:focus, .navbar-form-custom .form-control:hover, .open .btn.dropdown-toggle, .panel, .popover, .progress, .progress-bar { box-shadow: none; } .btn-outline { color: inherit; background-color: transparent; transition: all .5s; } .btn-rounded { border-radius: 50px; } .btn-large-dim { width: 90px; height: 90px; font-size: 42px; } button.dim { display: inline-block; text-decoration: none; text-transform: uppercase; text-align: center; padding-top: 6px; margin-right: 10px; position: relative; cursor: pointer; border-radius: 5px; font-weight: 600; margin-bottom: 20px !important; } button.dim:active { top: 3px; } button.btn-primary.dim { box-shadow: inset 0 0 0 #16987e, 0 5px 0 0 #16987e, 0 10px 5px #999999 !important; } button.btn-primary.dim:active { box-shadow: inset 0 0 0 #16987e, 0 2px 0 0 #16987e, 0 5px 3px #999999 !important; } button.btn-default.dim { box-shadow: inset 0 0 0 #b3b3b3, 0 5px 0 0 #b3b3b3, 0 10px 5px #999999 !important; } button.btn-default.dim:active { box-shadow: inset 0 0 0 #b3b3b3, 0 2px 0 0 #b3b3b3, 0 5px 3px #999999 !important; } button.btn-warning.dim { box-shadow: inset 0 0 0 #f79d3c, 0 5px 0 0 #f79d3c, 0 10px 5px #999999 !important; } button.btn-warning.dim:active { box-shadow: inset 0 0 0 #f79d3c, 0 2px 0 0 #f79d3c, 0 5px 3px #999999 !important; } button.btn-info.dim { box-shadow: inset 0 0 0 #1eacae, 0 5px 0 0 #1eacae, 0 10px 5px #999999 !important; } button.btn-info.dim:active { box-shadow: inset 0 0 0 #1eacae, 0 2px 0 0 #1eacae, 0 5px 3px #999999 !important; } button.btn-success.dim { box-shadow: inset 0 0 0 #1872ab, 0 5px 0 0 #1872ab, 0 10px 5px #999999 !important; } button.btn-success.dim:active { box-shadow: inset 0 0 0 #1872ab, 0 2px 0 0 #1872ab, 0 5px 3px #999999 !important; } button.btn-danger.dim { box-shadow: inset 0 0 0 #ea394c, 0 5px 0 0 #ea394c, 0 10px 5px #999999 !important; } button.btn-danger.dim:active { box-shadow: inset 0 0 0 #ea394c, 0 2px 0 0 #ea394c, 0 5px 3px #999999 !important; } button.dim:before { font-size: 50px; line-height: 1em; font-weight: normal; color: #fff; display: block; padding-top: 10px; } button.dim:active:before { top: 7px; font-size: 50px; } .btn:focus { outline: none !important; } .label { background-color: #d1dade; color: #5e5e5e; font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 600; padding: 3px 8px; text-shadow: none; border-radius: 0.25em; line-height: 1; white-space: nowrap; } .nav .label, .ibox .label { font-size: 10px; } .badge { background-color: #d1dade; color: #5e5e5e; font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 600; padding-bottom: 4px; padding-left: 6px; padding-right: 6px; text-shadow: none; white-space: nowrap; } .label-primary, .badge-primary { background-color: #1ab394; color: #FFFFFF; } .label-success, .badge-success { background-color: #1c84c6; color: #FFFFFF; } .label-warning, .badge-warning { background-color: #f8ac59; color: #FFFFFF; } .label-warning-light, .badge-warning-light { background-color: #f8ac59; color: #ffffff; } .label-danger, .badge-danger { background-color: #ed5565; color: #FFFFFF; } .label-info, .badge-info { background-color: #23c6c8; color: #FFFFFF; } .label-inverse, .badge-inverse { background-color: #262626; color: #FFFFFF; } .label-white, .badge-white { background-color: #FFFFFF; color: #5E5E5E; } .label-white, .badge-disable { background-color: #2A2E36; color: #8B91A0; } /* TOOGLE SWICH */ .onoffswitch { position: relative; width: 64px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; } .onoffswitch-checkbox { display: none; } .onoffswitch-label { display: block; overflow: hidden; cursor: pointer; border: 2px solid #1ab394; border-radius: 2px; } .onoffswitch-inner { width: 200%; margin-left: -100%; -moz-transition: margin 0.3s ease-in 0s; -webkit-transition: margin 0.3s ease-in 0s; -o-transition: margin 0.3s ease-in 0s; transition: margin 0.3s ease-in 0s; } .onoffswitch-inner:before, .onoffswitch-inner:after { float: left; width: 50%; height: 20px; padding: 0; line-height: 20px; font-size: 12px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } .onoffswitch-inner:before { content: "ON"; padding-left: 10px; background-color: #1ab394; color: #FFFFFF; } .onoffswitch-inner:after { content: "OFF"; padding-right: 10px; background-color: #FFFFFF; color: #999999; text-align: right; } .onoffswitch-switch { width: 20px; margin: 0; background: #FFFFFF; border: 2px solid #1ab394; border-radius: 2px; position: absolute; top: 0; bottom: 0; right: 44px; -moz-transition: all 0.3s ease-in 0s; -webkit-transition: all 0.3s ease-in 0s; -o-transition: all 0.3s ease-in 0s; transition: all 0.3s ease-in 0s; } .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { margin-left: 0; } .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { right: 0; } .onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-inner:before { background-color: #919191; } .onoffswitch-checkbox:disabled + .onoffswitch-label, .onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-switch { border-color: #919191; } /* CHOSEN PLUGIN */ .chosen-container-single .chosen-single { background: #ffffff; box-shadow: none; -moz-box-sizing: border-box; border-radius: 2px; cursor: text; height: auto !important; margin: 0; min-height: 30px; overflow: hidden; padding: 4px 12px; position: relative; width: 100%; } .chosen-container-multi .chosen-choices li.search-choice { background: #f1f1f1; border: 1px solid #e5e6e7; border-radius: 2px; box-shadow: none; color: #333333; cursor: default; line-height: 13px; margin: 3px 0 3px 5px; padding: 3px 20px 3px 5px; position: relative; } /* Tags Input Plugin */ .bootstrap-tagsinput { border: 1px solid #e5e6e7; box-shadow: none; } /* PAGINATIN */ .pagination > .active > a, .pagination > .active > span, .pagination > .active > a:hover, .pagination > .active > span:hover, .pagination > .active > a:focus, .pagination > .active > span:focus { border-color: #DDDDDD; cursor: default; z-index: 2; } .pagination > li > a, .pagination > li > span { background-color: #FFFFFF; border: 1px solid #DDDDDD; color: inherit; float: left; line-height: 1.42857; margin-left: -1px; padding: 4px 10px; position: relative; text-decoration: none; } .page-item.active .page-link { background-color: #1ab394; border-color: #1ab394; } .page-link:focus { box-shadow: none; } .page-link:hover { color: #676a6c; } .pagination .footable-page.active a { background-color: #1ab394; border-color: #1ab394; color: white; } /* TOOLTIPS */ .tooltip-inner { background-color: #2f4050; } .tooltip.top .tooltip-arrow { border-top-color: #2f4050; } .tooltip.right .tooltip-arrow { border-right-color: #2f4050; } .tooltip.bottom .tooltip-arrow { border-bottom-color: #2f4050; } .tooltip.left .tooltip-arrow { border-left-color: #2f4050; } /* EASY PIE CHART*/ .easypiechart { position: relative; text-align: center; } .easypiechart .h2 { margin-left: 10px; margin-top: 10px; display: inline-block; } .easypiechart canvas { top: 0; left: 0; } .easypiechart .easypie-text { line-height: 1; position: absolute; top: 33px; width: 100%; z-index: 1; } .easypiechart img { margin-top: -4px; } .jqstooltip { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; } /* FULLCALENDAR */ .fc-state-default { background-color: #ffffff; background-image: none; background-repeat: repeat-x; box-shadow: none; color: #333333; text-shadow: none; } .fc-state-default { border: 1px solid; } .fc-button { color: inherit; border: 1px solid #e7eaec; cursor: pointer; display: inline-block; height: 1.9em; line-height: 1.9em; overflow: hidden; padding: 0 0.6em; position: relative; white-space: nowrap; } .fc-state-active { background-color: #1ab394; border-color: #1ab394; color: #ffffff; } .fc-header-title h2 { font-size: 16px; font-weight: 600; color: inherit; } .fc-content .fc-widget-header, .fc-content .fc-widget-content { border-color: #e7eaec; font-weight: normal; } .fc-border-separate tbody { background-color: #F8F8F8; } .fc-state-highlight { background: none repeat scroll 0 0 #FCF8E3; } .external-event { padding: 5px 10px; border-radius: 2px; cursor: pointer; margin-bottom: 5px; } .fc-ltr .fc-event-hori.fc-event-end, .fc-rtl .fc-event-hori.fc-event-start { border-radius: 2px; } .fc-event, .fc-agenda .fc-event-time, .fc-event a { padding: 4px 6px; background-color: #1ab394; /* background color */ border-color: #1ab394; /* border color */ } .fc-event-time, .fc-event-title { color: #717171; padding: 0 1px; } .ui-calendar .fc-event-time, .ui-calendar .fc-event-title { color: #fff; } .fc-event-container a.fc-event { color: #fff; } /* Chat */ .chat-activity-list .chat-element { border-bottom: 1px solid #e7eaec; } .chat-element:first-child { margin-top: 0; } .chat-element { padding-bottom: 15px; } .chat-element, .chat-element .media { margin-top: 15px; } .chat-element, .media-body { overflow: hidden; } .chat-element .media-body { display: block; width: auto; } .chat-element > .float-left { margin-right: 10px; } .chat-element img.rounded-circle, .dropdown-messages-box img.rounded-circle { width: 38px; height: 38px; } .chat-element .well { border: 1px solid #e7eaec; box-shadow: none; margin-top: 10px; margin-bottom: 5px; padding: 10px 20px; font-size: 11px; line-height: 16px; } .chat-element .actions { margin-top: 10px; } .chat-element .photos { margin: 10px 0; } .right.chat-element > .float-right { margin-left: 10px; } .chat-photo { max-height: 180px; border-radius: 4px; overflow: hidden; margin-right: 10px; margin-bottom: 10px; } .chat { margin: 0; padding: 0; list-style: none; } .chat li { margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px dotted #B3A9A9; } .chat li.left .chat-body { margin-left: 60px; } .chat li.right .chat-body { margin-right: 60px; } .chat li .chat-body p { margin: 0; color: #777777; } .panel .slidedown .glyphicon, .chat .glyphicon { margin-right: 5px; } .chat-panel .panel-body { height: 350px; overflow-y: scroll; } /* LIST GROUP */ a.list-group-item.active, a.list-group-item.active:hover, a.list-group-item.active:focus { background-color: #1ab394; border-color: #1ab394; color: #FFFFFF; z-index: 2; } .list-group-item-heading { margin-top: 10px; } .list-group-item-text { margin: 0 0 10px; color: inherit; font-size: 12px; line-height: inherit; } .no-padding .list-group-item { border-left: none; border-right: none; border-bottom: none; } .no-padding .list-group-item:first-child { border-left: none; border-right: none; border-bottom: none; border-top: none; } .no-padding .list-group { margin-bottom: 0; } .list-group-item { background-color: inherit; border: 1px solid #e7eaec; display: block; margin-bottom: -1px; padding: 10px 15px; position: relative; } .elements-list .list-group-item { border-left: none; border-right: none; padding: 0; } .elements-list .list-group-item:first-child { border-left: none; border-right: none; border-top: none !important; } .elements-list .list-group { margin-bottom: 0; } .elements-list a { color: inherit; } .elements-list .list-group-item a.active, .elements-list .list-group-item a:hover { background: #f3f3f4; color: inherit; border-color: #e7eaec; border-radius: 0; } .elements-list li.active { transition: none; } .elements-list .nav-link { padding: 15px 25px; } .element-detail-box { padding: 25px; } /* FLOT CHART */ .flot-chart { display: block; height: 200px; } .widget .flot-chart.dashboard-chart { display: block; height: 120px; margin-top: 40px; } .flot-chart.dashboard-chart { display: block; height: 180px; margin-top: 40px; } .flot-chart-content { width: 100%; height: 100%; } .flot-chart-pie-content { width: 200px; height: 200px; margin: auto; } .jqstooltip { position: absolute; display: block; left: 0; top: 0; visibility: hidden; background: #2b303a; background-color: rgba(43, 48, 58, 0.8); color: white; text-align: left; white-space: nowrap; z-index: 10000; padding: 5px 5px 5px 5px; min-height: 22px; border-radius: 3px; } .jqsfield { color: white; text-align: left; } .fh-150 { height: 150px; } .fh-200 { height: 200px; } .h-150 { min-height: 150px; } .h-200 { min-height: 200px; } .h-300 { min-height: 300px; } .w-150 { min-width: 150px; } .w-200 { min-width: 200px; } .w-300 { min-width: 300px; } .legendLabel { padding-left: 5px; } .stat-list li:first-child { margin-top: 0; } .stat-list { list-style: none; padding: 0; margin: 0; } .stat-percent { float: right; } .stat-list li { margin-top: 15px; position: relative; } /* DATATABLES */ table.dataTable thead .sorting, table.dataTable thead .sorting_asc:after, table.dataTable thead .sorting_desc, table.dataTable thead .sorting_asc_disabled, table.dataTable thead .sorting_desc_disabled { background: transparent; } .dataTables_wrapper { padding-bottom: 30px; } .dataTables_length { float: left; } .dataTables_filter label { margin-right: 5px; } .html5buttons { float: right; } .html5buttons a { border: 1px solid #e7eaec; background: #fff; color: #676a6c; box-shadow: none; padding: 6px 8px; font-size: 12px; } .html5buttons a:hover, .html5buttons a:focus:active { background-color: #eee; color: inherit; border-color: #d2d2d2; } div.dt-button-info { z-index: 100; } @media (max-width: 768px) { .html5buttons { float: none; margin-top: 10px; } .dataTables_length { float: none; } } /* CIRCLE */ .img-circle { border-radius: 50%; } .btn-circle { width: 30px; height: 30px; padding: 6px 0; border-radius: 15px; text-align: center; font-size: 12px; line-height: 1.428571429; } .btn-circle.btn-lg { width: 50px; height: 50px; padding: 10px 16px; border-radius: 25px; font-size: 18px; line-height: 1.33; } .btn-circle.btn-xl { width: 70px; height: 70px; padding: 10px 16px; border-radius: 35px; font-size: 24px; line-height: 1.33; } .show-grid [class^="col-"] { padding-top: 10px; padding-bottom: 10px; border: 1px solid #ddd; background-color: #eee !important; } .show-grid { margin: 15px 0; } /* ANIMATION */ .css-animation-box h1 { font-size: 44px; } .animation-efect-links a { padding: 4px 6px; font-size: 12px; } #animation_box { background-color: #f9f8f8; border-radius: 16px; width: 80%; margin: 0 auto; padding-top: 80px; } .animation-text-box { position: absolute; margin-top: 40px; left: 50%; margin-left: -100px; width: 200px; } .animation-text-info { position: absolute; margin-top: -60px; left: 50%; margin-left: -100px; width: 200px; font-size: 10px; } .animation-text-box h2 { font-size: 54px; font-weight: 600; margin-bottom: 5px; } .animation-text-box p { font-size: 12px; text-transform: uppercase; } /* PEACE */ .pace { -webkit-pointer-events: none; pointer-events: none; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .pace-inactive { display: none; } .pace .pace-progress { background: #1ab394; position: fixed; z-index: 2040; top: 0; right: 100%; width: 100%; height: 2px; } .pace-inactive { display: none; } /* WIDGETS */ .widget { border-radius: 5px; padding: 15px 20px; margin-bottom: 10px; margin-top: 10px; } .widget.style1 h2 { font-size: 30px; } .widget h2, .widget h3 { margin-top: 5px; margin-bottom: 0; } .widget-text-box { padding: 20px; border: 1px solid #e7eaec; background: #ffffff; } .widget-head-color-box { border-radius: 5px 5px 0 0; margin-top: 10px; } .widget .flot-chart { height: 100px; } .vertical-align div { display: inline-block; vertical-align: middle; } .vertical-align h2, .vertical-align h3 { margin: 0; } .todo-list { list-style: none outside none; margin: 0; padding: 0; font-size: 14px; } .todo-list.small-list { font-size: 12px; } .todo-list.small-list > li { background: #f3f3f4; border-left: none; border-right: none; border-radius: 4px; color: inherit; margin-bottom: 2px; padding: 6px 6px 6px 12px; } .todo-list.small-list .btn-xs, .todo-list.small-list .btn-group-xs > .btn { border-radius: 5px; font-size: 10px; line-height: 1.5; padding: 1px 2px 1px 5px; } .todo-list > li { background: #f3f3f4; border-left: 6px solid #e7eaec; border-right: 6px solid #e7eaec; border-radius: 4px; color: inherit; margin-bottom: 2px; padding: 10px; } .todo-list .handle { cursor: move; display: inline-block; font-size: 16px; margin: 0 5px; } .todo-list > li .label { font-size: 9px; margin-left: 10px; } .check-link { font-size: 16px; } .todo-completed { text-decoration: line-through; } .geo-statistic h1 { font-size: 36px; margin-bottom: 0; } .glyphicon.fa { font-family: "FontAwesome"; } /* INPUTS */ .inline { display: inline-block !important; } .input-s-sm { width: 120px; } .input-s { width: 200px; } .form-control { font-size: 0.9rem; } select.form-control:not([size]):not([multiple]) { height: 2.05rem; } .input-sm, .form-control-sm { height: 31px; } .input-s-lg { width: 250px; } .i-checks { padding-left: 0; } .form-control, .single-line { background-color: #FFFFFF; background-image: none; border: 1px solid #e5e6e7; border-radius: 1px; color: inherit; display: block; padding: 6px 12px; transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s; width: 100%; } .form-control:focus, .single-line:focus { border-color: #1ab394; } .has-success .form-control, .has-success .form-control:focus { border-color: #1ab394; } .has-warning .form-control, .has-warning .form-control:focus { border-color: #f8ac59; } .has-error .form-control, .has-error .form-control:focus { border-color: #ed5565; } .has-success .control-label { color: #1ab394; } .has-warning .control-label { color: #f8ac59; } .has-error .control-label { color: #ed5565; } .input-group-addon { background-color: #fff; border: 1px solid #E5E6E7; border-radius: 1px; color: inherit; font-size: 14px; font-weight: 400; line-height: 1; padding: 9px 12px 4px 12px; text-align: center; } .input-daterange .input-group-addon { margin: 0; } .input-group.date .input-group-addon { border-right: 0; } .spinner-buttons.input-group-btn .btn-xs { line-height: 1.13; } .spinner-buttons.input-group-btn { width: 20%; } .noUi-connect { background: none repeat scroll 0 0 #1ab394; box-shadow: none; } .slider_red .noUi-connect { background: none repeat scroll 0 0 #ed5565; box-shadow: none; } /* UI Sortable */ .ui-sortable .ibox-title { cursor: move; } .ui-sortable-placeholder { border: 1px dashed #cecece !important; visibility: visible !important; background: #e7eaec; } .ibox.ui-sortable-placeholder { margin: 0 0 23px !important; } /* SWITCHES */ .onoffswitch { position: relative; width: 54px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; } .onoffswitch-checkbox { display: none; } .onoffswitch-label { display: block; overflow: hidden; cursor: pointer; border: 2px solid #1AB394; border-radius: 3px; } .onoffswitch-inner { display: block; width: 200%; margin-left: -100%; -moz-transition: margin 0.3s ease-in 0s; -webkit-transition: margin 0.3s ease-in 0s; -o-transition: margin 0.3s ease-in 0s; transition: margin 0.3s ease-in 0s; } .onoffswitch-inner:before, .onoffswitch-inner:after { display: block; float: left; width: 50%; height: 16px; padding: 0; line-height: 16px; font-size: 10px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } .onoffswitch-inner:before { content: "ON"; padding-left: 7px; background-color: #1AB394; color: #FFFFFF; } .onoffswitch-inner:after { content: "OFF"; padding-right: 7px; background-color: #FFFFFF; color: #919191; text-align: right; } .onoffswitch-switch { display: block; width: 18px; margin: 0; background: #FFFFFF; border: 2px solid #1AB394; border-radius: 3px; position: absolute; top: 0; bottom: 0; right: 36px; -moz-transition: all 0.3s ease-in 0s; -webkit-transition: all 0.3s ease-in 0s; -o-transition: all 0.3s ease-in 0s; transition: all 0.3s ease-in 0s; } .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { margin-left: 0; } .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { right: 0; } /* jqGrid */ .ui-jqgrid { -moz-box-sizing: content-box; } .ui-jqgrid-btable { border-collapse: separate; } .ui-jqgrid-htable { border-collapse: separate; } .ui-jqgrid-titlebar { height: 40px; line-height: 15px; color: #676a6c; background-color: #F9F9F9; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); } .ui-jqgrid .ui-jqgrid-title { float: left; margin: 1.1em 1em 0.2em; } .ui-jqgrid .ui-jqgrid-titlebar { position: relative; border-left: 0 solid; border-right: 0 solid; border-top: 0 solid; } .ui-widget-header { background: none; background-image: none; background-color: #f5f5f6; text-transform: uppercase; border-top-left-radius: 0; border-top-right-radius: 0; } .ui-jqgrid tr.ui-row-ltr td { border-right-color: inherit; border-right-style: solid; border-right-width: 1px; text-align: left; border-color: #DDDDDD; background-color: inherit; } .ui-search-toolbar input[type="text"] { font-size: 12px; height: 15px; border: 1px solid #CCCCCC; border-radius: 0; } .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { background: #F9F9F9; border: 1px solid #DDDDDD; line-height: 15px; font-weight: bold; color: #676a6c; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); } .ui-widget-content { box-sizing: content-box; } .ui-icon-triangle-1-n { background-position: 1px -16px; } .ui-jqgrid tr.ui-search-toolbar th { border-top-width: 0 !important; border-top-color: inherit !important; border-top-style: ridge !important; } .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { background: #f5f5f5; border-collapse: separate; } .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { background: #f2fbff; } .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #dddddd; background: #ffffff; font-weight: normal; color: #212121; } .ui-jqgrid .ui-pg-input { font-size: inherit; width: 50px; border: 1px solid #CCCCCC; height: 15px; } .ui-jqgrid .ui-pg-selbox { display: block; font-size: 1em; height: 25px; line-height: 18px; margin: 0; width: auto; } .ui-jqgrid .ui-pager-control { position: relative; } .ui-jqgrid .ui-jqgrid-pager { height: 32px; position: relative; } .ui-pg-table .navtable .ui-corner-all { border-radius: 0; } .ui-jqgrid .ui-pg-button:hover { padding: 1px; border: 0; } .ui-jqgrid .loading { position: absolute; top: 45%; left: 45%; width: auto; height: auto; z-index: 101; padding: 6px; margin: 5px; text-align: center; font-weight: bold; display: none; border-width: 2px !important; font-size: 11px; } .ui-jqgrid .form-control { height: 10px; width: auto; display: inline; padding: 10px 12px; } .ui-jqgrid-pager { height: 32px; } .ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { border-top-left-radius: 0; } .ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { border-top-right-radius: 0; } .ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { border-bottom-left-radius: 0; } .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { border-bottom-right-radius: 0; } .ui-widget-content { border: 1px solid #ddd; } .ui-jqgrid .ui-jqgrid-titlebar { padding: 0; } .ui-jqgrid .ui-jqgrid-titlebar { border-bottom: 1px solid #ddd; } .ui-jqgrid tr.jqgrow td { padding: 6px; } .ui-jqdialog .ui-jqdialog-titlebar { padding: 10px 10px; } .ui-jqdialog .ui-jqdialog-title { float: none !important; } .ui-jqdialog > .ui-resizable-se { position: absolute; } /* Nestable list */ .dd { position: relative; display: block; margin: 0; padding: 0; list-style: none; font-size: 13px; line-height: 20px; } .dd-list { display: block; position: relative; margin: 0; padding: 0; list-style: none; } .dd-list .dd-list { padding-left: 30px; } .dd-collapsed .dd-list { display: none; } .dd-item, .dd-empty, .dd-placeholder { display: block; position: relative; margin: 0; padding: 0; min-height: 20px; font-size: 13px; line-height: 20px; } .dd-handle { display: block; margin: 5px 0; padding: 5px 10px; color: #333; text-decoration: none; border: 1px solid #e7eaec; background: #f5f5f5; -webkit-border-radius: 3px; border-radius: 3px; box-sizing: border-box; -moz-box-sizing: border-box; } .dd-handle span { font-weight: bold; } .dd-handle:hover { background: #f0f0f0; cursor: pointer; font-weight: bold; } .dd-item > button { display: block; position: relative; cursor: pointer; float: left; width: 25px; height: 20px; margin: 5px 0; padding: 0; text-indent: 100%; white-space: nowrap; overflow: hidden; border: 0; background: transparent; font-size: 12px; line-height: 1; text-align: center; font-weight: bold; } .dd-item > button:before { content: '+'; display: block; position: absolute; width: 100%; text-align: center; text-indent: 0; } .dd-item > button[data-action="collapse"]:before { content: '-'; } #nestable2 .dd-item > button { font-family: FontAwesome; height: 34px; width: 33px; color: #c1c1c1; } #nestable2 .dd-item > button:before { content: "\f067"; } #nestable2 .dd-item > button[data-action="collapse"]:before { content: "\f068"; } .dd-placeholder, .dd-empty { margin: 5px 0; padding: 0; min-height: 30px; background: #f2fbff; border: 1px dashed #b6bcbf; box-sizing: border-box; -moz-box-sizing: border-box; } .dd-empty { border: 1px dashed #bbb; min-height: 100px; background-color: #e5e5e5; 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); 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); 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); background-size: 60px 60px; background-position: 0 0, 30px 30px; } .dd-dragel { position: absolute; z-index: 9999; pointer-events: none; } .dd-dragel > .dd-item .dd-handle { margin-top: 0; } .dd-dragel .dd-handle { -webkit-box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1); box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1); } /** * Nestable Extras */ .nestable-lists { display: block; clear: both; padding: 30px 0; width: 100%; border: 0; border-top: 2px solid #ddd; border-bottom: 2px solid #ddd; } #nestable-menu { padding: 0; margin: 10px 0 20px 0; } #nestable-output, #nestable2-output { width: 100%; font-size: 0.75em; line-height: 1.333333em; font-family: open sans, lucida grande, lucida sans unicode, helvetica, arial, sans-serif; padding: 5px; box-sizing: border-box; -moz-box-sizing: border-box; } #nestable2 .dd-handle { color: inherit; border: 1px dashed #e7eaec; background: #f3f3f4; padding: 10px; } #nestable2 span.label { margin-right: 10px; } #nestable-output, #nestable2-output { font-size: 12px; padding: 25px; box-sizing: border-box; -moz-box-sizing: border-box; } /* CodeMirror */ .CodeMirror { border: 1px solid #eee; height: auto; } .CodeMirror-scroll { overflow-y: hidden; overflow-x: auto; } /* Google Maps */ .google-map { height: 300px; } /* Validation */ label.error { color: #cc5965; display: inline-block; margin-left: 5px; } .form-control.error { border: 1px dotted #cc5965; } /* ngGrid */ .gridStyle { border: 1px solid #d4d4d4; width: 100%; height: 400px; } .gridStyle2 { border: 1px solid #d4d4d4; width: 500px; height: 300px; } .ngH eaderCell { border-right: none; border-bottom: 1px solid #e7eaec; } .ngCell { border-right: none; } .ngTopPanel { background: #F5F5F6; } .ngRow.even { background: #f9f9f9; } .ngRow.selected { background: #EBF2F1; } .ngRow { border-bottom: 1px solid #e7eaec; } .ngCell { background-color: transparent; } .ngHeaderCell { border-right: none; } /* Toastr custom style */ #toast-container > div { -moz-box-shadow: 0 0 3px #999; -webkit-box-shadow: 0 0 3px #999; box-shadow: 0 0 3px #999; opacity: .9; -ms-filter: alpha(opacity=90); filter: alpha(opacity=90); } #toast-container > :hover { -moz-box-shadow: 0 0 4px #999; -webkit-box-shadow: 0 0 4px #999; box-shadow: 0 0 4px #999; opacity: 1; -ms-filter: alpha(opacity=100); filter: alpha(opacity=100); cursor: pointer; } .toast { background-color: #1ab394; border-color: #e7eaec; } .toast-success { background-color: #1ab394; } .toast-error { background-color: #ed5565; } .toast-info { background-color: #23c6c8; } .toast-warning { background-color: #f8ac59; } .toast-top-full-width { margin-top: 20px; } .toast-bottom-full-width { margin-bottom: 20px; } .toast { z-index: 3000; } .toast.toast-bootstrap { background-color: white; } .toast.toast-bootstrap .toast-body { background-color: #fbfbfb; font-size: .775rem; } /* Notifie */ .cg-notify-message.inspinia-notify { background: #fff; padding: 0; box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2); -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2); border: none; margin-top: 30px; color: inherit; } .inspinia-notify.alert-warning { border-left: 6px solid #f8ac59; } .inspinia-notify.alert-success { border-left: 6px solid #1c84c6; } .inspinia-notify.alert-danger { border-left: 6px solid #ed5565; } .inspinia-notify.alert-info { border-left: 6px solid #1ab394; } /* Image cropper style */ .img-container, .img-preview { overflow: hidden; text-align: center; width: 100%; } .img-preview-sm { height: 130px; width: 200px; } /* Forum styles */ .forum-post-container .media { margin: 10px 10px 10px 10px; padding: 20px 10px 20px 10px; border-bottom: 1px solid #f1f1f1; } .forum-avatar { float: left; margin-right: 20px; text-align: center; width: 110px; } .forum-avatar .rounded-circle { height: 48px; width: 48px; } .author-info { color: #676a6c; font-size: 11px; margin-top: 5px; text-align: center; } .forum-post-info { padding: 9px 12px 6px 12px; background: #f9f9f9; border: 1px solid #f1f1f1; } .media-body > .media { background: #f9f9f9; border-radius: 3px; border: 1px solid #f1f1f1; } .forum-post-container .media-body .photos { margin: 10px 0; } .forum-photo { max-width: 140px; border-radius: 3px; } .media-body > .media .forum-avatar { width: 70px; margin-right: 10px; } .media-body > .media .forum-avatar .rounded-circle { height: 38px; width: 38px; } .mid-icon { font-size: 66px; } .forum-item { margin: 10px 0; padding: 10px 0 20px; border-bottom: 1px solid #f1f1f1; } .views-number { font-size: 24px; line-height: 18px; font-weight: 400; } .forum-container, .forum-post-container { padding: 30px !important; } .forum-item small { color: #999; } .forum-item .forum-sub-title { color: #999; margin-left: 50px; } .forum-title { margin: 15px 0 15px 0; } .forum-info { text-align: center; } .forum-desc { color: #999; } .forum-icon { float: left; width: 30px; margin-right: 20px; text-align: center; } a.forum-item-title { color: inherit; display: block; font-size: 18px; font-weight: 600; } a.forum-item-title:hover { color: inherit; } .forum-icon .fa { font-size: 30px; margin-top: 8px; color: #9b9b9b; } .forum-item.active .fa { color: #1ab394; } .forum-item.active a.forum-item-title { color: #1ab394; } @media (max-width: 992px) { .forum-info { margin: 15px 0 10px 0; /* Comment this is you want to show forum info in small devices */ display: none; } .forum-desc { float: none !important; } } /* New Timeline style */ .vertical-container { /* 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 */ width: 90%; max-width: 1170px; margin: 0 auto; } .vertical-container::after { /* clearfix */ content: ''; display: table; clear: both; } #vertical-timeline { position: relative; padding: 0; margin-top: 2em; margin-bottom: 2em; } #vertical-timeline::before { content: ''; position: absolute; top: 0; left: 18px; height: 100%; width: 4px; background: #f1f1f1; } .vertical-timeline-content .btn { float: right; } #vertical-timeline.light-timeline:before { background: #e7eaec; } .dark-timeline .vertical-timeline-content:before { border-color: transparent #f5f5f5 transparent transparent; } .dark-timeline.center-orientation .vertical-timeline-content:before { border-color: transparent transparent transparent #f5f5f5; } .dark-timeline .vertical-timeline-block:nth-child(2n) .vertical-timeline-content:before, .dark-timeline.center-orientation .vertical-timeline-block:nth-child(2n) .vertical-timeline-content:before { border-color: transparent #f5f5f5 transparent transparent; } .dark-timeline .vertical-timeline-content, .dark-timeline.center-orientation .vertical-timeline-content { background: #f5f5f5; } @media only screen and (min-width: 1170px) { #vertical-timeline.center-orientation { margin-top: 3em; margin-bottom: 3em; } #vertical-timeline.center-orientation:before { left: 50%; margin-left: -2px; } } @media only screen and (max-width: 1170px) { .center-orientation.dark-timeline .vertical-timeline-content:before { border-color: transparent #f5f5f5 transparent transparent; } } .vertical-timeline-block { position: relative; margin: 2em 0; } .vertical-timeline-block:after { content: ""; display: table; clear: both; } .vertical-timeline-block:first-child { margin-top: 0; } .vertical-timeline-block:last-child { margin-bottom: 0; } @media only screen and (min-width: 1170px) { .center-orientation .vertical-timeline-block { margin: 4em 0; } .center-orientation .vertical-timeline-block:first-child { margin-top: 0; } .center-orientation .vertical-timeline-block:last-child { margin-bottom: 0; } } .vertical-timeline-icon { position: absolute; top: 0; left: 0; width: 40px; height: 40px; border-radius: 50%; font-size: 16px; border: 3px solid #f1f1f1; text-align: center; } .vertical-timeline-icon i { display: block; width: 24px; height: 24px; position: relative; left: 50%; top: 50%; margin-left: -12px; margin-top: -9px; } @media only screen and (min-width: 1170px) { .center-orientation .vertical-timeline-icon { width: 50px; height: 50px; left: 50%; margin-left: -25px; -webkit-transform: translateZ(0); -webkit-backface-visibility: hidden; font-size: 19px; } .center-orientation .vertical-timeline-icon i { margin-left: -12px; margin-top: -10px; } .center-orientation .cssanimations .vertical-timeline-icon.is-hidden { visibility: hidden; } } .vertical-timeline-content { position: relative; margin-left: 60px; background: white; border-radius: 0.25em; padding: 1em; } .vertical-timeline-content:after { content: ""; display: table; clear: both; } .vertical-timeline-content h2 { font-weight: 400; margin-top: 4px; } .vertical-timeline-content p { margin: 1em 0; line-height: 1.6; } .vertical-timeline-content .vertical-date { float: left; font-weight: 500; } .vertical-date small { color: #1ab394; font-weight: 400; } .vertical-timeline-content::before { content: ''; position: absolute; top: 16px; right: 100%; height: 0; width: 0; border: 7px solid transparent; border-right: 7px solid white; } @media only screen and (min-width: 768px) { .vertical-timeline-content h2 { font-size: 18px; } .vertical-timeline-content p { font-size: 13px; } } @media only screen and (min-width: 1170px) { .center-orientation .vertical-timeline-content { margin-left: 0; padding: 1.6em; width: 45%; } .center-orientation .vertical-timeline-content::before { top: 24px; left: 100%; border-color: transparent; border-left-color: white; } .center-orientation .vertical-timeline-content .btn { float: left; } .center-orientation .vertical-timeline-content .vertical-date { position: absolute; width: 100%; left: 122%; top: 2px; font-size: 14px; } .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content { float: right; } .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content::before { top: 24px; left: auto; right: 100%; border-color: transparent; border-right-color: white; } .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content .btn { float: right; } .center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content .vertical-date { left: auto; right: 122%; text-align: right; } .center-orientation .cssanimations .vertical-timeline-content.is-hidden { visibility: hidden; } } /* Tabs */ .tabs-container .panel-body { background: #fff; border: 1px solid #e7eaec; border-radius: 2px; padding: 20px; position: relative; } .tabs-container .nav-tabs > li.active > a, .tabs-container .nav-tabs > li.active > a:hover, .tabs-container .nav-tabs > li.active > a:focus { border: 1px solid #e7eaec; border-bottom-color: transparent; background-color: #fff; } .tabs-container .nav-tabs > li { float: left; margin-bottom: -1px; } .tabs-container .tab-pane .panel-body { border-top: none; } .tabs-container .nav-tabs > li.active > a, .tabs-container .nav-tabs > li.active > a:hover, .tabs-container .nav-tabs > li.active > a:focus { border: 1px solid #e7eaec; border-bottom-color: transparent; } .tabs-container .nav-tabs { border-bottom: 1px solid #e7eaec; } .tabs-container .tab-pane .panel-body { border-top: none; } .tabs-container .tabs-left .tab-pane .panel-body, .tabs-container .tabs-right .tab-pane .panel-body { border-top: 1px solid #e7eaec; } .tabs-container .tabs-below > .nav-tabs, .tabs-container .tabs-right > .nav-tabs, .tabs-container .tabs-left > .nav-tabs { border-bottom: 0; } .tabs-container .tabs-left .panel-body { position: static; } .tabs-container .tabs-left > .nav-tabs, .tabs-container .tabs-right > .nav-tabs { width: 20%; } .tabs-container .tabs-left .panel-body { width: 80%; margin-left: 20%; } .tabs-container .tabs-right .panel-body { width: 80%; margin-right: 20%; } .tabs-container .tab-content > .tab-pane, .tabs-container .pill-content > .pill-pane { display: none; } .tabs-container .tab-content > .active, .tabs-container .pill-content > .active { display: block; } .tabs-container .tabs-below > .nav-tabs { border-top: 1px solid #e7eaec; } .tabs-container .tabs-below > .nav-tabs > li { margin-top: -1px; margin-bottom: 0; } .tabs-container .tabs-below > .nav-tabs > li > a { -webkit-border-radius: 0 0 4px 4px; -moz-border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px; } .tabs-container .tabs-below > .nav-tabs > li > a:hover, .tabs-container .tabs-below > .nav-tabs > li > a:focus { border-top-color: #e7eaec; border-bottom-color: transparent; } .tabs-container .tabs-left > .nav-tabs > li, .tabs-container .tabs-right > .nav-tabs > li { float: none; word-break: break-word; width: 100%; } .tabs-container .tabs-left > .nav-tabs > li > a, .tabs-container .tabs-right > .nav-tabs > li > a { margin-right: 0; margin-bottom: 3px; } .tabs-container .tabs-left > .nav-tabs { float: left; margin-right: 19px; } .tabs-container .tabs-left > .nav-tabs > li > a { margin-right: -1px; -webkit-border-radius: 4px 0 0 4px; -moz-border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px; } .tabs-container .tabs-left > .nav-tabs a.active, .tabs-container .tabs-left > .nav-tabs a.active:hover, .tabs-container .tabs-left > .nav-tabs a.active:focus { border-color: #e7eaec transparent #e7eaec #e7eaec; } .tabs-container .tabs-right > .nav-tabs { float: right; margin-left: 19px; } .tabs-container .tabs-right > .nav-tabs > li > a { margin-left: -1px; -webkit-border-radius: 0 4px 4px 0; -moz-border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0; } .tabs-container .tabs-right > .nav-tabs a.active, .tabs-container .tabs-right > .nav-tabs a.active:hover, .tabs-container .tabs-right > .nav-tabs a.active:focus { border-color: #e7eaec #e7eaec #e7eaec transparent; z-index: 1; } .tabs-container .tabs-right > .nav-tabs li { z-index: 1; } .nav-tabs .nav-link:not(.active):focus, .nav-tabs .nav-link:not(.active):hover { border-color: transparent; } @media (max-width: 767px) { .tabs-container .nav-tabs > li { float: none !important; } .tabs-container .nav-tabs > li.active > a { border-bottom: 1px solid #e7eaec !important; margin: 0; } } /* jsvectormap */ .jvectormap-container { width: 100%; height: 100%; position: relative; overflow: hidden; } .jvectormap-tip { position: absolute; display: none; border: solid 1px #CDCDCD; border-radius: 3px; background: #292929; color: white; font-family: sans-serif, Verdana; font-size: smaller; padding: 5px; } .jvectormap-zoomin, .jvectormap-zoomout, .jvectormap-goback { position: absolute; left: 10px; border-radius: 3px; background: #1ab394; padding: 3px; color: white; cursor: pointer; line-height: 10px; text-align: center; box-sizing: content-box; } .jvectormap-zoomin, .jvectormap-zoomout { width: 10px; height: 10px; } .jvectormap-zoomin { top: 10px; } .jvectormap-zoomout { top: 30px; } .jvectormap-goback { bottom: 10px; z-index: 1000; padding: 6px; } .jvectormap-spinner { position: absolute; left: 0; top: 0; right: 0; bottom: 0; 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==); } .jvectormap-legend-title { font-weight: bold; font-size: 14px; text-align: center; } .jvectormap-legend-cnt { position: absolute; } .jvectormap-legend-cnt-h { bottom: 0; right: 0; } .jvectormap-legend-cnt-v { top: 0; right: 0; } .jvectormap-legend { background: black; color: white; border-radius: 3px; } .jvectormap-legend-cnt-h .jvectormap-legend { float: left; margin: 0 10px 10px 0; padding: 3px 3px 1px 3px; } .jvectormap-legend-cnt-h .jvectormap-legend .jvectormap-legend-tick { float: left; } .jvectormap-legend-cnt-v .jvectormap-legend { margin: 10px 10px 0 0; padding: 3px; } .jvectormap-legend-cnt-h .jvectormap-legend-tick { width: 40px; } .jvectormap-legend-cnt-h .jvectormap-legend-tick-sample { height: 15px; } .jvectormap-legend-cnt-v .jvectormap-legend-tick-sample { height: 20px; width: 20px; display: inline-block; vertical-align: middle; } .jvectormap-legend-tick-text { font-size: 12px; } .jvectormap-legend-cnt-h .jvectormap-legend-tick-text { text-align: center; } .jvectormap-legend-cnt-v .jvectormap-legend-tick-text { display: inline-block; vertical-align: middle; line-height: 20px; padding-left: 3px; } /*Slick Carousel */ .slick-prev:before, .slick-next:before { color: #1ab394 !important; } /* Payments */ .payment-card { background: #ffffff; padding: 20px; margin-bottom: 25px; border: 1px solid #e7eaec; } .payment-icon-big { font-size: 60px; color: #d1dade; } .payments-method.panel-group .panel + .panel { margin-top: -1px; } .payments-method .panel-heading { padding: 15px; background-color: #f3f3f4; } .payments-method .panel-default { border: 1px solid #e7eaec; } .payments-method .panel { border-radius: 0; } .payments-method .panel-heading h5 { margin-bottom: 5px; } .payments-method .panel-heading i { font-size: 26px; } /* Select2 custom styles */ .select2-container--bootstrap4 .select2-results__option--highlighted, .select2-container--bootstrap4 .select2-results__option--highlighted.select2-results__option[aria-selected=true] { background-color: #1ab394; } .select2-container--bootstrap4 .select2-selection, .select2-container--bootstrap4 .select2-dropdown.select2-dropdown--above, .select2-container--bootstrap4 .select2-dropdown { border-color: #e7eaec; } .select2-container :focus { outline: none; } .select2-container--bootstrap4.select2-container--focus .select2-selection { box-shadow: none; border-color: #1ab394; } .select2-container--bootstrap4 .select2-selection__clear { margin-top: 0.9em; } /* Tour */ .tour-tour .btn.btn-default { background-color: #ffffff; border: 1px solid #d2d2d2; color: inherit; } .tour-step-backdrop { z-index: 2101; } .tour-backdrop { z-index: 2100; opacity: .7; } .popover[class*=tour-] { z-index: 2100; } .popover-header { margin-top: 0; } body.tour-open .animated { animation-fill-mode: initial; } .tour-tour .btn.btn-secondary { background-color: #ffffff; border: 1px solid #d2d2d2; color: inherit; } /* Resizable */ .resizable-panels .ibox { clear: none; margin: 10px; float: left; overflow: hidden; min-height: 150px; min-width: 150px; } .resizable-panels .ibox .ibox-content { height: calc(100% - 49px); } .ui-resizable-helper { background: rgba(211, 211, 211, 0.4); } /* Wizard step fix */ .wizard > .content > .body { position: relative; } /* PDF js style */ .pdf-toolbar { max-width: 600px; margin: 0 auto; } .pdf-toolbar .input-group { width: 100px; } /* Dropzone */ .dropzone { min-height: 140px; border: 1px dashed #1ab394; background: white; padding: 20px 20px; } .dropzone .dz-message { font-size: 16px; } /* Activity stream */ .stream { position: relative; padding: 10px 0; } .stream:first-child .stream-badge:before { top: 10px; } .stream:last-child .stream-badge:before { height: 30px; } .stream .stream-badge { width: 50px; } .stream .stream-badge i { border: 1px solid #e7eaec; border-radius: 50%; padding: 6px; color: #808486; position: absolute; background-color: #ffffff; left: 8px; } .stream .stream-badge i.fa-circle { color: #ced0d1; } .stream .stream-badge i.bg-success { color: #ffffff; background-color: #1c84c6; border-color: #1c84c6; } .stream .stream-badge i.bg-primary { color: #ffffff; background-color: #1ab394; border-color: #1ab394; } .stream .stream-badge i.bg-warning { color: #ffffff; background-color: #f8ac59; border-color: #f8ac59; } .stream .stream-badge i.bg-info { color: #ffffff; background-color: #23c6c8; border-color: #23c6c8; } .stream .stream-badge i.bg-danger { color: #ffffff; background-color: #ed5565; border-color: #ed5565; } .stream .stream-badge:before { content: ''; width: 1px; background-color: #e7eaec; position: absolute; top: 0; bottom: 0; left: 20px; } .stream .stream-info { font-size: 12px; margin-bottom: 5px; } .stream .stream-info img { border-radius: 50%; width: 18px; height: 18px; margin-right: 2px; margin-top: -4px; } .stream .stream-info .date { color: #9a9d9f; font-size: 80%; } .stream .stream-panel { margin-left: 55px; } .stream-small { margin: 10px 0; } .stream-small .label { padding: 2px 6px; margin-right: 2px; } /* Touch Spin */ .bootstrap-touchspin-postfix.input-group-addon { padding: inherit; } .bootstrap-touchspin-postfix .input-group-text { background-color: inherit; line-height: 1; border: none; } /* Code */ pre { display: block; padding: 9.5px; margin: 0 0 10px; font-size: 13px; line-height: 1.42857143; color: #333; word-break: break-all; word-wrap: break-word; background-color: #eff2f3; border: 1px solid #d1dade; border-radius: 2px; } code, kbd, pre, samp { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } .sidebar-panel { width: 220px; background: #ebebed; padding: 10px 20px; position: absolute; right: 0; height: calc(100% - 62px); } .sidebar-panel .feed-element img.rounded-circle { width: 32px; height: 32px; } .sidebar-panel .feed-element, .media-body, .sidebar-panel p { font-size: 12px; } .sidebar-panel .feed-element { margin-top: 20px; padding-bottom: 0; } .sidebar-panel .list-group { margin-bottom: 10px; } .sidebar-panel .list-group .list-group-item { padding: 5px 0; font-size: 12px; border: 0; } .sidebar-content .wrapper, .wrapper.sidebar-content { padding-right: 230px !important; } .body-small .sidebar-content .wrapper, .body-small .wrapper.sidebar-content { padding-right: 20px !important; } #right-sidebar { background-color: #fff; border-left: 1px solid #e7eaec; border-top: 1px solid #e7eaec; overflow: hidden; position: fixed; top: 60px; width: 260px !important; z-index: 1009; bottom: 0; right: -260px; } #right-sidebar.sidebar-open { right: 0; } #right-sidebar.sidebar-open.sidebar-top { top: 0; border-top: none; } .sidebar-container ul.nav-tabs { border: none; } .sidebar-container ul.nav-tabs.navs-4 li { width: 25%; } .sidebar-container ul.nav-tabs.navs-3 li { width: 33.3333%; } .sidebar-container ul.nav-tabs.navs-2 li { width: 50%; } .sidebar-container ul.nav-tabs li { border: none; } .sidebar-container ul.nav-tabs li a { border: none; padding: 12px 10px; margin: 0; border-radius: 0; background: #2f4050; color: #fff; text-align: center; border-right: 1px solid #334556; } .sidebar-container ul.nav-tabs li.active a { border: none; background: #f9f9f9; color: #676a6c; font-weight: bold; } .sidebar-container .nav-tabs > li.active > a:hover, .sidebar-container .nav-tabs > li.active > a:focus { border: none; } .sidebar-container ul.sidebar-list { margin: 0; padding: 0; } .sidebar-container ul.sidebar-list li { border-bottom: 1px solid #e7eaec; padding: 15px 20px; list-style: none; font-size: 12px; } .sidebar-container .sidebar-message:nth-child(2n+2) { background: #f9f9f9; } .sidebar-container ul.sidebar-list li a { text-decoration: none; color: inherit; } .sidebar-container .sidebar-content { padding: 15px 20px; font-size: 12px; } .sidebar-container .sidebar-title { background: #f9f9f9; padding: 20px; border-bottom: 1px solid #e7eaec; } .sidebar-container .sidebar-title h3 { margin-bottom: 3px; padding-left: 2px; } .sidebar-container .tab-content h4 { margin-bottom: 5px; } .sidebar-container .sidebar-message > a > .float-left { margin-right: 10px; } .sidebar-container .sidebar-message > a { text-decoration: none; color: inherit; } .sidebar-container .sidebar-message { padding: 15px 20px; } .sidebar-container .sidebar-message .media-body { display: block; width: auto; } .sidebar-container .sidebar-message .message-avatar { height: 38px; width: 38px; border-radius: 50%; } .sidebar-container .setings-item { padding: 15px 20px; border-bottom: 1px solid #e7eaec; } body { font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif; background-color: #2f4050; font-size: 13px; color: #676a6c; overflow-x: hidden; } html, body { height: 100%; } body.full-height-layout #wrapper, body.full-height-layout #page-wrapper { height: 100%; } #page-wrapper { min-height: 100vh; } body.boxed-layout { background: url('patterns/shattered.png'); } body.boxed-layout #wrapper { background-color: #2f4050; max-width: 1200px; margin: 0 auto; -webkit-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75); -moz-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75); box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75); } .top-navigation.boxed-layout #wrapper, .boxed-layout #wrapper.top-navigation { max-width: 1300px !important; } .block { display: block; } .clear { display: block; overflow: hidden; } a { cursor: pointer; } a:hover, a:focus { text-decoration: none; } .border-bottom { border-bottom: 1px solid #e7eaec !important; } .font-bold { font-weight: 600; } .font-normal { font-weight: 400; } .text-uppercase { text-transform: uppercase; } .font-italic { font-style: italic; } .b-r { border-right: 1px solid #e7eaec; } .hr-line-dashed { border-top: 1px dashed #e7eaec; color: #ffffff; background-color: #ffffff; height: 1px; margin: 20px 0; } .hr-line-solid { border-bottom: 1px solid #e7eaec; background-color: rgba(0, 0, 0, 0); border-style: solid !important; margin-top: 15px; margin-bottom: 15px; } video { width: 100% !important; height: auto !important; } /* GALLERY */ .gallery > .row > div { margin-bottom: 15px; } .fancybox img { margin-bottom: 5px; /* Only for demo */ width: 24%; } /* Summernote text editor */ .note-editor { height: auto !important; } .note-editor.fullscreen { z-index: 2050; } .note-editor.note-frame.fullscreen { z-index: 2020; } .note-editor.note-frame .note-editing-area .note-editable { color: #676a6c; padding: 15px; } .note-editor.note-frame { border: none; } .note-editor.panel { margin-bottom: 0; } /* MODAL */ .modal-content { background-clip: padding-box; background-color: #FFFFFF; border: 1px solid rgba(0, 0, 0, 0); border-radius: 4px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); outline: 0 none; position: relative; } .modal-dialog { z-index: 2200; } .modal-body { padding: 20px 30px 30px 30px; } .inmodal .modal-body { background: #f8fafb; } .inmodal .modal-header { padding: 30px 15px; text-align: center; display: block; } .animated.modal.fade .modal-dialog { -webkit-transform: none; -ms-transform: none; -o-transform: none; transform: none; } .inmodal .modal-title { font-size: 26px; } .inmodal .modal-icon { font-size: 84px; color: #e2e3e3; } .modal-footer { margin-top: 0; } /* WRAPPERS */ #wrapper { width: 100%; overflow-x: hidden; display: -ms-flex; display: -webkit-flex; display: flex; } .wrapper { padding: 0 20px; } .wrapper-content { padding: 20px 10px 40px; } #page-wrapper { padding: 0 15px; position: relative !important; flex-shrink: 1; width: calc(100% - 220px); } @media (min-width: 768px) { #page-wrapper { position: inherit; } } .title-action { text-align: right; padding-top: 30px; } .ibox-content h1, .ibox-content h2, .ibox-content h3, .ibox-content h4, .ibox-content h5, .ibox-title h1, .ibox-title h2, .ibox-title h3, .ibox-title h4, .ibox-title h5 { margin-top: 5px; } ul.unstyled, ol.unstyled { list-style: none outside none; margin-left: 0; } .big-icon { font-size: 160px !important; color: #e5e6e7; } /* FOOTER */ .footer { background: none repeat scroll 0 0 white; border-top: 1px solid #e7eaec; bottom: 0; left: 0; padding: 10px 20px; position: absolute; right: 0; } .footer.fixed_full { position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; padding: 10px 20px; background: white; border-top: 1px solid #e7eaec; } .footer.fixed { position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; padding: 10px 20px; background: white; border-top: 1px solid #e7eaec; margin-left: 220px; } body.mini-navbar .footer.fixed, body.body-small.mini-navbar .footer.fixed { margin: 0 0 0 70px; } body.mini-navbar.fixed-sidebar .footer.fixed { margin: 0; } body.mini-navbar.canvas-menu .footer.fixed, body.canvas-menu .footer.fixed { margin: 0 !important; } body.fixed-sidebar.body-small.mini-navbar .footer.fixed { margin: 0 0 0 220px; } body.body-small .footer.fixed { margin-left: 0; } /* PANELS */ .panel-title > .small, .panel-title > .small > a, .panel-title > a, .panel-title > small, .panel-title > small > a { color: inherit; } .page-heading { border-top: 0; padding: 0 10px 20px 10px; } .panel-heading h1, .panel-heading h2 { margin-bottom: 5px; } .panel-body { padding: 15px; } /* Bootstrap 3.3.x panels */ .panel { margin-bottom: 20px; background-color: #fff; border: 1px solid transparent; border-radius: 4px; } .panel-heading { color: white; padding: 10px 15px; border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; } .panel-footer { padding: 10px 15px; border-top: 1px solid #e7eaec; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .panel-default > .panel-heading { color: #333; background-color: #f5f5f5; border-color: #e7eaec; } .panel-default { border-color: #e7eaec; } .panel-group .panel + .panel { margin-top: 5px; } .panel-group .panel { margin-bottom: 0; border-radius: 4px; } /* TABLES */ .table > caption + thead > tr:first-child > td, .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > th, .table > thead:first-child > tr:first-child > td, .table > thead:first-child > tr:first-child > th { border-top: 0; } .table-bordered { border: 1px solid #EBEBEB; } .table-bordered > thead > tr > th, .table-bordered > thead > tr > td { background-color: #F5F5F6; border-bottom-width: 1px; } .table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td { border: 1px solid #e7e7e7; } .table > thead > tr > th { border-bottom: 1px solid #DDDDDD; vertical-align: bottom; } .table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { border-top: 1px solid #e7eaec; line-height: 1.42857; padding: 8px; vertical-align: top; } /* PANELS */ .panel.blank-panel { background: none; margin: 0; } .blank-panel .panel-heading { padding-bottom: 0; } .nav-tabs > li > a { color: #A7B1C2; font-weight: 600; padding: 10px 20px 10px 25px; } .nav-tabs > li > a:hover, .nav-tabs > li > a:focus { color: #676a6c; } .ui-tab .tab-content { padding: 20px 0; } /* GLOBAL */ .no-padding { padding: 0 !important; } .no-borders { border: none !important; } .no-margins { margin: 0 !important; } .no-top-border { border-top: 0 !important; } .ibox-content.text-box { padding-bottom: 0; padding-top: 15px; } .border-left-right { border-left: 1px solid #e7eaec; border-right: 1px solid #e7eaec; } .border-top-bottom { border-top: 1px solid #e7eaec; border-bottom: 1px solid #e7eaec; } .border-left { border-left: 1px solid #e7eaec; } .border-right { border-right: 1px solid #e7eaec; } .border-top { border-top: 1px solid #e7eaec; } .border-bottom { border-bottom: 1px solid #e7eaec; } .border-size-sm { border-width: 3px; } .border-size-md { border-width: 6px; } .border-size-lg { border-width: 9px; } .border-size-xl { border-width: 12px; } .full-width { width: 100% !important; } .link-block { font-size: 12px; padding: 10px; } .nav.navbar-top-links .link-block a { font-size: 12px; } .navbar-top-links { text-align: right; } .link-block a { font-size: 10px; color: inherit; } body.mini-navbar .branding { display: none; } img.circle-border { border: 6px solid #FFFFFF; border-radius: 50%; } .branding { float: left; color: #FFFFFF; font-size: 18px; font-weight: 600; padding: 17px 20px; text-align: center; background-color: #1ab394; } .login-panel { margin-top: 25%; } .icons-box h3 { margin-top: 10px; margin-bottom: 10px; } .icons-box .infont a i { font-size: 25px; display: block; color: #676a6c; } .icons-box .infont a { color: #a6a8a9; } .icons-box .infont a { padding: 10px; margin: 1px; display: block; } .ui-draggable .ibox-title { cursor: move; } .breadcrumb { background-color: #ffffff; padding: 0; margin-bottom: 0; } .breadcrumb > li a { color: inherit; } .breadcrumb > .active { color: inherit; } code { background-color: #F9F2F4; border-radius: 4px; color: #ca4440; font-size: 90%; padding: 2px 4px; white-space: nowrap; } .ibox { clear: both; margin-bottom: 25px; margin-top: 0; padding: 0; } .ibox.collapsed .ibox-content { display: none; } .ibox.collapsed .fa.fa-chevron-up:before { content: "\f078"; } .ibox.collapsed .fa.fa-chevron-down:before { content: "\f077"; } .ibox:after, .ibox:before { display: table; } .ibox-title { background-color: #ffffff; border-color: #e7eaec; border-image: none; border-style: solid solid none; border-width: 1px; color: inherit; margin-bottom: 0; padding: 15px 90px 8px 15px; min-height: 48px; position: relative; clear: both; -webkit-border-radius: 3px 3px 0 0; -moz-border-radius: 3px 3px 0 0; border-radius: 2px 2px 0 0; } .ibox-content { background-color: #ffffff; color: inherit; padding: 15px 20px 20px 20px; border-color: #e7eaec; border-image: none; border-style: solid; border-width: 1px; } .ibox-footer { color: inherit; border-top: 1px solid #e7eaec; font-size: 90%; background: #ffffff; padding: 10px 15px; } table.table-mail tr td { padding: 12px; } .table-mail .check-mail { padding-left: 20px; } .table-mail .mail-date { padding-right: 20px; } .star-mail, .check-mail { width: 40px; } .unread td a, .unread td { font-weight: 600; color: inherit; } .read td a, .read td { font-weight: normal; color: inherit; } .unread td { background-color: #f9f8f8; } .ibox-content { clear: both; } .ibox-heading { background-color: #f3f6fb; border-bottom: none; } .ibox-heading h3 { font-weight: 200; font-size: 24px; } .ibox-title h5 { display: inline-block; font-size: 14px; margin: 0 0 7px; padding: 0; text-overflow: ellipsis; float: none; } .ibox-title .label { margin-left: 4px; } .ibox-title .pull-right { position: absolute; right: 15px; top: 15px; } .ibox-tools { display: block; float: none; margin-top: 0; position: absolute; top: 15px; right: 15px; padding: 0; text-align: right; } .ibox-tools a { cursor: pointer; margin-left: 5px; color: #c4c4c4 !important; } .ibox-tools a.btn-primary { color: #fff !important; } .ibox-tools .dropdown-menu > li > a { padding: 4px 10px; font-size: 12px; color: #676a6c !important; } .ibox .ibox-tools.open > .dropdown-menu { left: auto; right: 0; } .ibox-tools .dropdown-toggle::after { display: none; } .dropdown-item { width: auto; } .dropdown-item.active, .dropdown-item:active { background-color: inherit; color: inherit; } /* BACKGROUNDS */ .gray-bg, .bg-muted { background-color: #f3f3f4; } .white-bg { background-color: #ffffff; } .blue-bg, .bg-success { background-color: #1c84c6 !important; color: #ffffff; } .navy-bg, .bg-primary { background-color: #1ab394 !important; color: #ffffff; } .lazur-bg, .bg-info { background-color: #23c6c8 !important; color: #ffffff; } .yellow-bg, .bg-warning { background-color: #f8ac59 !important; color: #ffffff; } .red-bg, .bg-danger { background-color: #ed5565 !important; color: #ffffff; } .black-bg { background-color: #262626; } .panel-primary { border-color: #1ab394; } .panel-primary > .panel-heading { background-color: #1ab394; border-color: #1ab394; } .panel-success { border-color: #1c84c6; } .panel-success > .panel-heading { background-color: #1c84c6; border-color: #1c84c6; color: #ffffff; } .panel-info { border-color: #23c6c8; } .panel-info > .panel-heading { background-color: #23c6c8; border-color: #23c6c8; color: #ffffff; } .panel-warning { border-color: #f8ac59; } .panel-warning > .panel-heading { background-color: #f8ac59; border-color: #f8ac59; color: #ffffff; } .panel-danger { border-color: #ed5565; } .panel-danger > .panel-heading { background-color: #ed5565; border-color: #ed5565; color: #ffffff; } .progress-bar { background-color: #1ab394; } .progress-small, .progress-small .progress-bar { height: 10px; } .progress-small, .progress-mini { margin-top: 5px; } .progress-mini, .progress-mini .progress-bar { height: 5px; margin-bottom: 0; } .progress-bar-navy-light { background-color: #3dc7ab; } .progress-bar-success { background-color: #1c84c6; } .progress-bar-info { background-color: #23c6c8; } .progress-bar-warning { background-color: #f8ac59; } .progress-bar-danger { background-color: #ed5565; } .panel-title { font-size: inherit; } .jumbotron { border-radius: 6px; padding: 40px; } .jumbotron h1 { margin-top: 0; } /* COLORS */ .text-navy { color: #1ab394 !important; } .text-primary { color: inherit !important; } .text-success { color: #1c84c6 !important; } .text-info { color: #23c6c8 !important; } .text-warning { color: #f8ac59 !important; } .text-danger { color: #ed5565 !important; } .text-muted { color: #888888 !important; } .text-white { color: #ffffff; } .simple_tag { background-color: #f3f3f4; border: 1px solid #e7eaec; border-radius: 2px; color: inherit; font-size: 10px; margin-right: 5px; margin-top: 5px; padding: 5px 12px; display: inline-block; } .img-shadow { -webkit-box-shadow: 0 0 3px 0 #919191; -moz-box-shadow: 0 0 3px 0 #919191; box-shadow: 0 0 3px 0 #919191; } /* For handle diferent bg color in AngularJS version */ .dashboards\.dashboard_2 nav.navbar, .dashboards\.dashboard_3 nav.navbar, .mailbox\.inbox nav.navbar, .mailbox\.email_view nav.navbar, .mailbox\.email_compose nav.navbar, .dashboards\.dashboard_4_1 nav.navbar, .metrics nav.navbar, .metrics\.index nav.navbar, .dashboards\.dashboard_5 nav.navbar { background: #fff; } /* For handle diferent bg color in MVC version */ .Dashboard_2 .navbar.navbar-static-top, .Dashboard_3 .navbar.navbar-static-top, .Dashboard_4_1 .navbar.navbar-static-top, .ComposeEmail .navbar.navbar-static-top, .EmailView .navbar.navbar-static-top, .Inbox .navbar.navbar-static-top, .Metrics .navbar.navbar-static-top, .Dashboard_5 .navbar.navbar-static-top { background: #fff; } a.close-canvas-menu { position: absolute; top: 10px; right: 15px; z-index: 1011; color: #a7b1c2; } a.close-canvas-menu:hover { color: #fff; } .close-canvas-menu { display: none; } .canvas-menu .close-canvas-menu { display: block; } .light-navbar .navbar.navbar-static-top { background-color: #ffffff; } /* FULL HEIGHT */ .full-height { height: 100%; } .fh-breadcrumb { height: calc(100% - 196px); margin: 0 -15px; position: relative; } .fh-no-breadcrumb { height: calc(100% - 99px); margin: 0 -15px; position: relative; } .fh-column { background: #fff; height: 100%; width: 240px; float: left; } .modal-backdrop { z-index: 2040 !important; } .modal { z-index: 2050 !important; } .spiner-example { height: 200px; padding-top: 70px; } legend { font-size: 1rem; } /* MARGINS & PADDINGS */ .p-xxs { padding: 5px; } .p-xs { padding: 10px; } .p-sm { padding: 15px; } .p-m { padding: 20px; } .p-md { padding: 25px; } .p-lg { padding: 30px; } .p-xl { padding: 40px; } .p-w-xs { padding: 0 10px; } .p-w-sm { padding: 0 15px; } .p-w-m { padding: 0 20px; } .p-w-md { padding: 0 25px; } .p-w-lg { padding: 0 30px; } .p-w-xl { padding: 0 40px; } .p-h-xs { padding: 10px 0; } .p-h-sm { padding: 15px 0; } .p-h-m { padding: 20px 0; } .p-h-md { padding: 25px 0; } .p-h-lg { padding: 30px 0; } .p-h-xl { padding: 40px 0; } .m-xxs { margin: 2px 4px; } .m { margin: 15px; } .m-xs { margin: 5px; } .m-sm { margin: 10px; } .m-md { margin: 20px; } .m-lg { margin: 30px; } .m-xl { margin: 50px; } .m-n { margin: 0 !important; } .m-l-none { margin-left: 0; } .m-l-xs { margin-left: 5px; } .m-l-sm { margin-left: 10px; } .m-l { margin-left: 15px; } .m-l-md { margin-left: 20px; } .m-l-lg { margin-left: 30px; } .m-l-xl { margin-left: 40px; } .m-l-n-xxs { margin-left: -1px; } .m-l-n-xs { margin-left: -5px; } .m-l-n-sm { margin-left: -10px; } .m-l-n { margin-left: -15px; } .m-l-n-md { margin-left: -20px; } .m-l-n-lg { margin-left: -30px; } .m-l-n-xl { margin-left: -40px; } .m-t-none { margin-top: 0; } .m-t-xxs { margin-top: 1px; } .m-t-xs { margin-top: 5px; } .m-t-sm { margin-top: 10px; } .m-t { margin-top: 15px; } .m-t-md { margin-top: 20px; } .m-t-lg { margin-top: 30px; } .m-t-xl { margin-top: 40px; } .m-t-n-xxs { margin-top: -1px; } .m-t-n-xs { margin-top: -5px; } .m-t-n-sm { margin-top: -10px; } .m-t-n { margin-top: -15px; } .m-t-n-md { margin-top: -20px; } .m-t-n-lg { margin-top: -30px; } .m-t-n-xl { margin-top: -40px; } .m-r-none { margin-right: 0; } .m-r-xxs { margin-right: 1px; } .m-r-xs { margin-right: 5px; } .m-r-sm { margin-right: 10px; } .m-r { margin-right: 15px; } .m-r-md { margin-right: 20px; } .m-r-lg { margin-right: 30px; } .m-r-xl { margin-right: 40px; } .m-r-n-xxs { margin-right: -1px; } .m-r-n-xs { margin-right: -5px; } .m-r-n-sm { margin-right: -10px; } .m-r-n { margin-right: -15px; } .m-r-n-md { margin-right: -20px; } .m-r-n-lg { margin-right: -30px; } .m-r-n-xl { margin-right: -40px; } .m-b-none { margin-bottom: 0; } .m-b-xxs { margin-bottom: 1px; } .m-b-xs { margin-bottom: 5px; } .m-b-sm { margin-bottom: 10px; } .m-b { margin-bottom: 15px; } .m-b-md { margin-bottom: 20px; } .m-b-lg { margin-bottom: 30px; } .m-b-xl { margin-bottom: 40px; } .m-b-n-xxs { margin-bottom: -1px; } .m-b-n-xs { margin-bottom: -5px; } .m-b-n-sm { margin-bottom: -10px; } .m-b-n { margin-bottom: -15px; } .m-b-n-md { margin-bottom: -20px; } .m-b-n-lg { margin-bottom: -30px; } .m-b-n-xl { margin-bottom: -40px; } .space-15 { margin: 15px 0; } .space-20 { margin: 20px 0; } .space-25 { margin: 25px 0; } .space-30 { margin: 30px 0; } .img-sm { width: 32px; height: 32px; } .img-md { width: 64px; height: 64px; } .img-lg { width: 96px; height: 96px; } .b-r-xs { -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; } .b-r-sm { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .b-r-md { -webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px; } .b-r-lg { -webkit-border-radius: 12px; -moz-border-radius: 12px; border-radius: 12px; } .b-r-xl { -webkit-border-radius: 24px; -moz-border-radius: 24px; border-radius: 24px; } .fullscreen-ibox-mode .animated { animation: none; } body.fullscreen-ibox-mode { overflow-y: hidden; } .ibox.fullscreen { z-index: 2030; position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: auto; margin-bottom: 0; } .ibox.fullscreen .collapse-link { display: none; } .ibox.fullscreen .ibox-content { min-height: calc(100% - 48px); } body.modal-open { padding-right: inherit !important; } _::-webkit-full-page-media, _:future, :root body.modal-open .wrapper-content.animated { -webkit-animation: none; -ms-animation-nam: none; animation: none; } body.modal-open .animated { animation-fill-mode: initial; z-index: inherit; } /* Show profile dropdown on fixed sidebar */ body.mini-navbar.fixed-sidebar .profile-element, .block { display: block !important; } body.mini-navbar.fixed-sidebar .nav-header { padding: 33px 25px; } body.mini-navbar.fixed-sidebar .logo-element { display: none; } .fullscreen-video .animated { animation: none; } .list-inline > li { display: inline-block; } .custom-file-label { padding: .5rem .75rem; } .custom-file-label::after { padding: .5rem .75rem; } /* SEARCH PAGE */ .search-form { margin-top: 10px; } .search-result h3 { margin-bottom: 0; color: #1E0FBE; } .search-result .search-link { color: #006621; } .search-result p { font-size: 12px; margin-top: 5px; } /* CONTACTS */ .contact-box { background-color: #ffffff; border: 1px solid #e7eaec; padding: 20px; margin-bottom: 20px; } .contact-box > a { color: inherit; } .contact-box.center-version { border: 1px solid #e7eaec; padding: 0; } .contact-box.center-version > a { display: block; background-color: #ffffff; padding: 20px; text-align: center; } .contact-box.center-version > a img { width: 80px; height: 80px; margin-top: 10px; margin-bottom: 10px; } .contact-box.center-version address { margin-bottom: 0; } .contact-box .contact-box-footer { text-align: center; background-color: #ffffff; border-top: 1px solid #e7eaec; padding: 15px 20px; } /* INVOICE */ .invoice-table tbody > tr > td:last-child, .invoice-table tbody > tr > td:nth-child(4), .invoice-table tbody > tr > td:nth-child(3), .invoice-table tbody > tr > td:nth-child(2) { text-align: right; } .invoice-table thead > tr > th:last-child, .invoice-table thead > tr > th:nth-child(4), .invoice-table thead > tr > th:nth-child(3), .invoice-table thead > tr > th:nth-child(2) { text-align: right; } .invoice-total > tbody > tr > td:first-child { text-align: right; } .invoice-total > tbody > tr > td { border: 0 none; } .invoice-total > tbody > tr > td:last-child { border-bottom: 1px solid #DDDDDD; text-align: right; width: 15%; } /* ERROR & LOGIN & LOCKSCREEN*/ .middle-box { max-width: 400px; z-index: 100; margin: 0 auto; padding-top: 40px; } .lockscreen.middle-box { width: 200px; padding-top: 110px; } .loginscreen.middle-box { width: 300px; } .loginColumns { max-width: 800px; margin: 0 auto; } .passwordBox { max-width: 460px; margin: 0 auto; padding: 100px 20px 20px 20px; } .logo-name { color: #e6e6e6; font-size: 180px; font-weight: 800; letter-spacing: -10px; margin-bottom: 0; } .middle-box h1 { font-size: 170px; } .wrapper .middle-box { margin-top: 140px; } .lock-word { z-index: 10; position: absolute; top: 110px; left: 50%; margin-left: -470px; } .lock-word span { font-size: 100px; font-weight: 600; color: #e9e9e9; display: inline-block; } .lock-word .first-word { margin-right: 160px; } /* DASBOARD */ .dashboard-header { border-top: 0; padding: 20px 20px 20px 20px; } .dashboard-header h2 { margin-top: 10px; font-size: 26px; } .fist-item { border-top: none !important; } .statistic-box { margin-top: 40px; } .dashboard-header .list-group-item span.label { margin-right: 10px; } .list-group.clear-list .list-group-item { border-top: 1px solid #e7eaec; border-bottom: 0; border-right: 0; border-left: 0; padding: 10px 0; } ul.clear-list:first-child { border-top: none !important; } /* Intimeline */ .timeline-item .date i { position: absolute; top: 0; right: 0; padding: 5px; width: 30px; text-align: center; border-top: 1px solid #e7eaec; border-bottom: 1px solid #e7eaec; border-left: 1px solid #e7eaec; background: #f8f8f8; } .timeline-item .date { text-align: right; width: 110px; position: relative; padding-top: 30px; } .timeline-item .content { border-left: 1px solid #e7eaec; border-top: 1px solid #e7eaec; padding-top: 10px; min-height: 100px; } .timeline-item .content:hover { background: #f6f6f6; } /* PIN BOARD */ ul.notes li, ul.tag-list li { list-style: none; } ul.notes li h4 { margin-top: 20px; font-size: 16px; } ul.notes li div { text-decoration: none; color: #000; background: #ffc; display: block; height: 140px; width: 140px; padding: 1em; position: relative; } ul.notes li div small { position: absolute; top: 5px; right: 5px; font-size: 10px; } ul.notes li div a { position: absolute; right: 10px; bottom: 10px; color: inherit; } ul.notes li { margin: 10px 40px 50px 0; float: left; } ul.notes li div p { font-size: 12px; } ul.notes li div { text-decoration: none; color: #000; background: #ffc; display: block; height: 140px; width: 140px; padding: 1em; /* Firefox */ -moz-box-shadow: 5px 5px 2px #212121; /* Safari+Chrome */ -webkit-box-shadow: 5px 5px 2px rgba(33, 33, 33, 0.7); /* Opera */ box-shadow: 5px 5px 2px rgba(33, 33, 33, 0.7); } ul.notes li div { -webkit-transform: rotate(-6deg); -o-transform: rotate(-6deg); -moz-transform: rotate(-6deg); -ms-transform: rotate(-6deg); } ul.notes li:nth-child(even) div { -o-transform: rotate(4deg); -webkit-transform: rotate(4deg); -moz-transform: rotate(4deg); -ms-transform: rotate(4deg); position: relative; top: 5px; } ul.notes li:nth-child(3n) div { -o-transform: rotate(-3deg); -webkit-transform: rotate(-3deg); -moz-transform: rotate(-3deg); -ms-transform: rotate(-3deg); position: relative; top: -5px; } ul.notes li:nth-child(5n) div { -o-transform: rotate(5deg); -webkit-transform: rotate(5deg); -moz-transform: rotate(5deg); -ms-transform: rotate(5deg); position: relative; top: -10px; } ul.notes li div:hover, ul.notes li div:focus { -webkit-transform: scale(1.1); -moz-transform: scale(1.1); -o-transform: scale(1.1); -ms-transform: scale(1.1); position: relative; z-index: 5; } ul.notes li div { text-decoration: none; color: #000; background: #ffc; display: block; height: 210px; width: 210px; padding: 1em; -moz-box-shadow: 5px 5px 7px #212121; -webkit-box-shadow: 5px 5px 7px rgba(33, 33, 33, 0.7); box-shadow: 5px 5px 7px rgba(33, 33, 33, 0.7); -moz-transition: -moz-transform 0.15s linear; -o-transition: -o-transform 0.15s linear; -webkit-transition: -webkit-transform 0.15s linear; } /* FILE MANAGER */ .file-box { float: left; width: 220px; } .file-manager h5 { text-transform: uppercase; } .file-manager { list-style: none outside none; margin: 0; padding: 0; } .folder-list li a { color: #666666; display: block; padding: 5px 0; } .folder-list li { border-bottom: 1px solid #e7eaec; display: block; } .folder-list li i { margin-right: 8px; color: #3d4d5d; } .category-list li a { color: #666666; display: block; padding: 5px 0; } .category-list li { display: block; } .category-list li i { margin-right: 8px; color: #3d4d5d; } .category-list li a .text-navy { color: #1ab394; } .category-list li a .text-primary { color: #1c84c6; } .category-list li a .text-info { color: #23c6c8; } .category-list li a .text-danger { color: #EF5352; } .category-list li a .text-warning { color: #F8AC59; } .file-manager h5.tag-title { margin-top: 20px; } .tag-list li { float: left; } .tag-list li a { font-size: 10px; background-color: #f3f3f4; padding: 5px 12px; color: inherit; border-radius: 2px; border: 1px solid #e7eaec; margin-right: 5px; margin-top: 5px; display: block; } .file { border: 1px solid #e7eaec; padding: 0; background-color: #ffffff; position: relative; margin-bottom: 20px; margin-right: 20px; } .file-manager .hr-line-dashed { margin: 15px 0; } .file .icon, .file .image { height: 100px; overflow: hidden; } .file .icon { padding: 15px 10px; text-align: center; } .file-control { color: inherit; font-size: 11px; margin-right: 10px; } .file-control.active { text-decoration: underline; } .file .icon i { font-size: 70px; color: #dadada; } .file .file-name { padding: 10px; background-color: #f8f8f8; border-top: 1px solid #e7eaec; } .file-name small { color: #676a6c; } .corner { position: absolute; display: inline-block; width: 0; height: 0; line-height: 0; border: 0.6em solid transparent; border-right: 0.6em solid #f1f1f1; border-bottom: 0.6em solid #f1f1f1; right: 0em; bottom: 0em; } a.compose-mail { padding: 8px 10px; } .mail-search { max-width: 300px; } /* PROFILE */ .profile-content { border-top: none !important; } .profile-stats { margin-right: 10px; } .profile-image { width: 120px; float: left; } .profile-image img { width: 96px; height: 96px; } .profile-info { margin-left: 120px; } .feed-activity-list .feed-element { border-bottom: 1px solid #e7eaec; } .feed-element:first-child { margin-top: 0; } .feed-element { padding-bottom: 15px; } .feed-element, .feed-element .media { margin-top: 15px; } .feed-element, .media-body { overflow: hidden; } .feed-element > a img { margin-right: 10px; } .feed-element img.rounded-circle, .dropdown-messages-box img.rounded-circle { width: 38px; height: 38px; } .feed-element .well { border: 1px solid #e7eaec; box-shadow: none; margin-top: 10px; margin-bottom: 5px; padding: 10px 20px; font-size: 11px; line-height: 16px; } .feed-element .actions { margin-top: 10px; } .feed-element .photos { margin: 10px 0; } .dropdown-messages-box .dropdown-item:focus, .dropdown-messages-box .dropdown-item:hover { background-color: inherit; } .feed-photo { max-height: 180px; border-radius: 4px; overflow: hidden; margin-right: 10px; margin-bottom: 10px; } .file-list li { padding: 5px 10px; font-size: 11px; border-radius: 2px; border: 1px solid #e7eaec; margin-bottom: 5px; } .file-list li a { color: inherit; } .file-list li a:hover { color: #1ab394; } .user-friends img { width: 42px; height: 42px; margin-bottom: 5px; margin-right: 5px; } /* MAILBOX */ .mail-box { background-color: #ffffff; border: 1px solid #e7eaec; border-top: 0; padding: 0; margin-bottom: 20px; } .mail-box-header { background-color: #ffffff; border: 1px solid #e7eaec; border-bottom: 0; padding: 30px 20px 20px 20px; } .mail-box-header h2 { margin-top: 0; } .mailbox-content .tag-list li a { background: #ffffff; } .mail-body { border-top: 1px solid #e7eaec; padding: 20px; } .mail-text { border-top: 1px solid #e7eaec; } .mail-text .note-toolbar { padding: 10px 15px; } .mail-body .form-group { margin-bottom: 5px; } .mail-text .note-editor .note-toolbar { background-color: #F9F8F8; } .mail-attachment { border-top: 1px solid #e7eaec; padding: 20px; font-size: 12px; } .mailbox-content { background: none; border: none; padding: 10px; } .mail-ontact { width: 23%; } /* PROJECTS */ .project-people, .project-actions { text-align: right; vertical-align: middle; } dd.project-people { text-align: left; margin-top: 5px; } .project-people img { width: 32px; height: 32px; } .project-title a { font-size: 14px; color: #676a6c; font-weight: 600; } .project-list table tr td { border-top: none; border-bottom: 1px solid #e7eaec; padding: 15px 10px; vertical-align: middle; } .project-manager .tag-list li a { font-size: 10px; background-color: white; padding: 5px 12px; color: inherit; border-radius: 2px; border: 1px solid #e7eaec; margin-right: 5px; margin-top: 5px; display: block; } .project-files li a { font-size: 11px; color: #676a6c; margin-left: 10px; line-height: 22px; } /* FAQ */ .faq-item { padding: 20px; margin-bottom: 2px; background: #fff; } .faq-question { font-size: 18px; font-weight: 600; color: #1ab394; display: block; } .faq-question:hover { color: #179d82; } .faq-answer { margin-top: 10px; background: #f3f3f4; border: 1px solid #e7eaec; border-radius: 3px; padding: 15px; } .faq-item .tag-item { background: #f3f3f4; padding: 2px 6px; font-size: 10px; text-transform: uppercase; } /* Chat view */ .message-input { height: 90px !important; } .chat-avatar { width: 36px; height: 36px; float: left; margin-right: 10px; } .chat-user-name { padding: 10px; } .chat-user { padding: 8px 10px; border-bottom: 1px solid #e7eaec; } .chat-user a { color: inherit; } .chat-view { z-index: 20012; } .chat-users, .chat-statistic { margin-left: -30px; } @media (max-width: 992px) { .chat-users, .chat-statistic { margin-left: 0; } } .chat-view .ibox-content { padding: 0; } .chat-message { padding: 10px 20px; } .message-avatar { height: 48px; width: 48px; border: 1px solid #e7eaec; border-radius: 4px; margin-top: 1px; } .chat-discussion .chat-message.left .message-avatar { float: left; margin-right: 10px; } .chat-discussion .chat-message.right .message-avatar { float: right; margin-left: 10px; } .message { background-color: #fff; border: 1px solid #e7eaec; text-align: left; display: block; padding: 10px 20px; position: relative; border-radius: 4px; } .chat-discussion .chat-message.left .message-date { float: right; } .chat-discussion .chat-message.right .message-date { float: left; } .chat-discussion .chat-message.left .message { text-align: left; margin-left: 55px; } .chat-discussion .chat-message.right .message { text-align: right; margin-right: 55px; } .message-date { font-size: 10px; color: #888888; } .message-content { display: block; } .chat-discussion { background: #eee; padding: 15px; height: 400px; overflow-y: auto; } .chat-users { overflow-y: auto; height: 400px; } .chat-message-form .form-group { margin-bottom: 0; } /* jsTree */ .jstree-open > .jstree-anchor > .fa-folder:before { content: "\f07c"; } .jstree-default .jstree-icon.none { width: 0; } /* CLIENTS */ .clients-list { margin-top: 20px; } .clients-list .tab-pane { position: relative; height: 600px; } .client-detail { position: relative; height: 620px; } .clients-list table tr td { height: 46px; vertical-align: middle; border: none; } .client-link { font-weight: 600; color: inherit; } .client-link:hover { color: inherit; } .client-avatar { width: 42px; } .client-avatar img { width: 28px; height: 28px; border-radius: 50%; } .contact-type { width: 20px; color: #c1c3c4; } .client-status { text-align: left; } .client-detail .vertical-timeline-content p { margin: 0; } .client-detail .vertical-timeline-icon.gray-bg { color: #a7aaab; } .clients-list .nav-tabs > li.active > a, .clients-list .nav-tabs > li.active > a:hover, .clients-list .nav-tabs > li.active > a:focus { border-bottom: 1px solid #fff; } /* BLOG ARTICLE */ .blog h2 { font-weight: 700; } .blog h5 { margin: 0 0 5px 0; } .blog .btn { margin: 0 0 5px 0; } .article h1 { font-size: 48px; font-weight: 700; color: #2f4050; } .article p { font-size: 15px; line-height: 26px; } .article-title { text-align: center; margin: 40px 0 100px 0; } .article .ibox-content { padding: 40px; } /* ISSUE TRACKER */ .issue-tracker .btn-link { color: #1ab394; } table.issue-tracker tbody tr td { vertical-align: middle; height: 50px; } .issue-info { width: 50%; } .issue-info a { font-weight: 600; color: #676a6c; } .issue-info small { display: block; } /* TEAMS */ .team-members { margin: 10px 0; } .team-members img.rounded-circle { width: 42px; height: 42px; margin-bottom: 5px; } /* AGILE BOARD */ .sortable-list { padding: 10px 0; } .agile-list { list-style: none; margin: 0; } .agile-list li { background: #FAFAFB; border: 1px solid #e7eaec; margin: 0 0 10px 0; padding: 10px; border-radius: 2px; } .agile-list li:hover { cursor: pointer; background: #fff; } .agile-list li.warning-element { border-left: 3px solid #f8ac59; } .agile-list li.danger-element { border-left: 3px solid #ed5565; } .agile-list li.info-element { border-left: 3px solid #1c84c6; } .agile-list li.success-element { border-left: 3px solid #1ab394; } .agile-detail { margin-top: 5px; font-size: 12px; } /* DIFF */ ins { background-color: #c6ffc6; text-decoration: none; } del { background-color: #ffc6c6; } /* E-commerce */ .product-box { padding: 0; border: 1px solid #e7eaec; } .product-box:hover, .product-box.active { border: 1px solid transparent; -webkit-box-shadow: 0 3px 7px 0 #a8a8a8; -moz-box-shadow: 0 3px 7px 0 #a8a8a8; box-shadow: 0 3px 7px 0 #a8a8a8; } .product-imitation { text-align: center; padding: 90px 0; background-color: #f8f8f9; color: #bebec3; font-weight: 600; } .cart-product-imitation { text-align: center; padding-top: 30px; height: 80px; width: 80px; background-color: #f8f8f9; } .product-imitation.xl { padding: 120px 0; } .product-desc { padding: 20px; position: relative; } .ecommerce .tag-list { padding: 0; } .ecommerce .fa-star { color: #d1dade; } .ecommerce .fa-star.active { color: #f8ac59; } .ecommerce .note-editor { border: 1px solid #e7eaec; } table.shoping-cart-table { margin-bottom: 0; } table.shoping-cart-table tr td { border: none; text-align: right; } table.shoping-cart-table tr td.desc, table.shoping-cart-table tr td:first-child { text-align: left; } table.shoping-cart-table tr td:last-child { width: 80px; } .product-name { font-size: 16px; font-weight: 600; color: #676a6c; display: block; margin: 2px 0 5px 0; } .product-name:hover, .product-name:focus { color: #1ab394; } .product-price { font-size: 14px; font-weight: 600; color: #ffffff; background-color: #1ab394; padding: 6px 12px; position: absolute; top: -32px; right: 0; } .product-detail .ibox-content { padding: 30px 30px 50px 30px; } .image-imitation { background-color: #f8f8f9; text-align: center; padding: 200px 0; } .product-main-price small { font-size: 10px; } .product-images { margin: 0 20px; } /* Social feed */ .social-feed-separated .social-feed-box { margin-left: 62px; } .social-feed-separated .social-avatar { float: left; padding: 0; } .social-feed-separated .social-avatar img { width: 52px; height: 52px; border: 1px solid #e7eaec; } .social-feed-separated .social-feed-box .social-avatar { padding: 15px 15px 0 15px; float: none; } .social-feed-box { /*padding: 15px;*/ border: 1px solid #e7eaec; background: #fff; margin-bottom: 15px; } .article .social-feed-box { margin-bottom: 0; border-bottom: none; } .article .social-feed-box:last-child { margin-bottom: 0; border-bottom: 1px solid #e7eaec; } .article .social-feed-box p { font-size: 13px; line-height: 18px; } .social-action { margin: 15px; } .social-action .dropdown-toggle::after { margin-left: auto; } .social-avatar { padding: 15px 15px 0 15px; } .social-comment .social-comment { margin-left: 45px; } .social-avatar img { height: 40px; width: 40px; margin-right: 10px; } .social-avatar .media-body a { font-size: 14px; display: block; } .social-body { padding: 15px; } .social-body img { margin-bottom: 10px; } .social-footer { border-top: 1px solid #e7eaec; padding: 10px 15px; background: #f9f9f9; } .social-footer .social-comment img { width: 32px; margin-right: 10px; } .social-comment:first-child { margin-top: 0; } .social-comment { margin-top: 15px; } .social-comment textarea { font-size: 12px; } /* Vote list */ .vote-item { padding: 20px 25px; background: #ffffff; border-top: 1px solid #e7eaec; } .vote-item:last-child { border-bottom: 1px solid #e7eaec; } .vote-item:hover { background: #fbfbfb; } .vote-actions { float: left; width: 30px; margin-right: 15px; text-align: center; } .vote-actions a { color: #1ab394; font-weight: 600; } .vote-actions { font-weight: 600; } .vote-title { display: block; color: inherit; font-size: 18px; font-weight: 600; margin-top: 5px; margin-bottom: 2px; } .vote-title:hover, .vote-title:focus { color: inherit; } .vote-info, .vote-title { margin-left: 45px; } .vote-info, .vote-info a { color: #b4b6b8; font-size: 12px; } .vote-info a { margin-right: 10px; } .vote-info a:hover { color: #1ab394; } .vote-icon { text-align: right; font-size: 38px; display: block; color: #e8e9ea; } .vote-icon.active { color: #1ab394; } body.body-small .vote-icon { display: none; } .lightBoxGallery { text-align: center; } .lightBoxGallery img { margin: 5px; } #small-chat { position: fixed; bottom: 20px; right: 20px; z-index: 1000; } #small-chat .badge { position: absolute; top: -3px; right: -4px; } .open-small-chat { height: 38px; width: 38px; display: block; background: #1ab394; padding: 9px 8px; text-align: center; color: #fff; border-radius: 50%; } .open-small-chat:hover { color: white; background: #1ab394; } .small-chat-box { display: none; position: fixed; bottom: 20px; right: 75px; background: #fff; border: 1px solid #e7eaec; width: 230px; height: 320px; border-radius: 4px; } .small-chat-box.ng-small-chat { display: block; } .body-small .small-chat-box { bottom: 70px; right: 20px; } .small-chat-box.active { display: block; } .small-chat-box { z-index: 1001; } .small-chat-box .heading { background: #2f4050; padding: 8px 15px; font-weight: bold; color: #fff; } .small-chat-box .chat-date { opacity: 0.6; font-size: 10px; font-weight: normal; } .small-chat-box .content { padding: 15px 15px; } .small-chat-box .content .author-name { font-weight: bold; margin-bottom: 3px; font-size: 11px; } .small-chat-box .content > div { padding-bottom: 20px; } .small-chat-box .content .chat-message { padding: 5px 10px; border-radius: 6px; font-size: 11px; line-height: 14px; max-width: 80%; background: #f3f3f4; margin-bottom: 10px; } .small-chat-box .content .chat-message.active { background: #1ab394; color: #fff; } .small-chat-box .content .left { text-align: left; clear: both; } .small-chat-box .content .left .chat-message { float: left; } .small-chat-box .content .right { text-align: right; clear: both; } .small-chat-box .content .right .chat-message { float: right; } .small-chat-box .form-chat { padding: 10px 10px; } /* * metismenu - v2.0.2 * A jQuery menu plugin * https://github.com/onokumus/metisMenu * * Made by Osman Nuri Okumus * Under MIT License */ .metismenu .plus-minus, .metismenu .plus-times { float: right; } .metismenu .arrow { float: right; line-height: 1.42857; } .metismenu .glyphicon.arrow:before { content: "\e079"; } .metismenu .active > a > .glyphicon.arrow:before { content: "\e114"; } .metismenu .fa.arrow:before { content: "\f104"; } .metismenu .active > a > .fa.arrow:before { content: "\f107"; } .metismenu .ion.arrow:before { content: "\f3d2"; } .metismenu .active > a > .ion.arrow:before { content: "\f3d0"; } .metismenu .fa.plus-minus:before, .metismenu .fa.plus-times:before { content: "\f067"; } .metismenu .active > a > .fa.plus-times { -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); } .metismenu .active > a > .fa.plus-minus:before { content: "\f068"; } .metismenu .collapse { display: none; } .metismenu .collapse.in { display: block; } .metismenu .collapsing { position: relative; height: 0; overflow: hidden; -webkit-transition-timing-function: ease; transition-timing-function: ease; -webkit-transition-duration: .35s; transition-duration: .35s; -webkit-transition-property: height, visibility; transition-property: height, visibility; } .mini-navbar .metismenu .collapse { opacity: 0; } .mini-navbar .metismenu .collapse.in { opacity: 1; } .mini-navbar .metismenu .collapse a { display: none; } .mini-navbar .metismenu .collapse.in a { display: block; } /* * Usage: * *
* */ .sk-spinner-rotating-plane.sk-spinner { width: 30px; height: 30px; background-color: #1ab394; margin: 0 auto; -webkit-animation: sk-rotatePlane 1.2s infinite ease-in-out; animation: sk-rotatePlane 1.2s infinite ease-in-out; } @-webkit-keyframes sk-rotatePlane { 0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg); } 50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); } 100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); } } @keyframes sk-rotatePlane { 0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg); } 50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); } 100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); } } /* * Usage: * *
*
*
*
* */ .sk-spinner-double-bounce.sk-spinner { width: 40px; height: 40px; position: relative; margin: 0 auto; } .sk-spinner-double-bounce .sk-double-bounce1, .sk-spinner-double-bounce .sk-double-bounce2 { width: 100%; height: 100%; border-radius: 50%; background-color: #1ab394; opacity: 0.6; position: absolute; top: 0; left: 0; -webkit-animation: sk-doubleBounce 2s infinite ease-in-out; animation: sk-doubleBounce 2s infinite ease-in-out; } .sk-spinner-double-bounce .sk-double-bounce2 { -webkit-animation-delay: -1s; animation-delay: -1s; } @-webkit-keyframes sk-doubleBounce { 0%, 100% { -webkit-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes sk-doubleBounce { 0%, 100% { -webkit-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1); transform: scale(1); } } /* * Usage: * *
*
*
*
*
*
*
* */ .sk-spinner-wave.sk-spinner { margin: 0 auto; width: 50px; height: 30px; text-align: center; font-size: 10px; } .sk-spinner-wave div { background-color: #1ab394; height: 100%; width: 6px; display: inline-block; -webkit-animation: sk-waveStretchDelay 1.2s infinite ease-in-out; animation: sk-waveStretchDelay 1.2s infinite ease-in-out; } .sk-spinner-wave .sk-rect2 { -webkit-animation-delay: -1.1s; animation-delay: -1.1s; } .sk-spinner-wave .sk-rect3 { -webkit-animation-delay: -1s; animation-delay: -1s; } .sk-spinner-wave .sk-rect4 { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } .sk-spinner-wave .sk-rect5 { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; } @-webkit-keyframes sk-waveStretchDelay { 0%, 40%, 100% { -webkit-transform: scaleY(0.4); transform: scaleY(0.4); } 20% { -webkit-transform: scaleY(1); transform: scaleY(1); } } @keyframes sk-waveStretchDelay { 0%, 40%, 100% { -webkit-transform: scaleY(0.4); transform: scaleY(0.4); } 20% { -webkit-transform: scaleY(1); transform: scaleY(1); } } /* * Usage: * *
*
*
*
* */ .sk-spinner-wandering-cubes.sk-spinner { margin: 0 auto; width: 32px; height: 32px; position: relative; } .sk-spinner-wandering-cubes .sk-cube1, .sk-spinner-wandering-cubes .sk-cube2 { background-color: #1ab394; width: 10px; height: 10px; position: absolute; top: 0; left: 0; -webkit-animation: sk-wanderingCubeMove 1.8s infinite ease-in-out; animation: sk-wanderingCubeMove 1.8s infinite ease-in-out; } .sk-spinner-wandering-cubes .sk-cube2 { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } @-webkit-keyframes sk-wanderingCubeMove { 25% { -webkit-transform: translateX(42px) rotate(-90deg) scale(0.5); transform: translateX(42px) rotate(-90deg) scale(0.5); } 50% { /* Hack to make FF rotate in the right direction */ -webkit-transform: translateX(42px) translateY(42px) rotate(-179deg); transform: translateX(42px) translateY(42px) rotate(-179deg); } 50.1% { -webkit-transform: translateX(42px) translateY(42px) rotate(-180deg); transform: translateX(42px) translateY(42px) rotate(-180deg); } 75% { -webkit-transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5); transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5); } 100% { -webkit-transform: rotate(-360deg); transform: rotate(-360deg); } } @keyframes sk-wanderingCubeMove { 25% { -webkit-transform: translateX(42px) rotate(-90deg) scale(0.5); transform: translateX(42px) rotate(-90deg) scale(0.5); } 50% { /* Hack to make FF rotate in the right direction */ -webkit-transform: translateX(42px) translateY(42px) rotate(-179deg); transform: translateX(42px) translateY(42px) rotate(-179deg); } 50.1% { -webkit-transform: translateX(42px) translateY(42px) rotate(-180deg); transform: translateX(42px) translateY(42px) rotate(-180deg); } 75% { -webkit-transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5); transform: translateX(0px) translateY(42px) rotate(-270deg) scale(0.5); } 100% { -webkit-transform: rotate(-360deg); transform: rotate(-360deg); } } /* * Usage: * *
* */ .sk-spinner-pulse.sk-spinner { width: 40px; height: 40px; margin: 0 auto; background-color: #1ab394; border-radius: 100%; -webkit-animation: sk-pulseScaleOut 1s infinite ease-in-out; animation: sk-pulseScaleOut 1s infinite ease-in-out; } @-webkit-keyframes sk-pulseScaleOut { 0% { -webkit-transform: scale(0); transform: scale(0); } 100% { -webkit-transform: scale(1); transform: scale(1); opacity: 0; } } @keyframes sk-pulseScaleOut { 0% { -webkit-transform: scale(0); transform: scale(0); } 100% { -webkit-transform: scale(1); transform: scale(1); opacity: 0; } } /* * Usage: * *
*
*
*
* */ .sk-spinner-chasing-dots.sk-spinner { margin: 0 auto; width: 40px; height: 40px; position: relative; text-align: center; -webkit-animation: sk-chasingDotsRotate 2s infinite linear; animation: sk-chasingDotsRotate 2s infinite linear; } .sk-spinner-chasing-dots .sk-dot1, .sk-spinner-chasing-dots .sk-dot2 { width: 60%; height: 60%; display: inline-block; position: absolute; top: 0; background-color: #1ab394; border-radius: 100%; -webkit-animation: sk-chasingDotsBounce 2s infinite ease-in-out; animation: sk-chasingDotsBounce 2s infinite ease-in-out; } .sk-spinner-chasing-dots .sk-dot2 { top: auto; bottom: 0; -webkit-animation-delay: -1s; animation-delay: -1s; } @-webkit-keyframes sk-chasingDotsRotate { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes sk-chasingDotsRotate { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @-webkit-keyframes sk-chasingDotsBounce { 0%, 100% { -webkit-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes sk-chasingDotsBounce { 0%, 100% { -webkit-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1); transform: scale(1); } } /* * Usage: * *
*
*
*
*
* */ .sk-spinner-three-bounce.sk-spinner { margin: 0 auto; width: 70px; text-align: center; } .sk-spinner-three-bounce div { width: 18px; height: 18px; background-color: #1ab394; border-radius: 100%; display: inline-block; -webkit-animation: sk-threeBounceDelay 1.4s infinite ease-in-out; animation: sk-threeBounceDelay 1.4s infinite ease-in-out; /* Prevent first frame from flickering when animation starts */ -webkit-animation-fill-mode: both; animation-fill-mode: both; } .sk-spinner-three-bounce .sk-bounce1 { -webkit-animation-delay: -0.32s; animation-delay: -0.32s; } .sk-spinner-three-bounce .sk-bounce2 { -webkit-animation-delay: -0.16s; animation-delay: -0.16s; } @-webkit-keyframes sk-threeBounceDelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes sk-threeBounceDelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1); transform: scale(1); } } /* * Usage: * *
*
*
*
*
*
*
*
*
*
*
*
*
*
* */ .sk-spinner-circle.sk-spinner { margin: 0 auto; width: 22px; height: 22px; position: relative; } .sk-spinner-circle .sk-circle { width: 100%; height: 100%; position: absolute; left: 0; top: 0; } .sk-spinner-circle .sk-circle:before { content: ''; display: block; margin: 0 auto; width: 20%; height: 20%; background-color: #1ab394; border-radius: 100%; -webkit-animation: sk-circleBounceDelay 1.2s infinite ease-in-out; animation: sk-circleBounceDelay 1.2s infinite ease-in-out; /* Prevent first frame from flickering when animation starts */ -webkit-animation-fill-mode: both; animation-fill-mode: both; } .sk-spinner-circle .sk-circle2 { -webkit-transform: rotate(30deg); -ms-transform: rotate(30deg); transform: rotate(30deg); } .sk-spinner-circle .sk-circle3 { -webkit-transform: rotate(60deg); -ms-transform: rotate(60deg); transform: rotate(60deg); } .sk-spinner-circle .sk-circle4 { -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .sk-spinner-circle .sk-circle5 { -webkit-transform: rotate(120deg); -ms-transform: rotate(120deg); transform: rotate(120deg); } .sk-spinner-circle .sk-circle6 { -webkit-transform: rotate(150deg); -ms-transform: rotate(150deg); transform: rotate(150deg); } .sk-spinner-circle .sk-circle7 { -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .sk-spinner-circle .sk-circle8 { -webkit-transform: rotate(210deg); -ms-transform: rotate(210deg); transform: rotate(210deg); } .sk-spinner-circle .sk-circle9 { -webkit-transform: rotate(240deg); -ms-transform: rotate(240deg); transform: rotate(240deg); } .sk-spinner-circle .sk-circle10 { -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .sk-spinner-circle .sk-circle11 { -webkit-transform: rotate(300deg); -ms-transform: rotate(300deg); transform: rotate(300deg); } .sk-spinner-circle .sk-circle12 { -webkit-transform: rotate(330deg); -ms-transform: rotate(330deg); transform: rotate(330deg); } .sk-spinner-circle .sk-circle2:before { -webkit-animation-delay: -1.1s; animation-delay: -1.1s; } .sk-spinner-circle .sk-circle3:before { -webkit-animation-delay: -1s; animation-delay: -1s; } .sk-spinner-circle .sk-circle4:before { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } .sk-spinner-circle .sk-circle5:before { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; } .sk-spinner-circle .sk-circle6:before { -webkit-animation-delay: -0.7s; animation-delay: -0.7s; } .sk-spinner-circle .sk-circle7:before { -webkit-animation-delay: -0.6s; animation-delay: -0.6s; } .sk-spinner-circle .sk-circle8:before { -webkit-animation-delay: -0.5s; animation-delay: -0.5s; } .sk-spinner-circle .sk-circle9:before { -webkit-animation-delay: -0.4s; animation-delay: -0.4s; } .sk-spinner-circle .sk-circle10:before { -webkit-animation-delay: -0.3s; animation-delay: -0.3s; } .sk-spinner-circle .sk-circle11:before { -webkit-animation-delay: -0.2s; animation-delay: -0.2s; } .sk-spinner-circle .sk-circle12:before { -webkit-animation-delay: -0.1s; animation-delay: -0.1s; } @-webkit-keyframes sk-circleBounceDelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes sk-circleBounceDelay { 0%, 80%, 100% { -webkit-transform: scale(0); transform: scale(0); } 40% { -webkit-transform: scale(1); transform: scale(1); } } /* * Usage: * *
*
*
*
*
*
*
*
*
*
*
* */ .sk-spinner-cube-grid { /* * Spinner positions * 1 2 3 * 4 5 6 * 7 8 9 */ } .sk-spinner-cube-grid.sk-spinner { width: 30px; height: 30px; margin: 0 auto; } .sk-spinner-cube-grid .sk-cube { width: 33%; height: 33%; background-color: #1ab394; float: left; -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; } .sk-spinner-cube-grid .sk-cube:nth-child(1) { -webkit-animation-delay: 0.2s; animation-delay: 0.2s; } .sk-spinner-cube-grid .sk-cube:nth-child(2) { -webkit-animation-delay: 0.3s; animation-delay: 0.3s; } .sk-spinner-cube-grid .sk-cube:nth-child(3) { -webkit-animation-delay: 0.4s; animation-delay: 0.4s; } .sk-spinner-cube-grid .sk-cube:nth-child(4) { -webkit-animation-delay: 0.1s; animation-delay: 0.1s; } .sk-spinner-cube-grid .sk-cube:nth-child(5) { -webkit-animation-delay: 0.2s; animation-delay: 0.2s; } .sk-spinner-cube-grid .sk-cube:nth-child(6) { -webkit-animation-delay: 0.3s; animation-delay: 0.3s; } .sk-spinner-cube-grid .sk-cube:nth-child(7) { -webkit-animation-delay: 0s; animation-delay: 0s; } .sk-spinner-cube-grid .sk-cube:nth-child(8) { -webkit-animation-delay: 0.1s; animation-delay: 0.1s; } .sk-spinner-cube-grid .sk-cube:nth-child(9) { -webkit-animation-delay: 0.2s; animation-delay: 0.2s; } @-webkit-keyframes sk-cubeGridScaleDelay { 0%, 70%, 100% { -webkit-transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1); } 35% { -webkit-transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1); } } @keyframes sk-cubeGridScaleDelay { 0%, 70%, 100% { -webkit-transform: scale3D(1, 1, 1); transform: scale3D(1, 1, 1); } 35% { -webkit-transform: scale3D(0, 0, 1); transform: scale3D(0, 0, 1); } } /* * Usage: * *
* *
* */ .sk-spinner-wordpress.sk-spinner { background-color: #1ab394; width: 30px; height: 30px; border-radius: 30px; position: relative; margin: 0 auto; -webkit-animation: sk-innerCircle 1s linear infinite; animation: sk-innerCircle 1s linear infinite; } .sk-spinner-wordpress .sk-inner-circle { display: block; background-color: #fff; width: 8px; height: 8px; position: absolute; border-radius: 8px; top: 5px; left: 5px; } @-webkit-keyframes sk-innerCircle { 0% { -webkit-transform: rotate(0); transform: rotate(0); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes sk-innerCircle { 0% { -webkit-transform: rotate(0); transform: rotate(0); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } /* * Usage: * *
*
*
*
*
*
*
*
*
*
*
*
*
*
* */ .sk-spinner-fading-circle.sk-spinner { margin: 0 auto; width: 22px; height: 22px; position: relative; } .sk-spinner-fading-circle .sk-circle { width: 100%; height: 100%; position: absolute; left: 0; top: 0; } .sk-spinner-fading-circle .sk-circle:before { content: ''; display: block; margin: 0 auto; width: 18%; height: 18%; background-color: #1ab394; border-radius: 100%; -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out; animation: sk-circleFadeDelay 1.2s infinite ease-in-out; /* Prevent first frame from flickering when animation starts */ -webkit-animation-fill-mode: both; animation-fill-mode: both; } .sk-spinner-fading-circle .sk-circle2 { -webkit-transform: rotate(30deg); -ms-transform: rotate(30deg); transform: rotate(30deg); } .sk-spinner-fading-circle .sk-circle3 { -webkit-transform: rotate(60deg); -ms-transform: rotate(60deg); transform: rotate(60deg); } .sk-spinner-fading-circle .sk-circle4 { -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .sk-spinner-fading-circle .sk-circle5 { -webkit-transform: rotate(120deg); -ms-transform: rotate(120deg); transform: rotate(120deg); } .sk-spinner-fading-circle .sk-circle6 { -webkit-transform: rotate(150deg); -ms-transform: rotate(150deg); transform: rotate(150deg); } .sk-spinner-fading-circle .sk-circle7 { -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .sk-spinner-fading-circle .sk-circle8 { -webkit-transform: rotate(210deg); -ms-transform: rotate(210deg); transform: rotate(210deg); } .sk-spinner-fading-circle .sk-circle9 { -webkit-transform: rotate(240deg); -ms-transform: rotate(240deg); transform: rotate(240deg); } .sk-spinner-fading-circle .sk-circle10 { -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .sk-spinner-fading-circle .sk-circle11 { -webkit-transform: rotate(300deg); -ms-transform: rotate(300deg); transform: rotate(300deg); } .sk-spinner-fading-circle .sk-circle12 { -webkit-transform: rotate(330deg); -ms-transform: rotate(330deg); transform: rotate(330deg); } .sk-spinner-fading-circle .sk-circle2:before { -webkit-animation-delay: -1.1s; animation-delay: -1.1s; } .sk-spinner-fading-circle .sk-circle3:before { -webkit-animation-delay: -1s; animation-delay: -1s; } .sk-spinner-fading-circle .sk-circle4:before { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } .sk-spinner-fading-circle .sk-circle5:before { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; } .sk-spinner-fading-circle .sk-circle6:before { -webkit-animation-delay: -0.7s; animation-delay: -0.7s; } .sk-spinner-fading-circle .sk-circle7:before { -webkit-animation-delay: -0.6s; animation-delay: -0.6s; } .sk-spinner-fading-circle .sk-circle8:before { -webkit-animation-delay: -0.5s; animation-delay: -0.5s; } .sk-spinner-fading-circle .sk-circle9:before { -webkit-animation-delay: -0.4s; animation-delay: -0.4s; } .sk-spinner-fading-circle .sk-circle10:before { -webkit-animation-delay: -0.3s; animation-delay: -0.3s; } .sk-spinner-fading-circle .sk-circle11:before { -webkit-animation-delay: -0.2s; animation-delay: -0.2s; } .sk-spinner-fading-circle .sk-circle12:before { -webkit-animation-delay: -0.1s; animation-delay: -0.1s; } @-webkit-keyframes sk-circleFadeDelay { 0%, 39%, 100% { opacity: 0; } 40% { opacity: 1; } } @keyframes sk-circleFadeDelay { 0%, 39%, 100% { opacity: 0; } 40% { opacity: 1; } } .ibox-content > .sk-spinner { display: none; } .ibox-content.sk-loading { position: relative; } .ibox-content.sk-loading:after { content: ''; background-color: rgba(255, 255, 255, 0.7); position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .ibox-content.sk-loading > .sk-spinner { display: block; position: absolute; top: 40%; left: 0; right: 0; z-index: 2000; } /* PACE PLUGIN -------------------------------------------------- */ .landing-page.pace .pace-progress { background: #fff; position: fixed; z-index: 2000; top: 0; left: 0; height: 2px; -webkit-transition: width 1s; -moz-transition: width 1s; -o-transition: width 1s; transition: width 1s; } .pace-inactive { display: none; } body.landing-page { color: #676a6c; font-family: 'Open Sans', helvetica, arial, sans-serif; background-color: #fff; } .landing-page { /* CUSTOMIZE THE NAVBAR -------------------------------------------------- */ /* Flip around the padding for proper display in narrow viewports */ /* BACKGROUNDS SLIDER -------------------------------------------------- */ /* CUSTOMIZE THE CAROUSEL -------------------------------------------------- */ /* Carousel base class */ /* Since positioning the image, we need to help out the caption */ /* Declare heights because of positioning of img element */ /* Sections ------------------------- */ /* Buttons - only primary custom button ------------------------- */ /* RESPONSIVE CSS -------------------------------------------------- */ } .landing-page button:focus { outline: 0; } .landing-page .container { overflow: hidden; } .landing-page span.navy { color: #1ab394; } .landing-page p.text-color { color: #676a6c; } .landing-page a.navy-link { color: #1ab394; text-decoration: none; } .landing-page a.navy-link:hover { color: #179d82; } .landing-page section p { color: #aeaeae; font-size: 13px; } .landing-page address { font-size: 13px; } .landing-page h1 { margin-top: 10px; font-size: 30px; font-weight: 200; } .landing-page .navy-line { width: 60px; height: 1px; margin: 60px auto 0; border-bottom: 2px solid #1ab394; } .landing-page .navbar { padding: 0 1rem; } .landing-page .navbar-wrapper { position: fixed; top: 0; right: 0; left: 0; z-index: 200; } .landing-page .navbar-wrapper > .container { padding-right: 0; padding-left: 0; } .landing-page .navbar-wrapper .navbar { padding-right: 15px; padding-left: 15px; } .landing-page .navbar-default.navbar-scroll { background-color: #fff; border-color: #fff; padding: 15px 0; } .landing-page .navbar-default { background-color: transparent; border-color: transparent; transition: all 0.3s ease-in-out 0s; } .landing-page .navbar-default .nav li a { color: #fff; font-family: 'Open Sans', helvetica, arial, sans-serif; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; font-size: 14px; } .landing-page .navbar-nav > li > a { padding-top: 25px; border-top: 6px solid transparent; } .landing-page .navbar-default .navbar-nav > .active > a, .landing-page .navbar-default .navbar-nav > .active > a:hover { background: transparent; color: #fff; border-top: 6px solid #1ab394; } .landing-page .navbar-default .navbar-nav > li > a:hover, .landing-page .navbar-default .navbar-nav > li > a:focus { color: #1ab394; background: inherit; } .landing-page .navbar-default .navbar-nav > .active > a:focus { background: transparent; color: #fff; } .landing-page .navbar-default .navbar-nav > .active > a:focus { background: transparent; color: #ffffff; } .landing-page .navbar-default.navbar-scroll .navbar-nav > .active > a:focus { background: transparent; color: inherit; } .landing-page .navbar-default .navbar-brand:hover, .landing-page .navbar-default .navbar-brand:focus { background: #179d82; color: #fff; } .landing-page .navbar-default .navbar-brand { color: #fff; height: auto; display: block; font-size: 14px; background: #1ab394; padding: 15px 20px 15px 20px; border-radius: 0 0 5px 5px; font-weight: 700; transition: all 0.3s ease-in-out 0s; margin-top: -16px; } .landing-page .navbar-scroll.navbar-default .nav li a { color: #676a6c; } .landing-page .navbar-scroll.navbar-default .nav li a:hover { color: #1ab394; } .landing-page .navbar-wrapper .navbar.navbar-scroll { padding-top: 0; padding-bottom: 5px; border-bottom: 1px solid #e7eaec; border-radius: 0; } .landing-page .nav.navbar-right { flex-direction: row; } .landing-page .nav > li.active { border: none; background: inherit; } .landing-page .nav > li > a { padding: 25px 10px 15px 10px; } .landing-page .navbar-scroll .navbar-nav > li > a { padding: 20px 10px; } .landing-page .navbar-default .navbar-nav > li .nav-link.active, .landing-page .navbar-default .navbar-nav > li .nav-link.active:hover { border-top: 6px solid #1ab394; } .landing-page .navbar-fixed-top { border: none !important; } .landing-page .navbar-fixed-top.navbar-scroll { border-bottom: 1px solid #e7eaec !important; } .landing-page .navbar.navbar-scroll .navbar-brand { margin-top: 5px; border-radius: 5px; font-size: 12px; padding: 10px; height: auto; } .landing-page .header-back { height: 470px; width: 100%; } .landing-page .header-back.one { background: url('../img/landing/header_one.jpg') 50% 0 no-repeat; } .landing-page .header-back.two { background: url('../img/landing/header_two.jpg') 50% 0 no-repeat; } .landing-page .carousel { height: 470px; overflow: hidden; } .landing-page .carousel-caption { z-index: 10; } .landing-page .carousel .item { height: 470px; background-color: #777; } .landing-page .carousel-inner > .item > img { position: absolute; top: 0; left: 0; min-width: 100%; height: 470px; } .landing-page .carousel-fade .carousel-inner .item { opacity: 0; -webkit-transition-property: opacity; transition-property: opacity; } .landing-page .carousel-fade .carousel-inner .active { opacity: 1; } .landing-page .carousel-fade .carousel-inner .active.left, .landing-page .carousel-fade .carousel-inner .active.right { left: 0; opacity: 0; z-index: 1; } .landing-page .carousel-fade .carousel-inner .next.left, .landing-page .carousel-fade .carousel-inner .prev.right { opacity: 1; } .landing-page .carousel-fade .carousel-control { z-index: 2; } .landing-page .carousel-control.left, .landing-page .carousel-control.right { background: none; } .landing-page .carousel-control { width: 6%; } .landing-page .carousel-inner .container { position: relative; overflow: visible; } .landing-page .carousel-inner { overflow: visible; } .landing-page .carousel-caption { position: absolute; top: 100px; left: 0; bottom: auto; right: auto; text-align: left; } .landing-page .carousel-caption { position: absolute; top: 100px; left: 0; bottom: auto; right: auto; text-align: left; } .landing-page .carousel-caption.blank { top: 140px; } .landing-page .carousel-image { position: absolute; right: 10px; top: 150px; } .landing-page .carousel-indicators { padding-right: 60px; } .landing-page .carousel-caption h1 { font-weight: 700; font-size: 38px; text-transform: uppercase; text-shadow: none; letter-spacing: -1.5px; } .landing-page .carousel-caption p { font-weight: 700; text-transform: uppercase; text-shadow: none; } .landing-page .caption-link { color: #fff; margin-left: 10px; text-transform: capitalize; font-weight: 400; } .landing-page .caption-link:hover { text-decoration: none; color: inherit; } .landing-page .services { padding-top: 60px; } .landing-page .services h2 { font-size: 20px; letter-spacing: -1px; font-weight: 600; text-transform: uppercase; } .landing-page .features-block { margin-top: 40px; } .landing-page .features-text { margin-top: 40px; } .landing-page .features small { color: #1ab394; } .landing-page .features h2 { font-size: 18px; margin-top: 5px; } .landing-page .features-text-alone { margin: 40px 0; } .landing-page .features-text-alone h1 { font-weight: 200; } .landing-page .features-icon { color: #1ab394; font-size: 40px; } .landing-page .navy-section { margin-top: 60px; background: #1ab394; color: #fff; padding: 20px 0; } .landing-page .gray-section { background: #f4f4f4; margin-top: 60px; } .landing-page .team-member { text-align: center; } .landing-page .team-member img { margin: auto; } .landing-page .social-icon a { background: #1ab394; color: #fff; padding: 4px 8px; height: 28px; width: 28px; display: block; border-radius: 50px; } .landing-page .social-icon a:hover { background: #179d82; } .landing-page .img-small { height: 88px; width: 88px; } .landing-page .pricing-plan { margin: 20px 30px 0 30px; border-radius: 4px; } .landing-page .pricing-plan.selected { transform: scale(1.1); background: #f4f4f4; } .landing-page .pricing-plan li { padding: 10px 16px; border-top: 1px solid #e7eaec; text-align: center; color: #aeaeae; } .landing-page .pricing-plan .pricing-price span { font-weight: 700; color: #1ab394; } .landing-page li.pricing-desc { font-size: 13px; border-top: none; padding: 20px 16px; } .landing-page li.pricing-title { background: #1ab394; color: #fff; padding: 10px; border-radius: 4px 4px 0 0; font-size: 22px; font-weight: 600; } .landing-page .testimonials { padding-top: 80px; padding-bottom: 90px; background-color: #1ab394; background-image: url('../img/landing/avatar_all.png'); } .landing-page .big-icon { font-size: 56px !important; } .landing-page .features .big-icon { color: #1ab394 !important; } .landing-page .contact { background-image: url('../img/landing/word_map.png'); background-position: 50% 50%; background-repeat: no-repeat; margin-top: 60px; } .landing-page section.timeline { padding-bottom: 30px; } .landing-page section.comments { padding-bottom: 80px; } .landing-page .comments-avatar { margin-top: 25px; margin-left: 22px; margin-bottom: 25px; } .landing-page .comments-avatar .commens-name { font-weight: 600; font-size: 14px; } .landing-page .comments-avatar img { width: 42px; height: 42px; border-radius: 50%; margin-right: 10px; } .landing-page .bubble { position: relative; height: 120px; padding: 20px; background: #FFFFFF; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; font-style: italic; font-size: 14px; } .landing-page .bubble:after { content: ''; position: absolute; border-style: solid; border-width: 15px 14px 0; border-color: #FFFFFF transparent; display: block; width: 0; z-index: 1; bottom: -15px; left: 30px; } .landing-page .btn-primary.btn-outline:hover, .landing-page .btn-success.btn-outline:hover, .landing-page .btn-info.btn-outline:hover, .landing-page .btn-warning.btn-outline:hover, .landing-page .btn-danger.btn-outline:hover { color: #fff; } .landing-page .btn-primary { background-color: #1ab394; border-color: #1ab394; color: #FFFFFF; font-size: 14px; padding: 10px 20px; font-weight: 600; } .landing-page .btn-primary:hover, .landing-page .btn-primary:focus, .landing-page .btn-primary:active, .landing-page .btn-primary.active, .landing-page .open .dropdown-toggle.btn-primary { background-color: #179d82; border-color: #179d82; color: #FFFFFF; } .landing-page .btn-primary:active, .landing-page .btn-primary.active, .landing-page .open .dropdown-toggle.btn-primary { background-image: none; } .landing-page .btn-primary.disabled, .landing-page .btn-primary.disabled:hover, .landing-page .btn-primary.disabled:focus, .landing-page .btn-primary.disabled:active, .landing-page .btn-primary.disabled.active, .landing-page .btn-primary[disabled], .landing-page .btn-primary[disabled]:hover, .landing-page .btn-primary[disabled]:focus, .landing-page .btn-primary[disabled]:active, .landing-page .btn-primary.active[disabled], .landing-page fieldset[disabled] .btn-primary, .landing-page fieldset[disabled] .btn-primary:hover, .landing-page fieldset[disabled] .btn-primary:focus, .landing-page fieldset[disabled] .btn-primary:active, .landing-page fieldset[disabled] .btn-primary.active { background-color: #1dc5a3; border-color: #1dc5a3; } @media (min-width: 768px) { .landing-page { /* Navbar positioning foo */ /* The navbar becomes detached from the top, so we round the corners */ /* Bump up size of carousel content */ } .landing-page .navbar-wrapper .container { padding-right: 15px; padding-left: 15px; } .landing-page .navbar-wrapper .navbar { padding-right: 0; padding-left: 0; } .landing-page .navbar-wrapper .navbar { border-radius: 4px; } .landing-page .carousel-caption p { margin-bottom: 20px; font-size: 14px; line-height: 1.4; } .landing-page .featurette-heading { font-size: 50px; } } @media (max-width: 992px) { .landing-page .carousel-image { display: none; } } @media (max-width: 768px) { .landing-page .carousel-caption, .landing-page .carousel-caption.blank { left: 5%; top: 80px; } .landing-page .carousel-caption h1 { font-size: 28px; } .landing-page .navbar.navbar-scroll .navbar-brand { margin-top: 6px; } .landing-page .navbar-default { background-color: #fff; border-color: #fff; padding: 15px 0; } .landing-page .navbar-default .navbar-nav > .active > a:focus { background: transparent; color: inherit; } .landing-page .navbar-default .nav li a { color: #676a6c; } .landing-page .navbar-default .nav li a:hover { color: #1ab394; } .landing-page .navbar-wrapper .navbar { padding-top: 0; padding-bottom: 5px; border-bottom: 1px solid #e7eaec; border-radius: 0; } .landing-page .nav > li > a { padding: 10px 10px 15px 10px; } .landing-page .navbar-nav > li > a { padding: 20px 10px; } .landing-page .navbar .navbar-brand { margin-top: 5px; border-radius: 5px; font-size: 12px; padding: 10px; height: auto; } .landing-page .navbar-wrapper .navbar { padding-left: 15px; padding-right: 5px; } .landing-page .navbar-default .navbar-nav > .active > a, .landing-page .navbar-default .navbar-nav > .active > a:hover { color: inherit; } .landing-page .carousel-control { display: none; } } @media (min-width: 992px) { .landing-page .featurette-heading { margin-top: 120px; } } @media (max-width: 768px) { .landing-page .navbar .navbar-header { display: block; float: none; } .landing-page .navbar .navbar-header .navbar-toggle { background-color: #ffffff; padding: 9px 10px; border: none; } .landing-page .nav.navbar-right { flex-direction: column; } } .landing-page .navbar-toggle { color: #ddd; float: right; } .landing-page .navbar-toggle i { font-size: 24px; } body.rtls { text-align: right !important; /* Theme config */ } body.rtls .nav-second-level li a { padding: 7px 35px 7px 10px; } body.rtls .ibox-title h5 { float: right; } body.rtls .float-right { float: left !important; } body.rtls .float-left { float: right !important; } body.rtls .ibox-title { padding: 15px 15px 8px 15px; } body.rtls .ibox-title .label { float: left; } body.rtls #small-chat { right: auto; left: 20px; } body.rtls .small-chat-box { right: auto; left: 75px; } body.rtls .ibox-tools { float: left; position: static; } body.rtls .stat-percent { float: left; } body.rtls .navbar-right { float: left !important; } body.rtls .navbar-top-links li:last-child { margin-left: 40px; margin-right: 0; } body.rtls .minimalize-styl-2 { float: right; margin: 14px 20px 5px 5px; } body.rtls .feed-element > .float-left { margin-left: 10px; margin-right: 0; } body.rtls .timeline-item .date { text-align: left; } body.rtls .timeline-item .date i { left: 0; right: auto; } body.rtls .timeline-item .content { border-right: 1px solid #e7eaec; border-left: none; } body.rtls .theme-config { left: 0; right: auto; } body.rtls .spin-icon { border-radius: 0 20px 20px 0; } body.rtls .toast-close-button { float: left; } body.rtls #toast-container > .toast:before { margin: auto -1.5em auto 0.5em; } body.rtls #toast-container > div { padding: 15px 50px 15px 15px; } body.rtls #toast-container > div { background-position: 95% center; } body.rtls .center-orientation .vertical-timeline-icon i { margin-left: 0; margin-right: -12px; } body.rtls .vertical-timeline-icon i { right: 50%; left: auto; margin-left: auto; margin-right: -12px; } body.rtls .file-box { float: right; } body.rtls ul.notes li { float: right; } body.rtls .chat-users, body.rtls .chat-statistic { margin-right: -30px; margin-left: auto; } body.rtls .dropdown-menu > li > a { text-align: right; } body.rtls .b-r { border-left: 1px solid #e7eaec; border-right: none; } body.rtls .dd-list .dd-list { padding-right: 30px; padding-left: 0; } body.rtls .dd-item > button { float: right; } body.rtls .theme-config-box { margin-left: -220px; margin-right: 0; } body.rtls .theme-config-box.show { margin-left: 0; margin-right: 0; } body.rtls .spin-icon { right: 0; left: auto; } body.rtls .skin-settings { margin-right: 40px; margin-left: 0; } body.rtls .skin-settings { direction: ltr; } body.rtls .footer.fixed { margin-right: 220px; margin-left: 0; } body.rtls .navbar-static-top .dropdown-menu { left: 0; right: auto; } body.rtls .social-footer .social-comment img, body.rtls .social-avatar img { margin-left: 10px; margin-right: 0; } body.rtls .sidebar-container .sidebar-message > a > .float-left { margin-left: 10px; margin-right: 0; } body.rtls .setings-item .switch { margin-left: 5px; } body.rtls .nav > li > a i { margin-left: 6px; } @media (max-width: 992px) { body.rtls .chat-users, body.rtls .chat-statistic { margin-right: 0; } } body.rtls.mini-navbar .footer.fixed, body.body-small.mini-navbar .footer.fixed { margin: 0 70px 0 0; } body.rtls.mini-navbar.fixed-sidebar .footer.fixed, body.body-small.mini-navbar .footer.fixed { margin: 0 0 0 0; } body.rtls.top-navigation .navbar-toggle { float: right; margin-left: 15px; margin-right: 15px; } .body-small.rtls.top-navigation .navbar-header { float: none; } body.rtls.top-navigation #page-wrapper { margin: 0; } body.rtls.mini-navbar.fixed-sidebar #page-wrapper { margin: 0 0 0 0; } body.rtls.body-small.fixed-sidebar.mini-navbar #page-wrapper { margin: 0 220px 0 0; } body.rtls.body-small.fixed-sidebar.mini-navbar .navbar-static-side { width: 220px; } .body-small.rtls .navbar-fixed-top { margin-right: 0; } .body-small.rtls .navbar-header { float: right; } body.rtls .navbar-top-links li:last-child { margin-left: 20px; } body.rtls .top-navigation #page-wrapper, body.rtls.mini-navbar .top-navigation #page-wrapper, body.rtls.mini-navbar.top-navigation #page-wrapper { margin: 0; } body.rtls .top-navigation .footer.fixed, body.rtls.top-navigation .footer.fixed { margin: 0; } @media (max-width: 768px) { body.rtls .navbar-top-links li:last-child { margin-left: 10px; } .navbar-top-links li a { padding: 20px 5px; } .body-small.rtls #page-wrapper { position: inherit; margin: 0 0 0 0; min-height: 1000px; } .rtls.fixed-sidebar.body-small .navbar-static-side { display: none; z-index: 2001; position: fixed; width: 220px; } .rtls.fixed-sidebar.body-small.mini-navbar .navbar-static-side { display: block; } } .rtls .ltr-support { direction: ltr; } .rtls.mini-navbar .nav-second-level, .rtls.mini-navbar li.active .nav-second-level { left: auto; right: 70px; } .rtls #right-sidebar { left: -260px; right: auto; } .rtls #right-sidebar.sidebar-open { left: 0; } /* * * This is style for skin config * Use only in demo theme * */ .theme-config { position: absolute; top: 90px; right: 0; overflow: hidden; } .theme-config-box { margin-right: -220px; position: relative; z-index: 2100; transition-duration: 0.8s; } .theme-config-box.show { margin-right: 0; } .spin-icon { background: #1ab394; position: absolute; padding: 7px 10px 7px 13px; border-radius: 20px 0 0 20px; font-size: 16px; top: 0; left: 0; width: 40px; color: #fff; cursor: pointer; } .skin-settings { width: 220px; margin-left: 40px; background: #f3f3f4; } .skin-settings .title { background: #efefef; text-align: center; text-transform: uppercase; font-weight: 600; display: block; padding: 10px 15px; font-size: 12px; } .setings-item { padding: 10px 30px; } .setings-item.skin { text-align: center; } .setings-item .switch { float: right; } .skin-name a { text-transform: uppercase; } .setings-item a { color: #fff; } .default-skin, .blue-skin, .ultra-skin, .yellow-skin { text-align: center; } .default-skin { font-weight: 600; background: #283A49; } .default-skin:hover { background: #1e2e3d; } .blue-skin { font-weight: 600; background: url("patterns/header-profile-skin-1.png") repeat scroll 0 0; } .blue-skin:hover { background: #0d8ddb; } .yellow-skin { font-weight: 600; background: url("patterns/header-profile-skin-3.png") repeat scroll 0 100%; } .yellow-skin:hover { background: #ce8735; } .ultra-skin { padding: 20px 10px; font-weight: 600; background: url("patterns/3.png") repeat scroll 0 0; } .ultra-skin:hover { background: url("patterns/4.png") repeat scroll 0 0; } /* * * SKIN 1 - INSPINIA - Responsive Admin Theme * NAME - Blue light * */ .skin-1 .minimalize-styl-2 { margin: 14px 5px 5px 30px; } .skin-1 .navbar-top-links li:last-child { margin-right: 30px; } .skin-1.fixed-nav .minimalize-styl-2 { margin: 14px 5px 5px 15px; } .skin-1 .spin-icon { background: #0e9aef !important; } .skin-1 .nav-header { background-color: #0e9aef; background-image: url('patterns/header-profile-skin-1.png'); } .skin-1.mini-navbar .nav-second-level { background: #3e495f; } .skin-1 .breadcrumb { background: transparent; } .skin-1 .page-heading { border: none; } .skin-1 .nav > li.active { background: #3a4459; } .skin-1 .nav > li > a { color: #9ea6b9; } .skin-1 ul.nav-second-level { background-color: inherit; } .skin-1 .nav > li.active > a { color: #fff; } .skin-1 .navbar-minimalize { background: #0e9aef; border-color: #0e9aef; } body.skin-1 { background: #3e495f; } .skin-1 .navbar-static-top { background: #ffffff; } .skin-1 .dashboard-header { background: transparent; border-bottom: none !important; border-top: none; padding: 20px 30px 10px 30px; } .fixed-nav.skin-1 .navbar-fixed-top { background: #fff; } .skin-1 .wrapper-content { padding: 30px 15px; } .skin-1 #page-wrapper { background: #f4f6fa; } .skin-1 .ibox-title, .skin-1 .ibox-content { border-width: 1px; } .skin-1 .ibox-content:last-child { border-style: solid solid solid solid; } .skin-1 .nav > li.active { border: none; } .skin-1 .nav-header { padding: 35px 25px 25px 25px; } .skin-1 .nav-header a.dropdown-toggle { color: #fff; margin-top: 10px; } .skin-1 .nav-header a.dropdown-toggle .text-muted { color: #fff; opacity: 0.8; } .skin-1 .profile-element { text-align: center; } .skin-1 .rounded-circle { border-radius: 5px; } .skin-1 .navbar-default .nav > li > a:hover, .skin-1 .navbar-default .nav > li > a:focus { background: #3a4459; color: #fff; } .skin-1 .nav.nav-tabs > li.active > a { color: #555; } .skin-1 .nav.nav-tabs > li.active { background: transparent; } /* * * SKIN 2 - INSPINIA - Responsive Admin Theme * NAME - Inspinia Ultra * */ body.skin-2 { color: #565758 !important; } .skin-2 .minimalize-styl-2 { margin: 14px 5px 5px 25px; } .skin-2 .navbar-top-links li:last-child { margin-right: 30px; } .skin-2 .spin-icon { background: #23c6c8 !important; } .skin-2 .nav-header { background-color: #23c6c8; background-image: url('patterns/header-profile-skin-2.png'); } .skin-2.mini-navbar .nav-second-level { background: #ededed; } .skin-2 .breadcrumb { background: transparent; } .skin-2.fixed-nav .minimalize-styl-2 { margin: 14px 5px 5px 15px; } .skin-2 .page-heading { border: none; background: rgba(255, 255, 255, 0.7); } .skin-2 ul.nav-second-level { background-color: inherit; } .skin-2 .nav > li.active { background: #e0e0e0; } .skin-2 .logo-element { padding: 17px 0; } .skin-2 .nav > li > a, .skin-2 .welcome-message { color: #edf6ff; } .skin-2 #top-search::-moz-placeholder { color: #edf6ff; opacity: 0.5; } .skin-2 #side-menu > li > a, .skin-2 .nav.nav-second-level > li > a { color: #586b7d; } .skin-2 .nav > li.active > a { color: #213a53; } .skin-2.mini-navbar .nav-header { background: #213a53; } .skin-2 .navbar-minimalize { background: #23c6c8; border-color: #23c6c8; } .skin-2 .border-bottom { border-bottom: none !important; } .skin-2 #top-search { color: #fff; } body.skin-2 #wrapper { background-color: #ededed; } .skin-2 .navbar-static-top { background: #213a53; } .fixed-nav.skin-2 .navbar-fixed-top { background: #213a53; border-bottom: none !important; } .skin-2 .nav-header { padding: 30px 25px 30px 25px; } .skin-2 .dashboard-header { background: rgba(255, 255, 255, 0.4); border-bottom: none !important; border-top: none; padding: 20px 30px 20px 30px; } .skin-2 .wrapper-content { padding: 30px 15px; } .skin-2 .dashoard-1 .wrapper-content { padding: 0 30px 25px 30px; } .skin-2 .ibox-title { background: rgba(255, 255, 255, 0.7); border: none; margin-bottom: 1px; } .skin-2 .ibox-content { background: rgba(255, 255, 255, 0.4); border: none !important; } .skin-2 #page-wrapper { background: #f6f6f6; background: -webkit-radial-gradient(center, ellipse cover, #f6f6f6 20%, #d5d5d5 100%); background: -o-radial-gradient(center, ellipse cover, #f6f6f6 20%, #d5d5d5 100%); background: -ms-radial-gradient(center, ellipse cover, #f6f6f6 20%, #d5d5d5 100%); background: radial-gradient(ellipse at center, #f6f6f6 20%, #d5d5d5 100%); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#f6f6f6, endColorstr=#d5d5d5)"; } .skin-2 .ibox-title, .skin-2 .ibox-content { border-width: 1px; } .skin-2 .ibox-content:last-child { border-style: solid solid solid solid; } .skin-2 .nav > li.active { border: none; } .skin-2 .nav-header a.dropdown-toggle { color: #edf6ff; margin-top: 10px; } .skin-2 .nav-header a.dropdown-toggle .text-muted { color: #edf6ff; opacity: 0.8; } .skin-2 .rounded-circle { border-radius: 10px; } .skin-2 .nav.navbar-top-links > li > a:hover, .skin-2 .nav.navbar-top-links > li > a:focus { background: #1a2d41; } .skin-2 .navbar-default .nav > li > a:hover, .skin-2 .navbar-default .nav > li > a:focus { background: #e0e0e0; color: #213a53; } .skin-2 .nav.nav-tabs > li.active > a { color: #555; } .skin-2 .nav.nav-tabs > li.active { background: transparent; } /* * * SKIN 3 - INSPINIA - Responsive Admin Theme * NAME - Yellow/purple * */ .skin-3 .minimalize-styl-2 { margin: 14px 5px 5px 30px; } .skin-3 .navbar-top-links li:last-child { margin-right: 30px; } .skin-3.fixed-nav .minimalize-styl-2 { margin: 14px 5px 5px 15px; } .skin-3 .spin-icon { background: #ecba52 !important; } body.boxed-layout.skin-3 #wrapper { background: #3e2c42; } .skin-3 .nav-header { background-color: #ecba52; background-image: url('patterns/header-profile-skin-3.png'); } .skin-3.mini-navbar .nav-second-level { background: #3e2c42; } .skin-3 .breadcrumb { background: transparent; } .skin-3 .page-heading { border: none; } .skin-3 ul.nav-second-level { background-color: inherit; } .skin-3 .nav > li.active { background: #38283c; } .fixed-nav.skin-3 .navbar-fixed-top { background: #fff; } .skin-3 .nav > li > a { color: #948b96; } .skin-3 .nav > li.active > a { color: #fff; } .skin-3 .navbar-minimalize { background: #ecba52; border-color: #ecba52; } body.skin-3 { background: #3e2c42; } .skin-3 .navbar-static-top { background: #ffffff; } .skin-3 .dashboard-header { background: transparent; border-bottom: none !important; border-top: none; padding: 20px 30px 10px 30px; } .skin-3 .wrapper-content { padding: 30px 15px; } .skin-3 #page-wrapper { background: #f4f6fa; } .skin-3 .ibox-title, .skin-3 .ibox-content { border-width: 1px; } .skin-3 .ibox-content:last-child { border-style: solid solid solid solid; } .skin-3 .nav > li.active { border: none; } .skin-3 .nav-header { padding: 35px 25px 25px 25px; } .skin-3 .nav-header a.dropdown-toggle { color: #fff; margin-top: 10px; } .skin-3 .nav-header a.dropdown-toggle .text-muted { color: #fff; opacity: 0.8; } .skin-3 .profile-element { text-align: center; } .skin-3 .rounded-circle { border-radius: 5px; } .skin-3 .navbar-default .nav > li > a:hover, .skin-3 .navbar-default .nav > li > a:focus { background: #38283c; color: #fff; } .skin-3 .nav.nav-tabs > li.active > a { color: #555; } .skin-3 .nav.nav-tabs > li.active { background: transparent; } /* * * SKIN 4 - INSPINIA - Responsive Admin Theme * NAME - Light-Skin * */ body.light-skin { background-color: #f9f9f9; color: #3e5476; } .light-skin { /* For handle diferent bg color in MVC version */ } .light-skin .select2-container--default .select2-selection--single, .light-skin .select2-container--default .select2-selection--multiple, .light-skin .select2-container--default .select2-selection--single .select2-selection__arrow { height: 2.05rem; } .light-skin .select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 32px; } .light-skin .navbar-fixed-top, .light-skin .navbar-static-top { background-color: transparent; } .light-skin .Dashboard_2 .navbar.navbar-static-top, .light-skin .Dashboard_3 .navbar.navbar-static-top, .light-skin .Dashboard_4_1 .navbar.navbar-static-top, .light-skin .ComposeEmail .navbar.navbar-static-top, .light-skin .EmailView .navbar.navbar-static-top, .light-skin .Inbox .navbar.navbar-static-top, .light-skin .Metrics .navbar.navbar-static-top, .light-skin .Dashboard_5 .navbar.navbar-static-top { background: transparent; } .light-skin.fixed-nav .navbar-fixed-top { background-color: #FFFFFF; } .light-skin.mini-navbar .nav .nav-second-level { background-color: #f9f9f9; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1); } .light-skin.fixed-sidebar.mini-navbar .nav .nav-second-level, .light-skin.canvas-menu.mini-navbar .nav .nav-second-level { box-shadow: none; } .light-skin.canvas-menu nav.navbar-static-side { background-color: #f9f9f9; } .light-skin.canvas-menu.mini-navbar nav.navbar-static-side { box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1); } .light-skin.mini-navbar .nav-header { background-color: #f9f9f9; } .light-skin #page-wrapper.gray-bg, .light-skin #page-wrapper.bg-muted { background-color: #f9f9f9; } .light-skin .logo-element { color: #7c899a; } .light-skin nav > .sidebar-collapse > ul > li a { color: #7c899a; padding-top: 9px; padding-bottom: 9px; } .light-skin nav > .sidebar-collapse .nav-second-level li a { padding-top: 7px; padding-bottom: 7px; } .light-skin .nav > li.active > a { color: #384d6c; font-weight: 700; } .light-skin .nav-header a { color: #384d6c; } .light-skin .navbar-default .nav > li > a:hover, .light-skin .navbar-default .nav > li > a:focus { background-color: inherit; color: #384d6c; font-weight: 700; } .light-skin .nav-header .font-bold { font-size: 12px; font-weight: 700; color: #384d6c; } .light-skin .nav-header .text-muted { color: #8291a3 !important; font-size: 12px; } .light-skin .nav-header { background-color: #f9f9f9; background-image: none; padding: 20px 25px 20px 25px; } .light-skin .profile-element img { border-radius: 6px !important; } .light-skin .nav > li.active { background: #f9f9f9; } .light-skin ul.nav-second-level { background: #f9f9f9; } .light-skin .dashboard-header { border-bottom: none !important; border-top: 0; border-radius: 4px; padding: 20px 20px 20px 20px; margin: 10px 10px 0 10px; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1); } .light-skin .page-heading { padding-bottom: 10px; } .light-skin .ibox-title { background-color: transparent; border: none; padding-left: 5px; } .light-skin .ibox-title h5 { font-size: 12px; } .light-skin .ibox-tools { right: 5px; } .light-skin .ibox-tools a { color: #7c899a !important; } .light-skin .ibox-heading { background-color: #fff; margin-bottom: 20px; } .light-skin .ibox-content { box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1); border-radius: 4px; border: none; background-color: #FFFFFF; } .light-skin .breadcrumb { background-color: transparent; } .light-skin .minimalize-styl-2 { margin: 14px 5px 5px 30px; } .light-skin .footer { border: none; background: none; } .light-skin .sidebar-panel { background-color: #fff; } .light-skin #page-wrapper > .border-bottom, .light-skin .ibox.border-bottom { border-bottom: transparent !important; } .light-skin .fh-breadcrumb { height: calc(100% - 176px); } .light-skin.top-navigation #page-wrapper > .border-bottom { border-bottom: 1px solid #e7eaec !important; } .light-skin .wrapper.white-bg { background-color: transparent; } .light-skin .ibox-tools a.btn-primary { color: #FFFFFF !important; } .light-skin .chat-discussion { background-color: #FFFFFF; } .light-skin .ibox-footer { margin-top: 4px; border: none; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1); border-radius: 4px; } .light-skin .contact-box, .light-skin .social-feed-box, .light-skin .vertical-timeline-content { box-shadow: 0 1px 8px rgba(0, 0, 0, 0.05), 0 1px 2px rgba(0, 0, 0, 0.1); border: none; } .light-skin .navbar-default .landing_link a, .light-skin .navbar-default .special_link a { color: #FFFFFF; } body.md-skin { font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; background-color: #ffffff; } .md-skin .nav-header { background: url("patterns/4.png") no-repeat; } .md-skin .label, .md-skin .badge { font-family: 'Roboto'; } .md-skin ul.nav-second-level { background-color: inherit; } .md-skin .font-bold { font-weight: 500; } .md-skin .wrapper-content { padding: 30px 20px 40px; } @media (max-width: 768px) { .md-skin .wrapper-content { padding: 30px 0 40px; } } .md-skin .page-heading { border-bottom: none !important; border-top: 0; padding: 0 10px 20px 10px; box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.34), 0 0 6px 0 rgba(0, 0, 0, 0.14); } .md-skin .full-height-layout .page-heading { border-bottom: 1px solid #e7eaec !important; } .md-skin .ibox { clear: both; margin-bottom: 25px; margin-top: 0; padding: 0; 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); } .md-skin .ibox.border-bottom { border-bottom: none !important; } .md-skin .ibox-title, .md-skin .ibox-content { border-style: none; } .md-skin .ibox-title h5 { font-size: 16px; font-weight: 400; } .md-skin a.close-canvas-menu { color: #ffffff; } .md-skin .welcome-message { color: #ffffff !important; font-weight: 300; } .md-skin #top-search::-moz-placeholder { color: #ffffff; } .md-skin #top-search::-webkit-input-placeholder { color: #ffffff; } .md-skin #nestable-output, .md-skin #nestable2-output { font-family: 'Roboto', lucida grande, lucida sans unicode, helvetica, arial, sans-serif; } .md-skin .landing-page { font-family: 'Roboto', helvetica, arial, sans-serif; } .md-skin .landing-page.navbar-default.navbar-scroll { background-color: #fff !important; } .md-skin .landing-page.navbar-default { background-color: transparent !important; box-shadow: none; } .md-skin .landing-page.navbar-default .nav li a { font-family: 'Roboto', helvetica, arial, sans-serif; } .md-skin .nav > li > a { color: #676a6c; padding: 14px 20px 14px 25px; } .md-skin .nav.navbar-right > li > a { color: #ffffff; } .md-skin .nav > li.active > a { color: #5b5d5f; font-weight: 700; } .md-skin .navbar-default .nav > li > a:hover, .md-skin .navbar-default .nav > li > a:focus { font-weight: 700; color: #5b5d5f; } .md-skin .nav .open > a, .md-skin .nav .open > a:hover, .md-skin .nav .open > a:focus { background: #1ab394; } .md-skin .navbar-top-links li { display: inline-table; } .md-skin .navbar-top-links .dropdown-menu li { display: block; } .md-skin .pace-done .nav-header { transition: all 0.4s; } .md-skin .nav > li.active { background: #f8f8f9; } .md-skin .nav-second-level li a { padding: 7px 10px 7px 52px; } .md-skin .nav-third-level li a { padding-left: 62px; } .md-skin .navbar-top-links li a { padding: 20px 10px; min-height: 50px; } .md-skin .nav > li > a { font-weight: 400; } .md-skin .navbar-static-side .nav > li > a:focus, .md-skin .navbar-static-side .nav > li > a:hover { background-color: inherit; } .md-skin .navbar-top-links .dropdown-menu li a { padding: 3px 20px; min-height: inherit; } .md-skin .nav-header .navbar-fixed-top a { color: #ffffff; } .md-skin .nav-header .text-muted { color: #ffffff !important; } .md-skin .navbar-form-custom .form-control { font-weight: 300; } .md-skin .mini-navbar .nav-second-level { background-color: inherit; } .md-skin .mini-navbar li.active .nav-second-level { left: 65px; } .md-skin .canvas-menu.mini-navbar .nav-second-level { background: inherit; } .md-skin .pace-done .navbar-static-side, .md-skin .pace-done .nav-header, .md-skin .pace-done li.active, .md-skin .pace-done #page-wrapper, .md-skin .pace-done .footer { -webkit-transition: all 0.4s; -moz-transition: all 0.4s; -o-transition: all 0.4s; transition: all 0.4s; } .md-skin .navbar-fixed-top { background: #fff; transition-duration: 0.4s; z-index: 2030; border-bottom: none !important; } .md-skin .navbar-fixed-top, .md-skin .navbar-static-top { background-color: #1ab394 !important; 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); } .md-skin .navbar-static-side { 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); } .md-skin #right-sidebar { 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); border: none; z-index: 900; } .md-skin .white-bg .navbar-fixed-top, .md-skin .white-bg .navbar-static-top { background: #fff !important; } .md-skin .contact-box { 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); border: none; } .md-skin .dashboard-header { border-bottom: none !important; border-top: 0; padding: 20px 20px 20px 20px; margin: 30px 20px 0 20px; 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); } @media (max-width: 768px) { .md-skin .dashboard-header { margin: 20px 0 0 0; } } .md-skin ul.notes li div { 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); } .md-skin .file { border: none; 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); } .md-skin .mail-box { background-color: #ffffff; 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); padding: 0; margin-bottom: 20px; border: none; } .md-skin .mail-box-header { border: none; background-color: #ffffff; 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); padding: 30px 20px 20px 20px; } .md-skin .mailbox-content { border: none; padding: 20px; background: #ffffff; } .md-skin .social-feed-box { border: none; background: #fff; margin-bottom: 15px; 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); } .md-skin.landing-page .navbar-default { background-color: transparent !important; border-color: transparent; transition: all 0.3s ease-in-out 0s; box-shadow: none; } .md-skin.landing-page .navbar-default.navbar-scroll, .md-skin.landing-page.body-small .navbar-default { background-color: #ffffff !important; } .md-skin.landing-page .nav > li.active { background: inherit; } .md-skin.landing-page .navbar-scroll .navbar-nav > li > a { padding: 20px 10px; } .md-skin.landing-page .navbar-default .nav li a { font-family: 'Roboto', helvetica, arial, sans-serif; } .md-skin.landing-page .nav > li > a { padding: 25px 10px 15px 10px; } .md-skin.landing-page .navbar-default .navbar-nav > li > a:hover, .md-skin.landing-page .navbar-default .navbar-nav > li > a:focus { background: inherit; color: #1ab394; } .md-skin.landing-page.body-small .nav.navbar-right > li > a { color: #676a6c; } .md-skin .landing_link a, .md-skin .special_link a { color: #ffffff !important; } .md-skin.canvas-menu.mini-navbar .nav-second-level { background: #f8f8f9; } .md-skin.mini-navbar .nav-second-level { background-color: #ffffff; 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); } .md-skin.mini-navbar .nav-second-level li a { padding-left: 0; } .md-skin.mini-navbar.fixed-sidebar .nav-second-level li a { padding-left: 52px; } .md-skin.top-navigation .nav.navbar-right > li > a { padding: 15px 20px; color: #676a6c; } .md-skin.top-navigation .nav > li a:hover, .md-skin .top-navigation .nav > li a:focus, .md-skin.top-navigation .nav .open > a, .md-skin.top-navigation .nav .open > a:hover, .md-skin.top-navigation .nav .open > a:focus { color: #1ab394; background: #ffffff; } .md-skin.top-navigation .nav > li.active a { color: #1ab394; background: #ffffff; } .md-skin.fixed-nav #side-menu { background-color: #fff; } .md-skin.fixed-nav #wrapper.top-navigation #page-wrapper { margin-top: 0; } .md-skin.fixed-sidebar.mini-navbar #page-wrapper { margin: 0 0 0 0; } .md-skin.body-small.fixed-sidebar.mini-navbar .navbar-static-side { width: 220px; background-color: #ffffff; } .md-skin.boxed-layout #wrapper { background-color: #ffffff; } .md-skin.canvas-menu nav.navbar-static-side { z-index: 2001; background: #ffffff; height: 100%; position: fixed; display: none; } @media (min-width: 768px) { #page-wrapper { position: inherit; min-height: 100vh; } .navbar-static-side { z-index: 2001; width: 220px; } .navbar-top-links .dropdown-messages, .navbar-top-links .dropdown-tasks, .navbar-top-links .dropdown-alerts { margin-left: auto; } } @media (max-width: 768px) { #page-wrapper { position: inherit; margin: 0 0 0 0; min-height: 100vh; width: 100%; } .body-small .navbar-static-side { display: block; z-index: 2001; width: 0; overflow: hidden; } .body-small.mini-navbar .navbar-static-side { display: block; overflow: visible; } .lock-word { display: none; } .navbar-form-custom { display: none; } .navbar-header { display: inline; float: left; } .sidebar-panel { z-index: 2; position: relative; width: auto; min-height: 100% !important; } .sidebar-content .wrapper { padding-right: 0; z-index: 1; } .fixed-sidebar.body-small .navbar-static-side { display: none; z-index: 2001; position: fixed; width: 220px; } .fixed-sidebar.body-small.mini-navbar .navbar-static-side { display: block; } .ibox-tools { float: none; text-align: right; display: block; } .navbar-static-side { display: none; } body:not(.mini-navbar) { -webkit-transition: background-color 500ms linear; -moz-transition: background-color 500ms linear; -o-transition: background-color 500ms linear; -ms-transition: background-color 500ms linear; transition: background-color 500ms linear; background-color: #f3f3f4; } } @media (max-width: 350px) { .timeline-item .date { text-align: left; width: 110px; position: relative; padding-top: 30px; } .timeline-item .date i { position: absolute; top: 0; left: 15px; padding: 5px; width: 30px; text-align: center; border: 1px solid #e7eaec; background: #f8f8f8; } .timeline-item .content { border-left: none; border-top: 1px solid #e7eaec; padding-top: 10px; min-height: 100px; } .nav.navbar-top-links li.dropdown { display: none; } .ibox-tools { float: none; text-align: left; display: inline-block; } } /* Only demo */ @media (max-width: 1000px) { .welcome-message { display: none; } } @media print { nav.navbar-static-side { display: none; } body { overflow: visible !important; } #page-wrapper { margin: 0; } } ================================================ FILE: web/static/js/inspinia.js ================================================ /* * * INSPINIA - Responsive Admin Theme * version 2.9.3 * */ $(document).ready(function () { // Fast fix bor position issue with Propper.js // Will be fixed in Bootstrap 4.1 - https://github.com/twbs/bootstrap/pull/24092 //Popper.Defaults.modifiers.computeStyle.gpuAcceleration = false; // Add body-small class if window less than 768px if (window.innerWidth < 769) { $('body').addClass('body-small') } else { $('body').removeClass('body-small') } // MetisMenu //var sideMenu = $('#side-menu').metisMenu(); // Collapse ibox function $('.collapse-link').on('click', function (e) { e.preventDefault(); var ibox = $(this).closest('div.ibox'); var button = $(this).find('i'); var content = ibox.children('.ibox-content'); content.slideToggle(200); button.toggleClass('fa-chevron-up').toggleClass('fa-chevron-down'); ibox.toggleClass('').toggleClass('border-bottom'); setTimeout(function () { ibox.resize(); ibox.find('[id^=map-]').resize(); }, 50); }); // Close ibox function $('.close-link').on('click', function (e) { e.preventDefault(); var content = $(this).closest('div.ibox'); content.remove(); }); // Fullscreen ibox function $('.fullscreen-link').on('click', function (e) { e.preventDefault(); var ibox = $(this).closest('div.ibox'); var button = $(this).find('i'); $('body').toggleClass('fullscreen-ibox-mode'); button.toggleClass('fa-expand').toggleClass('fa-compress'); ibox.toggleClass('fullscreen'); setTimeout(function () { $(window).trigger('resize'); }, 100); }); // Close menu in canvas mode $('.close-canvas-menu').on('click', function (e) { e.preventDefault(); $("body").toggleClass("mini-navbar"); SmoothlyMenu(); }); // Run menu of canvas //$('body.canvas-menu .sidebar-collapse').slimScroll({ // height: '100%', // railOpacity: 0.9 //}); // Open close right sidebar $('.right-sidebar-toggle').on('click', function (e) { e.preventDefault(); $('#right-sidebar').toggleClass('sidebar-open'); }); // Initialize slimscroll for right sidebar //$('.sidebar-container').slimScroll({ // height: '100%', // railOpacity: 0.4, // wheelStep: 10 //}); // Open close small chat $('.open-small-chat').on('click', function (e) { e.preventDefault(); $(this).children().toggleClass('fa-comments').toggleClass('fa-times'); $('.small-chat-box').toggleClass('active'); }); // Initialize slimscroll for small chat //$('.small-chat-box .content').slimScroll({ // height: '234px', // railOpacity: 0.4 //}); // Small todo handler $('.check-link').on('click', function () { var button = $(this).find('i'); var label = $(this).next('span'); button.toggleClass('fa-check-square').toggleClass('fa-square-o'); label.toggleClass('todo-completed'); return false; }); // Append config box / Only for demo purpose // Uncomment on server mode to enable XHR calls //$.get("skin-config2.html", function (data) { // if (!$('body').hasClass('no-skin-config')) // $('body').append(data); //}); // Minimalize menu $('.navbar-minimalize').on('click', function (event) { event.preventDefault(); $("body").toggleClass("mini-navbar"); SmoothlyMenu(); }); // Tooltips demo //$('.tooltip-demo').tooltip({ // selector: "[data-toggle=tooltip]", // container: "body" //}); // Move right sidebar top after scroll $(window).scroll(function () { if ($(window).scrollTop() > 0 && !$('body').hasClass('fixed-nav')) { $('#right-sidebar').addClass('sidebar-top'); } else { $('#right-sidebar').removeClass('sidebar-top'); } }); //$("[data-toggle=popover]") // .popover(); // Add slimscroll to element //$('.full-height-scroll').slimscroll({ // height: '100%' //}) }); // Minimalize menu when screen is less than 768px $(window).bind("resize", function () { if (window.innerWidth < 769) { $('body').addClass('body-small') } else { $('body').removeClass('body-small') } }); // Fixed Sidebar $(window).bind("load", function () { if ($("body").hasClass('fixed-sidebar')) { $('.sidebar-collapse').slimScroll({ height: '100%', railOpacity: 0.9 }); } }); // check if browser support HTML5 local storage function localStorageSupport() { return (('localStorage' in window) && window['localStorage'] !== null) } // Local Storage functions // Set proper body class and plugins based on user configuration $(document).ready(function () { if (localStorageSupport()) { var collapse = localStorage.getItem("collapse_menu"); var fixedsidebar = localStorage.getItem("fixedsidebar"); var fixednavbar = localStorage.getItem("fixednavbar"); var boxedlayout = localStorage.getItem("boxedlayout"); var fixedfooter = localStorage.getItem("fixedfooter"); var body = $('body'); if (fixedsidebar == 'on') { body.addClass('fixed-sidebar'); $('.sidebar-collapse').slimScroll({ height: '100%', railOpacity: 0.9 }); } if (collapse == 'on') { if (body.hasClass('fixed-sidebar')) { if (!body.hasClass('body-small')) { body.addClass('mini-navbar'); } } else { if (!body.hasClass('body-small')) { body.addClass('mini-navbar'); } } } if (fixednavbar == 'on') { $(".navbar-static-top").removeClass('navbar-static-top').addClass('navbar-fixed-top'); body.addClass('fixed-nav'); } if (boxedlayout == 'on') { body.addClass('boxed-layout'); } if (fixedfooter == 'on') { $(".footer").addClass('fixed'); } } }); // For demo purpose - animation css script //function animationHover(element, animation) { // element = $(element); // element.hover( // function () { // element.addClass('animated ' + animation); // }, // function () { // //wait for animation to finish before removing classes // window.setTimeout(function () { // element.removeClass('animated ' + animation); // }, 2000); // }); //} function SmoothlyMenu() { if (!$('body').hasClass('mini-navbar') || $('body').hasClass('body-small')) { // Hide menu in order to smoothly turn on when maximize menu $('#side-menu').hide(); // For smoothly turn on menu setTimeout( function () { $('#side-menu').fadeIn(400); }, 200); } else if ($('body').hasClass('fixed-sidebar')) { $('#side-menu').hide(); setTimeout( function () { $('#side-menu').fadeIn(400); }, 100); } else { // Remove all inline style from jquery fadeIn function to reset menu state $('#side-menu').removeAttr('style'); } } // Dragable panels function WinMove() { var element = "[class*=col]"; var handle = ".ibox-title"; var connect = "[class*=col]"; $(element).sortable( { handle: handle, connectWith: connect, tolerance: 'pointer', forcePlaceholderSize: true, opacity: 0.8 }) .disableSelection(); } ================================================ FILE: web/static/js/language.js ================================================ (function ($) { function xml2json(Xml) { var tempvalue, tempJson = {}; $(Xml).each(function() { var tagName = ($(this).attr('id') || this.tagName); tempvalue = (this.childElementCount == 0) ? this.textContent : xml2json($(this).children()); switch ($.type(tempJson[tagName])) { case 'undefined': tempJson[tagName] = tempvalue; break; case 'object': tempJson[tagName] = Array(tempJson[tagName]); case 'array': tempJson[tagName].push(tempvalue); } }); return tempJson; } function setCookie (c_name, value, expiredays) { var exdate = new Date(); exdate.setDate(exdate.getDate() + expiredays); document.cookie = c_name + '=' + escape(value) + ((expiredays == null) ? '' : ';expires=' + exdate.toGMTString())+ '; path='+window.nps.web_base_url+'/;'; } function getCookie (c_name) { if (document.cookie.length > 0) { c_start = document.cookie.indexOf(c_name + '='); if (c_start != -1) { c_start = c_start + c_name.length + 1; c_end = document.cookie.indexOf(';', c_start); if (c_end == -1) c_end = document.cookie.length; return unescape(document.cookie.substring(c_start, c_end)); } } return null; } function setchartlang (langobj,chartobj) { if ( $.type (langobj) == 'string' ) return langobj; if ( $.type (langobj) == 'chartobj' ) return false; var flag = true; for (key in langobj) { var item = key; children = (chartobj.hasOwnProperty(item)) ? setchartlang (langobj[item],chartobj[item]) : setchartlang (langobj[item],undefined); switch ($.type(children)) { case 'string': if ($.type(chartobj[item]) != 'string' ) continue; case 'object': chartobj[item] = (children['value'] || children); default: flag = false; } } if (flag) { return {'value':(langobj[languages['current']] || langobj[languages['default']] || 'N/A')}} } $.fn.cloudLang = function () { $.ajax({ type: 'GET', url: window.nps.web_base_url + '/static/page/languages.xml', dataType: 'xml', success: function (xml) { languages['content'] = xml2json($(xml).children())['content']; languages['menu'] = languages['content']['languages']; languages['default'] = languages['content']['default']; languages['navigator'] = (getCookie ('lang') || navigator.language || navigator.browserLanguage); for(var key in languages['menu']){ $('#languagemenu').next().append('
  • ' + languages['menu'][key] +'
  • '); if ( key == languages['navigator'] ) languages['current'] = key; } $('#languagemenu').attr('lang',(languages['current'] || languages['default'])); $('body').setLang (''); } }); }; $.fn.setLang = function (dom) { languages['current'] = $('#languagemenu').attr('lang'); if ( dom == '' ) { $('#languagemenu span').text(' ' + languages['menu'][languages['current']]); if (languages['current'] != getCookie('lang')) setCookie('lang', languages['current']); if($("#table").length>0) $('#table').bootstrapTable('refreshOptions', { 'locale': languages['current']}); } $.each($(dom + ' [langtag]'), function (i, item) { var index = $(item).attr('langtag'); string = languages['content'][index.toLowerCase()]; switch ($.type(string)) { case 'string': break; case 'array': string = string[Math.floor((Math.random()*string.length))]; case 'object': string = (string[languages['current']] || string[languages['default']] || null); break; default: string = 'Missing language string "' + index + '"'; $(item).css('background-color','#ffeeba'); } if($.type($(item).attr('placeholder')) == 'undefined') { $(item).text(string); } else { $(item).attr('placeholder', string); } }); if ( !$.isEmptyObject(chartdatas) ) { setchartlang(languages['content']['charts'],chartdatas); for(var key in chartdatas){ if ($('#'+key).length == 0) continue; if($.type(chartdatas[key]) == 'object') charts[key] = echarts.init(document.getElementById(key)); charts[key].setOption(chartdatas[key], true); } } } })(jQuery); $(document).ready(function () { $('body').cloudLang(); $('body').on('click','li[lang]',function(){ $('#languagemenu').attr('lang',$(this).attr('lang')); $('body').setLang (''); }); }); var languages = {}; var charts = {}; var chartdatas = {}; var postsubmit; function langreply(langstr) { var langobj = languages['content']['reply'][langstr.replace(/[\s,\.\?]*/g,"").toLowerCase()]; if ($.type(langobj) == 'undefined') return langstr langobj = (langobj[languages['current']] || langobj[languages['default']] || langstr); return langobj } function submitform(action, url, postdata) { postsubmit = false; switch (action) { case 'start': case 'stop': case 'delete': var langobj = languages['content']['confirm'][action]; action = (langobj[languages['current']] || langobj[languages['default']] || 'Are you sure you want to ' + action + ' it?'); if (! confirm(action)) return; postsubmit = true; case 'add': case 'edit': $.ajax({ type: "POST", url: url, data: postdata, success: function (res) { alert(langreply(res.msg)); if (res.status) { if (postsubmit) {document.location.reload();}else{history.back(-1);} } } }); } } function changeunit(limit) { var size = ""; if (limit < 0.1 * 1024) { size = limit.toFixed(2) + "B"; } else if (limit < 0.1 * 1024 * 1024) { size = (limit / 1024).toFixed(2) + "KB"; } else if (limit < 0.1 * 1024 * 1024 * 1024) { size = (limit / (1024 * 1024)).toFixed(2) + "MB"; } else { size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB"; } var sizeStr = size + ""; var index = sizeStr.indexOf("."); var dou = sizeStr.substr(index + 1, 2); if (dou == "00") { return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2); } return size; } ================================================ FILE: web/static/page/error.html ================================================ nps error 404 not found,power by nps ================================================ FILE: web/static/page/languages.xml ================================================ NPS en-US 简体中文 English NPS - 管理 NPS Admin NPS - 登录 NPS login NPS - 注册 NPS Register 域名解析 Host TCP 隧道 TCP UDP 隧道 UDP HTTP 代理 HTTP proxy SOCKS 代理 SOCKS 5 私密代理 Secret P2P 连接 P2P 文件访问 File server 客户端列表 Client list 新增客户端 Add client 编辑客户端 Edit client 主机列表 Host list 新增主机 Add host 编辑主机 Edit host 隧道列表 - 客户端 ID: Tunnels list - Client ID: TCP 隧道列表 TCP list UDP 隧道列表 UDP list HTTP 代理列表 HTTP proxy list SOCKS 代理列表 SOCKS 5 list 私密代理列表 Secret list P2P 连接列表 P2P list 文件代理列表 File server list 新增 Add 编辑 Edit 客户端地址 Client address 新增 Add 管理员 Admin 所有 All 带宽 Bandwidth Basic 认证密码 Basic authentication password Basic 认证用户名 Basic authentication username 桥接模式 Bridging mode 客户端 ID Client ID 客户端状态 Client status 客户端 Client 关闭 Close 访问端命令 (SOCKS 5) Access command (SOCKS 5) 访问端命令 (透明代理) Access command (Transparent proxy) 访问端命令 (TCP) Access command (TCP) 访问端命令 Access command 客户端命令 Command 压缩 Compress 配置信息 Configuration information 允许客户端通过配置文件连接 Allow client connect by config file 客户端连接端口 Client connection port 连接数 (已建立) Connections (establish) 连接 Connect 版权所有 Copyright 处理器 CPU 加密 Crypt 当前连接数 Current connections 仪表盘 Dashboard 出口流量 Export Flow Flase 流量限制 Flow limit 进入 go 使用说明 Manual 主机 Host HTTP HTTP 端口 HTTP port HTTPS 证书 HTTPS cert HTTPS HTTPS 密钥 HTTPS key HTTPS 端口 HTTPS port 唯一标识密钥 Unique identification Key ID 流入带宽 In 入口流量 Inlet Flow IP 限制 IP restriction 负载 Load 本地路径 Local path 位置 Location 登录 Login 日志级别 Log level 退出 Logout 最大连接数 Maximum connections 最大隧道数 Maximum tunnels 内存 Memory No 离线 Offline 在线客户端 Online clients 在线 Online 开放 Open 选项 option 流出带宽 Out P2P 端口 P2P port 密码 Password 端口 Port 代理到服务器本地 Proxy to server local 公钥 Public vkey 带宽限制 Rate limit 更多说明 Read more 注册 Register 备注 Remark 请求头部信息修改 Header modify 请求主机信息修改 Host modify 运行状态 Run status 保存 Save 模式 Scheme 服务端 IP Server IP 服务端 IP Server IP 服务端端口 Server Port 服务端版本 Server version 查看 Show 网速 Speed 状态 Status 访问前缀 Strip prefix 交换空间 Swap memory 系统信息 System information 系统 System 目标 (IP:端口) Target (IP:Port) TCP 连接 (已建立) TCP connections (establish) 当前TCP连接数 TCP connections 客户端总数 Total clients 流量数据持久化 Traffic data persistence 流量统计 Traffic statistics True 隧道 Tunnel 连接类型 Connect type UDP 连接 (已建立) UDP connections (establish) 单位 Flow limit URL 路由 Url router 使用场景 Use Case 用户名 Username 用户 User 唯一验证密钥 Unique verify Key 版本 Version 虚拟内存 Virtual memory Web登陆密码 Password of Web login Web登陆用户名 Username of Web login 欢迎使用 Welcome to use Yes 唯一值,不填将自动生成 Unique, non-filling will be generated automatically 通提供一个公网可访问的本地文件服务,此模式仅客户端使用配置文件模式方可启动。 Provide a local file service accessible to the public network, which can only be started by the client using the profile mode. 将公网服务器1.1.1.1的8004端口作为HTTP代理,访问内网网站。 Use port 8004 of public server 1.1.1.1 as HTTP proxy to visit intranet site. 流量不经过公网服务器,受nat类型影响较大,不能保证100%成功,支持大部分nat类型。 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. 无需新增端口,实现访问内网服务器10.1.50.2的22端口,可防止其他人连接。还需要一个客户端作为访问端提供一个端口进行访问。 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. 将公网服务器1.1.1.1的8003端口作为SOCKS5代理,访问内网任意设备或者资源。 Use port 8003 of public server 1.1.1.1 as Socks5 proxy to access any device or resource in the Intranet. 通过公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现SSH连接。 Connect port 8001 of public server 1.1.1.1 to port 22 of Intranet machine 10.1.50.101 to realize SSH connection. 通过公网服务器1.1.1.1的53端口,访问内网机器10.1.50.101的53端口,使用DNS服务。 Through port 53 of public server 1.1.1.1, access port 53 of Intranet machine 10.1.50.101, and use DNS service. 创建账号以进行管理 Create account to see it in action. 已经有帐号了? Already have an account? 冒号分割,多个头部请填写多行 Colon separated, multiple lines please fill in P2P连接和私密代理模式需要 When P2P or Secret 协议支持全面,兼容几乎所有常用协议,例如tcp、udp、http(s)、socks5、p2p、http代理... Comprehensive protocol support, compatible with almost all commonly used protocols, such as tcp, udp, http(s), socks5, p2p, http proxy ... 全平台兼容(linux、windows、macos、群辉等),支持一键安装为系统服务 Full platform compatibility (linux, windows, macos, Qunhui, etc.), support installation as a system service simply. 控制全面,同时支持服务端和客户端控制 Comprehensive control, both client and server control are allowed. https集成,支持将后端代理和web服务转成https,同时支持多证书 Https integration, support to convert backend proxy and web services to https, and support multiple certificates. 操作简单,只需简单的配置即可在web ui上完成其余操作 Just simple configuration on web ui can complete most requirements. 展示信息全面,流量、系统信息、即时带宽、客户端版本等 Complete information display, such as traffic, system information, real-time bandwidth, client version, etc. 扩展功能强大,该有的都有了(缓存、压缩、加密、流量限制、带宽限制、端口复用等等) Powerful extension functions, everything is available (cache, compression, encryption, traffic limit, bandwidth limit, port reuse, etc.) 域名解析具备自定义header、404页面配置、host修改、站点保护、URL路由、泛解析等功能 Domain name resolution has functions such as custom headers, 404 page configuration, host modification, site protection, URL routing, and pan-resolution. 服务端支持多用户和用户注册功能 Multi-user and user registration support on server. 还没有有帐号? Do not have an account? 仅限Socks5、Web、HTTP转发代理 Only socks5 , web, HTTP forward proxy 注册到 NPS Register to NPS 例如 a.proxy.com such as a.proxy.com 例如 0.0.0.0 such as 0.0.0.0 例如 10.1.50.203:80 10.1.50.202:80 such as 10.1.50.203:80 10.1.50.202:80 例如 /tmp such as /tmp 例如 8024 such as 8024 例如 static such as static 一款轻量级、高性能、功能强大的内网穿透代理服务器 A lightweight, high-performance, powerful intranet reverse proxy server 分行填写多个目标可实现负载均衡 Line break if load balancing 代理到本地可以只填写端口号,只有TCP模式支持负载均衡 Can only fill in ports if it is local machine proxy, only tcp supports load balancing 留空表示不受限制 Empty means to be unrestricted 你确定你要删除它吗? Are you sure you want to delete it? 你确定你要启动它吗? Are you sure you want to start it? 你确定你要停止它吗? Are you sure you want to stop it? 添加错误,找不到客户端 Add error, the client can not be found 添加失败,主机已存在 Add fail, host has exist 添加成功 Add success 删除出错 Delete error 删除成功 Delete success 主机已存在 Host has exist 修改出错,客户端不存在 Modified error, the client is not exist 修改失败 Modified fail 修改成功 Modified success 保存成功 Save success 启动出错 Start error 启动成功 Start success 停止出错 Stop error 停止成功 Stop success 隧道数量超过限制 The number of tunnels exceeds the limit 无法打开端口,因为它可能已被占用或不再被允许。 The port cannot be opened because it may has been occupied or is no longer allowed. 唯一验证密钥重复,请重新设置 Unique verify key duplicate, please reset Web登陆用户名重复,请重新设置 Web login username duplicate, please reset 平均负载 1 Load avg 1 平均负载 5 Load avg 5 平均负载 15 Load avg 15 处理器 CPU 虚拟内存 Virtual memory 交换空间 Swap memory TCP UDP 入口 In 出口 Out 出口 Out 入口 In 流量统计 Traffic 入口 In 出口 Out 域名转发 HOST TCP 隧道 TCP UDP 隧道 UDP HTTP 代理 HTTP SOCKS 代理 SOCKS 5 私密代理 Secret P2P 连接 P2P 协议类型 Tcheme type 域名转发 HOST TCP 隧道 TCP UDP 隧道 UDP HTTP 代理 HTTP SOCKS 代理 SOCKS 5 私密代理 Secret P2P 连接 P2P ================================================ FILE: web/views/client/add.html ================================================

    {{if eq true .allow_flow_limit}}
    : M
    {{end}} {{if eq true .allow_rate_limit}}
    : KB/S
    {{end}} {{if eq true .allow_connection_num_limit}}
    {{end}} {{if eq true .allow_tunnel_num_limit}}
    {{end}}
    {{if eq true .allow_user_login}}
    {{end}}
    ================================================ FILE: web/views/client/edit.html ================================================

    {{if eq true .isAdmin}} {{if eq true .allow_flow_limit}}
    : M
    {{end}} {{if eq true .allow_rate_limit}}
    : KB/S
    {{end}} {{if eq true .allow_connection_num_limit}}
    {{end}} {{if eq true .allow_tunnel_num_limit}}
    {{end}} {{end}}
    {{if eq true .isAdmin}}
    {{end}} {{if eq true .allow_user_login}} {{if or (eq true .allow_user_change_username) (eq true .isAdmin)}}
    {{end}}
    {{end}}
    ================================================ FILE: web/views/client/list.html ================================================
    {{if eq true .isAdmin}}
    {{end}}
    ================================================ FILE: web/views/index/add.html ================================================

    :
    {{if eq true .allow_multi_ip}}
    {{end}}
    {{if eq true .allow_local_proxy}}
    {{end}}
    ================================================ FILE: web/views/index/edit.html ================================================

    :
    {{if eq true .allow_multi_ip}}
    {{end}}
    {{if eq true .allow_local_proxy}}
    {{end}}
    ================================================ FILE: web/views/index/hadd.html ================================================

    {{if eq false .https_just_proxy}}
    {{end}}
    {{if eq true .allow_local_proxy}}
    {{end}}
    ================================================ FILE: web/views/index/hedit.html ================================================

    {{if eq false .https_just_proxy}}
    {{end}}
    {{if eq true .allow_local_proxy}}
    {{end}}
    ================================================ FILE: web/views/index/help.html ================================================

    域名代理模式

    适用范围: 小程序开发、微信公众号开发、产品演示

    假设场景:

  • 有一个域名proxy.com,有一台公网机器ip为{{.ip}}
  • 两个内网开发站点127.0.0.1:81,127.0.0.1:82
  • 想通过a.proxy.com访问127.0.0.1:81,通过b.proxy.com访问127.0.0.1:82
  • 使用步骤:

    • 将*.proxy.com解析到公网服务器{{.ip}}
    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 点击该客户端的域名管理,添加两条规则规则:1、域名:a.proxy.com,内网目标:127.0.0.1:81,2、域名:b.proxy.com,内网目标:127.0.0.1:82
    • 内网客户端运行
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
    • 现在访问a.proxy.com,b.proxy.com即可成功

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,如需使用https请在配置文件中将https端口设置为443,和将对应的证书文件路径添加到配置文件中

    tcp隧道模式

    适用范围: ssh、远程桌面等tcp连接场景

    假设场景: 想通过访问公网服务器{{.ip}}的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接

    使用步骤:

    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 内网客户端运行
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
    • 在该客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),选择压缩方式,保存。
    • 访问公网服务器ip({{.ip}}),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:ssh -p 8001 root@{{.ip}}

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    udp隧道模式

    适用范围: 内网dns解析等udp连接场景

    假设场景: 内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为{{.ip}}

    使用步骤:

    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 内网客户端运行
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
    • 在该客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),选择压缩方式,保存。
    • 修改本机dns为{{.ip}},则相当于使用10.1.50.202作为dns服务器

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    socks5代理模式

    适用范围: 在外网环境下如同使用vpn一样访问内网设备或者资源

    假设场景: 想将公网服务器{{.ip}}的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果

    使用步骤:

    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 内网客户端运行
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
    • 在该客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),验证用户名和密码自行选择(建议先不填,部分客户端不支持,proxifer支持),选择压缩方式,保存。
    • 在外网环境的本机配置socks5代理,ip为公网服务器ip({{.ip}}),端口为填写的监听端口(8003),即可畅享内网了

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    http代理模式

    适用范围: 在外网环境下访问内网站点

    假设场景: 想将公网服务器{{.ip}}的8004端口作为http代理,访问内网网站

    使用步骤:

    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 内网客户端运行
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
    • 在该客户端隧道管理中添加一条http代理,填写监听的端口(8004),选择压缩方式,保存。
    • 在外网环境的本机配置http代理,ip为公网服务器ip({{.ip}}),端口为填写的监听端口(8004),即可访问了

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    单个客户端可以添加多条隧道或者域名解析

    ================================================ FILE: web/views/index/hlist.html ================================================ ================================================ FILE: web/views/index/index.html ================================================

    {{.p}}

    {{.data.clientCount}}

    {{/*今日*/}}

    {{.data.clientOnlineCount}}

    {{/*
    44%
    */}} {{/*新访客*/}}

    {{.data.tcpCount}}

    • {{.data.bridgeType}}
    • {{.data.httpProxyPort}}
    • {{.data.httpsProxyPort}}
    • {{.data.flowStoreInterval}}
    • {{.data.logLevel}}
    • {{.data.p2pPort}}
    • {{.data.serverIp}}
    • {{.data.version}}
    {{if eq true .system_info_display}} {{end}}
    ================================================ FILE: web/views/index/list.html ================================================ ================================================ FILE: web/views/login/index.html ================================================

    {{if eq true .register_allow}}

    {{end}}

    ================================================ FILE: web/views/login/register.html ================================================


    ================================================ FILE: web/views/public/error.html ================================================

    Error 404: Page not found

    The page you have requested is not found.

    Go Back

    ================================================ FILE: web/views/public/layout.html ================================================
    {{.LayoutContent}}
    {{/**/}}