├── .babelrc ├── .env.example ├── .env.travis ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── docker-publish.yml ├── .gitignore ├── .gitlab-ci.sh ├── .gitlab-ci.yml ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── app.yaml ├── app ├── Console │ ├── Commands │ │ ├── Base.php │ │ ├── CreateAdmin.php │ │ ├── DailyMail.php │ │ ├── GenLang.php │ │ ├── Job.php │ │ ├── Migration.php │ │ └── V2rayInit.php │ └── Kernel.php ├── Contracts │ ├── Codes │ │ ├── Auth.php │ │ └── Cfg.php │ ├── MailService.php │ ├── TokenInterface.php │ └── TokenStorageInterface.php ├── Controllers │ ├── Api │ │ ├── Admin │ │ │ ├── ConfigController.php │ │ │ ├── InfoController.php │ │ │ ├── InviteController.php │ │ │ ├── NodeController.php │ │ │ ├── TrafficLogController.php │ │ │ └── UserController.php │ │ ├── CodeController.php │ │ ├── ConfigController.php │ │ ├── NodeController.php │ │ ├── PasswordController.php │ │ ├── TokenController.php │ │ ├── UserController.php │ │ └── UserPayController.php │ ├── BaseController.php │ ├── HomeController.php │ ├── Mu │ │ ├── NodeController.php │ │ └── UserController.php │ ├── MuV2 │ │ ├── NodeController.php │ │ └── UserController.php │ └── ResController.php ├── Exceptions │ └── BaseException.php ├── Middleware │ ├── Admin.php │ ├── Api.php │ ├── ApiLimit.php │ ├── Cors.php │ ├── Helper.php │ └── Mu.php ├── Models │ ├── CheckInLog.php │ ├── ConfigModel.php │ ├── EmailVerify.php │ ├── InviteCode.php │ ├── Log.php │ ├── Model.php │ ├── Node.php │ ├── NodeInfoLog.php │ ├── NodeOnlineLog.php │ ├── PasswordReset.php │ ├── Role.php │ ├── Token.php │ ├── TrafficLog.php │ └── User.php ├── Providers │ ├── DbConfigServiceProvider.php │ ├── MailServiceProvider.php │ └── TokenStorageServiceProvider.php ├── Services │ ├── Analytic.php │ ├── Auth │ │ ├── EmailVerify.php │ │ ├── Token.php │ │ ├── TokenStorage.php │ │ └── User.php │ ├── Aws │ │ ├── Client.php │ │ └── Factory.php │ ├── Config │ │ ├── DbConfig.php │ │ ├── Factory.php │ │ ├── ModelConfigInterface.php │ │ └── MysqlConfig.php │ ├── Databases │ │ └── Migration.php │ ├── Factories │ │ ├── Pay.php │ │ └── Redis.php │ ├── Factory.php │ ├── FakeApp.php │ ├── Logger.php │ ├── Mail.php │ ├── Mail │ │ ├── Base.php │ │ ├── File.php │ │ ├── Mailgun.php │ │ ├── SendCloud.php │ │ ├── Ses.php │ │ └── Smtp.php │ ├── Migration.php │ ├── Password.php │ ├── Token │ │ ├── Base.php │ │ ├── DB.php │ │ ├── Dynamodb.php │ │ └── Token.php │ └── V2rayGenerator.php ├── Storage │ └── Dynamodb │ │ └── TrafficLog.php ├── Support │ └── helper.php └── Utils │ ├── Check.php │ ├── EmptyClass.php │ ├── Hash.php │ ├── Helper.php │ ├── Http.php │ ├── Ss.php │ └── Tools.php ├── bootstrap └── app.php ├── build.xml ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── database.php ├── paypal.php ├── v2ray.php ├── view.php └── vue-i18n-generator.php ├── databases ├── 2017_07_04_205431_create_sp_config_table.php ├── 2017_07_04_205432_create_sp_email_verify_table.php ├── 2017_07_04_205433_create_sp_log_table.php ├── 2017_07_04_205433_create_ss_checkin_log_table.php ├── 2017_07_04_205434_create_ss_invite_code_table.php ├── 2017_07_04_205435_create_ss_node_info_log_table.php ├── 2017_07_04_205435_create_ss_node_table.php ├── 2017_07_04_205436_create_ss_node_online_log_table.php ├── 2017_07_04_205436_create_ss_password_reset_table.php ├── 2017_07_04_205437_create_user_table.php ├── 2017_07_04_205438_create_user_traffic_log_table.php ├── 2017_11_19_205437_update_ss_node_table.php ├── 2017_11_19_205437_update_user_table.php └── 2018_02_25_015826_create_ss_chg_code_table.php ├── db-testing.sql ├── dev.md ├── docker-compose.yml ├── docker-entrypoint.sh ├── mix-manifest.json ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── assets │ ├── css │ │ ├── app.css │ │ ├── bootstrap.min.css │ │ ├── home.css │ │ ├── now-ui-kit.css │ │ ├── theme.css │ │ └── uikit.min.css │ ├── fonts │ │ ├── ProximaNova-Light-webfont.ttf │ │ ├── ProximaNova-Light-webfont.woff │ │ ├── ProximaNova-Reg-webfont.ttf │ │ ├── ProximaNova-Reg-webfont.woff │ │ ├── nucleo-outline.eot │ │ ├── nucleo-outline.ttf │ │ ├── nucleo-outline.woff │ │ └── nucleo-outline.woff2 │ ├── img │ │ ├── flag.png │ │ ├── header.jpg │ │ ├── header3.jpg │ │ ├── logo.png │ │ ├── snow.jpg │ │ ├── ss.png │ │ └── ssr.png │ └── js │ │ ├── admin.js │ │ ├── app.js │ │ ├── app.js.map │ │ ├── home.js │ │ ├── jquery.transit.min.js │ │ ├── left_bar.js │ │ ├── now-ui-kit.js │ │ ├── tether.min.js │ │ ├── uikit-icons.min.js │ │ └── uikit.min.js ├── downloads │ ├── ProxyBase.conf │ └── SSEncrypt.module ├── favicon.ico ├── index.php ├── js │ └── app.js └── robots.txt ├── resources ├── lang │ ├── en │ │ ├── admin-nav.php │ │ ├── admin.php │ │ ├── alert.php │ │ ├── auth.php │ │ ├── base.php │ │ ├── index.php │ │ ├── nav.php │ │ ├── ss.php │ │ ├── user-index.php │ │ └── user-nav.php │ └── zh_cn │ │ ├── admin-nav.php │ │ ├── admin.php │ │ ├── alert.php │ │ ├── auth.php │ │ ├── base.php │ │ ├── index.php │ │ ├── nav.php │ │ ├── ss.php │ │ ├── user-index.php │ │ └── user-nav.php └── views │ └── default │ ├── admin.html │ ├── dashboard.html │ ├── email │ ├── auth │ │ └── verify.tpl │ ├── news │ │ └── daily-traffic-report.tpl │ ├── password │ │ └── reset.html │ └── test.tpl │ ├── error │ └── 500.html │ └── index.html ├── routes └── web.php ├── run.sh ├── src ├── Admin.vue ├── App.vue ├── Home.vue ├── admin.js ├── adminRouter.js ├── app.js ├── code │ └── auth.js ├── components │ ├── AdminLeftbar.vue │ ├── Lang.vue │ └── Leftbar.vue ├── home.js ├── homeRouter.js ├── http │ ├── admin.js │ ├── base.js │ ├── rest.js │ └── user.js ├── lang │ ├── index.js │ └── vue-i18n-locales.generated.js ├── pages │ ├── Admin │ │ ├── Config.vue │ │ ├── Index.vue │ │ ├── Invite.vue │ │ ├── InviteAdd.vue │ │ ├── Mail.vue │ │ ├── Node.vue │ │ ├── NodeAdd.vue │ │ ├── TrafficLog.vue │ │ └── User.vue │ ├── Auth │ │ ├── Login.vue │ │ ├── Logout.vue │ │ └── Register.vue │ ├── Blank.vue │ ├── Code.vue │ ├── Dashboard.vue │ ├── Index.vue │ ├── Invite.vue │ ├── Node.vue │ ├── Password.vue │ ├── PasswordToken.vue │ ├── Profile.vue │ ├── Setting.vue │ └── TrafficLog.vue ├── res │ └── config.js ├── router.js ├── store │ ├── index.js │ ├── root.js │ └── types.js └── tools │ └── util.js ├── storage ├── framework │ └── views │ │ └── .gitignore ├── logs │ ├── .gitignore │ └── .gitkeep └── ss-panel │ └── .gitignore ├── tests ├── HomeTest.php ├── Models │ ├── CheckInLogTest.php │ ├── NodeTest.php │ └── TrafficLogTest.php ├── Mu │ ├── MuTest.php │ └── NodeTest.php ├── ResTest.php ├── Services │ ├── FactoryTest.php │ ├── LoggerTest.php │ └── RedisClientTest.php ├── TestCase.php └── Utils │ ├── CheckTest.php │ ├── CookieTest.php │ ├── HashTest.php │ └── ToolsTest.php ├── webpack.mix.js └── xcat /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }] 4 | ], 5 | "plugins": [ 6 | "transform-runtime" 7 | ] 8 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | // ss-panel v4 Config 2 | 3 | ENV = 'prod' 4 | DEBUG = 'false' 5 | 6 | // TimeZone 7 | APP_TIMEZONE = 'PRC' 8 | 9 | // Auth 10 | AUTH_SALT= 11 | AUTH_PASSWORD_ENCRYPTION_TYPE=bcrypt 12 | 13 | // Database 14 | DB_HOST=127.0.0.1 15 | DB_PORT = '3306' 16 | DB_DATABASE = 'sspanel' 17 | DB_USERNAME = 'sspanel' 18 | DB_PASSWORD = 'sspanel' 19 | 20 | // Redis 21 | REDIS_HOST = '127.0.0.1' 22 | REDIS_PORT = '6379' 23 | #REDIS_PASSWORD = '' 24 | 25 | -------------------------------------------------------------------------------- /.env.travis: -------------------------------------------------------------------------------- 1 | // ss-panel v4 Config 2 | 3 | ENV = 'prod' 4 | DEBUG = 'false' 5 | 6 | // TimeZone 7 | APP_TIMEZONE = 'PRC' 8 | 9 | // Auth 10 | AUTH_SALT= 11 | AUTH_PASSWORD_ENCRYPTION_TYPE=bcrypt 12 | 13 | 14 | // Database 15 | DB_HOST=127.0.0.1 16 | DB_PORT = '3306' 17 | DB_DATABASE = 'sspanel' 18 | DB_USERNAME = 'root' 19 | DB_PASSWORD = '' 20 | 21 | // Redis 22 | REDIS_HOST = '127.0.0.1' 23 | REDIS_PORT = '6379' 24 | #REDIS_PASSWORD = '' 25 | 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.tpl linguist-language=php -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/.github/CONTRIBUTING.md -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ** 开issue前请确认: ** 2 | * 安装问题等错误请开启debug模式,并附上debug log 3 | * 安全问题请发邮箱 sspanel#orx.me 4 | * 未填写运行环境的issue将会被直接关闭 5 | * 一个issue请只题一个问题,多个问题分多个issue开. 6 | 7 | 8 | ## 运行环境 9 | 10 | * PHP Version /PHP版本: 11 | * ss-panel version/ ss-panel版本: 12 | * 其他环境信息(Nginx,系统等): 13 | 14 | ### Description of the problem / 问题描述 15 | 在这里输入描述信息. 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/.github/PULL_REQUEST_TEMPLATE.md -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | schedule: 10 | - cron: '39 16 * * *' 11 | push: 12 | branches: [ "master" ] 13 | # Publish semver tags as releases. 14 | tags: [ 'v*.*.*' ] 15 | pull_request: 16 | branches: [ "master" ] 17 | 18 | env: 19 | # Use docker.io for Docker Hub if empty 20 | REGISTRY: ghcr.io 21 | # github.repository as / 22 | IMAGE_NAME: ${{ github.repository }} 23 | 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: read 31 | packages: write 32 | # This is used to complete the identity challenge 33 | # with sigstore/fulcio when running outside of PRs. 34 | id-token: write 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v3 39 | 40 | # Install the cosign tool except on PR 41 | # https://github.com/sigstore/cosign-installer 42 | - name: Install cosign 43 | if: github.event_name != 'pull_request' 44 | uses: sigstore/cosign-installer@main 45 | with: 46 | cosign-release: 'v1.13.1' 47 | 48 | - name: Set up QEMU 49 | uses: docker/setup-qemu-action@v2 50 | 51 | - name: Set up Docker Buildx 52 | id: buildx 53 | uses: docker/setup-buildx-action@v2 54 | 55 | - name: Available platforms 56 | run: echo ${{ steps.buildx.outputs.platforms }} 57 | 58 | # Login against a Docker registry except on PR 59 | # https://github.com/docker/login-action 60 | - name: Log into registry ${{ env.REGISTRY }} 61 | if: github.event_name != 'pull_request' 62 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 63 | with: 64 | registry: ${{ env.REGISTRY }} 65 | username: ${{ github.actor }} 66 | password: ${{ secrets.GITHUB_TOKEN }} 67 | 68 | # Extract metadata (tags, labels) for Docker 69 | # https://github.com/docker/metadata-action 70 | - name: Extract Docker metadata 71 | id: meta 72 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 73 | with: 74 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 75 | 76 | # Build and push Docker image with Buildx (don't push on PR) 77 | # https://github.com/docker/build-push-action 78 | - name: Build and push Docker image 79 | id: build-and-push 80 | uses: docker/build-push-action@v3 81 | with: 82 | platforms: linux/amd64,linux/arm64 83 | context: . 84 | push: ${{ github.event_name != 'pull_request' }} 85 | tags: ${{ steps.meta.outputs.tags }} 86 | labels: ${{ steps.meta.outputs.labels }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .env 3 | vendor/ 4 | composer.phar 5 | build/ 6 | composer 7 | node_modules/ 8 | npm-debug.log 9 | -------------------------------------------------------------------------------- /.gitlab-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install dependencies only for Docker. 4 | [[ ! -e /.dockerinit ]] && exit 0 5 | set -xe 6 | 7 | # Update packages and install composer and PHP dependencies. 8 | apt-get update -yqq 9 | apt-get install git libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq 10 | 11 | # Compile PHP, include these extensions. 12 | docker-php-ext-install mbstring mcrypt pdo_mysql curl json intl gd xml zip bz2 opcache 13 | 14 | # Install Composer and project dependencies. 15 | curl -sS https://getcomposer.org/installer | php 16 | php composer.phar install 17 | 18 | # Copy over testing configuration. 19 | # cp .env.testing .env 20 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # Official docker image. 3 | image: docker:latest 4 | services: 5 | - docker:dind 6 | stages: 7 | - build 8 | - build_front 9 | 10 | build: 11 | stage: build 12 | image: gitlab/dind 13 | script: 14 | - export IMAGE_TAG=$(echo -en $CI_BUILD_REF_NAME | tr -c '[:alnum:]_.-' '-') 15 | - docker login -u "$CI_BUILD_USER" -p "$CI_BUILD_TOKEN" $CI_REGISTRY 16 | - docker build --pull -t "$CI_REGISTRY_IMAGE:$IMAGE_TAG" . 17 | - docker push "$CI_REGISTRY_IMAGE:$IMAGE_TAG" 18 | 19 | build_front: 20 | stage: build_front 21 | image: node:latest 22 | script: 23 | - npm install 24 | - npm run prod -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - 7.1 8 | - hhvm 9 | - nightly 10 | 11 | matrix: 12 | allow_failures: 13 | - php: 5.5 14 | - php: 5.6 15 | - php: 7.0 16 | - php: hhvm 17 | - php: nightly 18 | 19 | 20 | services: 21 | - redis-server 22 | - mysql 23 | 24 | install: 25 | - travis_retry composer install --no-interaction --prefer-source 26 | 27 | before_script: 28 | - "mysql -e 'create database `sspanel`;'" 29 | - mysql -u root sspanel < db-testing.sql 30 | - cp .env.travis .env 31 | - if [[ "$TRAVIS_PHP_VERSION" == '7.0' ]]; then composer require satooshi/php-coveralls:dev-master squizlabs/php_codesniffer:2.* -n ; fi 32 | - if [[ "$TRAVIS_PHP_VERSION" != '7.0' ]]; then composer install -n ; fi 33 | 34 | 35 | script: 36 | - if [ "$TRAVIS_PHP_VERSION" != "5.5.9" ] && [ "$TRAVIS_PHP_VERSION" != "5.5" ] && [ "$TRAVIS_PHP_VERSION" != "5.6" ]; then vendor/bin/phpunit; fi 37 | - if [ "$TRAVIS_PHP_VERSION" == "5.5.9" ] || [ "$TRAVIS_PHP_VERSION" == "5.5" ] || [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; fi 38 | 39 | after_script: 40 | - if [ "$TRAVIS_PHP_VERSION" == "5.5.9" ] || [ "$TRAVIS_PHP_VERSION" == "5.5" ] || [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi 41 | - if [ "$TRAVIS_PHP_VERSION" == "5.5.9" ] || [ "$TRAVIS_PHP_VERSION" == "5.5" ] || [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml; fi 42 | 43 | after_success: 44 | - travis_retry php vendor/bin/coveralls -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM orvice/apache-base 2 | MAINTAINER orvice 3 | 4 | ENV SSPANEL_VERSION 4.0.0 5 | WORKDIR /var/www/html 6 | 7 | # Install sspanel 8 | COPY . /var/www/html 9 | 10 | 11 | # Install Composer 12 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 13 | 14 | # Install dependencies with Composer. 15 | RUN cd /var/www/html && composer install --no-scripts 16 | 17 | # Entrypoint 18 | COPY docker-entrypoint.sh /entrypoint.sh 19 | 20 | RUN chmod -R 777 storage 21 | 22 | EXPOSE 80 23 | 24 | ENTRYPOINT ["/entrypoint.sh"] 25 | CMD ["apache2-foreground"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 orvice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ss-panel 2 | 3 | Let's talk about cat. A simple panel for shadowsocks. 4 | 5 | Based on [LightFish](https://github.com/Pongtan/LightFish) and [Vue.js](https://vuejs.org). 6 | 7 | [Demo](https://demo.sspanel.xyz/)|[API Document](https://doc.sspanel.xyz/)| [安装文档](https://sspanel.xyz/docs) 8 | 9 | [![Build Status](https://travis-ci.org/orvice/ss-panel.svg?branch=master)](https://travis-ci.org/orvice/ss-panel) [![Coverage Status](https://coveralls.io/repos/github/orvice/ss-panel/badge.svg?branch=master)](https://coveralls.io/github/orvice/ss-panel?branch=master) [![Join the chat at https://gitter.im/orvice/ss-panel](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/orvice/ss-panel?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![](https://images.microbadger.com/badges/image/orvice/ss-panel.svg)](https://microbadger.com/images/orvice/ss-panel "Get your own image badge on microbadger.com") 10 | 11 | ## About 12 | 13 | Please visit [releases pages](https://github.com/orvice/ss-panel/releases) to download source. 14 | 15 | ## Supported Server 16 | 17 | * [shadowsocks manyuser](https://github.com/mengskysama/shadowsocks/tree/manyuser) 18 | * [shadowsocksrss manyuser](https://github.com/breakwa11/shadowsocks/tree/manyuser) 19 | * [shadowsocks-go mu](https://github.com/orvice/shadowsocks-go) 20 | * [shadowsocks-go mu ng](https://github.com/catpie/ss-go-mu) 21 | 22 | ## Install with Docker 23 | 24 | ### Get it 25 | ```bash 26 | docker pull orvice/ss-panel 27 | ``` 28 | 29 | ### Install with Docker-compose 30 | 31 | [Install docker-compose](https://docs.docker.com/compose/install/) 32 | 33 | ```bash 34 | wget https://raw.githubusercontent.com/orvice/ss-panel/master/docker-compose.yml 35 | docker-compose up -d 36 | ``` 37 | 38 | Visit `ip:8080` 39 | 40 | 41 | 42 | You can also install manual with Nginx or other web server,[check wiki](https://github.com/orvice/ss-panel/wiki/Install-with-Nginx). 43 | 44 | ## ToDo 45 | * Full unit test for api 46 | * Unit test for Front 47 | 48 | ## Thanks to 49 | * [LightFish](https://github.com/Pongtan/LightFish) 50 | * [Vue.js](https://vuejs.org) 51 | * [UIKit](https://getuikit.com) 52 | * [UIAdmin](https://github.com/ConsoleTVs/UIAdmin) 53 | * [Now UI Kit](https://github.com/creativetimofficial/now-ui-kit) -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | application: app 2 | version: 1 3 | runtime: php55 4 | api_version: 1 5 | 6 | handlers: 7 | 8 | - url: /assets/(.*\.(htm$|html$|css$|js$|png$)) 9 | static_files: public/assets/\1 10 | upload: public/assets/(.*\.(htm$|html$|css$|js$|png$)) 11 | application_readable: true 12 | 13 | - url: /.* 14 | script: public/index.php 15 | 16 | env_variables: 17 | debug: true 18 | timeZone: PRC 19 | 20 | VIEW_CACHE_PATH: gs://app.appspot.com/achievotmp/compiled/ 21 | 22 | -------------------------------------------------------------------------------- /app/Console/Commands/Base.php: -------------------------------------------------------------------------------- 1 | setName('createAdmin'); 20 | $this->setDescription('Create a admin'); 21 | $this->addArgument('email', InputArgument::REQUIRED); 22 | $this->addArgument('pass', InputArgument::REQUIRED); 23 | } 24 | 25 | /** 26 | * @param InputInterface $input 27 | * @param OutputInterface $output 28 | */ 29 | protected function execute(InputInterface $input, OutputInterface $output) 30 | { 31 | $email = $input->getArgument('email'); 32 | $pass = $input->getArgument('pass'); 33 | $output->writeln(sprintf("create admin with email: %s",$email)); 34 | 35 | $newUser = false; 36 | $u = function () use($email,$newUser){ 37 | $u = User::where('email',$email)->first(); 38 | if($u != null){ 39 | return $u; 40 | } 41 | $u = new User(); 42 | $u->email = $email; 43 | return $u; 44 | }; 45 | $user = $u(); 46 | if(!$user->port){ 47 | $newUser = true; 48 | } 49 | $this->createUser($user,$pass,$newUser); 50 | } 51 | 52 | /** 53 | * @param User $user 54 | * @param $passwd 55 | * @param $isNewUser 56 | * @return bool 57 | */ 58 | protected function createUser(User $user,$passwd,$isNewUser){ 59 | $user->user_name = "admin"; 60 | $user->is_admin = 1; 61 | // $user->email = $email; 62 | $user->pass = Hash::passwordHash($passwd); 63 | if($isNewUser){ 64 | $user->passwd = Tools::genRandomChar(6); 65 | $user->port = Tools::getLastPort() + 1; 66 | $user->t = 0; 67 | $user->u = 0; 68 | $user->d = 0; 69 | $user->transfer_enable = Tools::toGB(db_config(self::DefaultTraffic, 1)); 70 | $user->invite_num = db_config(self::DefaultInviteNum, 10); 71 | $user->reg_ip = '127.0.0.1'; 72 | $user->ref_by = 0; 73 | $user->v2ray_uuid = Tools::genUUID(); 74 | $user->v2ray_alter_id = config('v2ray.alter_id'); 75 | $user->v2ray_level = config('v2ray.level'); 76 | } 77 | return $user->save(); 78 | } 79 | } -------------------------------------------------------------------------------- /app/Console/Commands/DailyMail.php: -------------------------------------------------------------------------------- 1 | ifUsedIn(24)) { 16 | echo 'Send daily mail to user: '.$user->user_name.' '.$user->email."\n"; 17 | $subject = Config::get('appName').'-每日流量报告'; 18 | $to = $user->email; 19 | try { 20 | Mail::send($to, $subject, 'news/daily-traffic-report.tpl', [ 21 | 'user' => $user, 22 | ], [ 23 | ]); 24 | } catch (Exception $e) { 25 | echo $e->getMessage(); 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Console/Commands/GenLang.php: -------------------------------------------------------------------------------- 1 | generateFromPath($root); 17 | $jsFile = base_path() . config('vue-i18n-generator.jsFile'); 18 | file_put_contents($jsFile, $data); 19 | echo "Written to " . $jsFile . PHP_EOL; 20 | } 21 | 22 | protected function configure() 23 | { 24 | $this->setName('genVueLang'); 25 | $this->setDescription('Generate Vue Lang js file'); 26 | } 27 | 28 | protected function execute(InputInterface $input, OutputInterface $output) 29 | { 30 | $this->handle(); 31 | } 32 | } -------------------------------------------------------------------------------- /app/Console/Commands/Job.php: -------------------------------------------------------------------------------- 1 | setName('migrate'); 17 | $this->setDescription('Run Migrations'); 18 | } 19 | 20 | protected function execute(InputInterface $input, OutputInterface $output) 21 | { 22 | $m = new MigrationService(); 23 | if( !$m->getMigrator()->repositoryExists()){ 24 | $output->writeln("create migrations table"); 25 | $m->getMigrator()->getRepository()->createRepository(); 26 | } 27 | $m->getMigrator()->run(base_path('databases')); 28 | } 29 | 30 | 31 | } -------------------------------------------------------------------------------- /app/Console/Commands/V2rayInit.php: -------------------------------------------------------------------------------- 1 | setName('v2ray:init'); 16 | $this->setDescription('Init v2ray Users'); 17 | } 18 | 19 | protected function execute(InputInterface $input, OutputInterface $output) 20 | { 21 | $users = User::all(); 22 | foreach ($users as $user){ 23 | if(strlen($user->v2ray_uuid) == 0){ 24 | $user->v2ray_uuid = Tools::genUUID(); 25 | $user->save(); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | app = new Application('ss-panel', get_version()); 23 | $this->registerCommands(); 24 | } 25 | 26 | public function registerCommands() 27 | { 28 | foreach ($this->commands as $class) { 29 | $this->app->add(new $class); 30 | } 31 | } 32 | 33 | public function run() 34 | { 35 | $this->app->run(); 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /app/Contracts/Codes/Auth.php: -------------------------------------------------------------------------------- 1 | cfgs as $cfg) { 44 | $data[$cfg] = db_config($cfg); 45 | } 46 | return $this->echoJsonWithData($response, $data); 47 | } 48 | 49 | public function update(Request $request, Response $response, $args) 50 | { 51 | $cfg = $this->getCfg(); 52 | $input = file_get_contents("php://input"); 53 | $arr = json_decode($input, true); 54 | foreach ($arr as $k => $v) { 55 | if(!$v){ 56 | continue; 57 | } 58 | $cfg->set($k, $v); 59 | } 60 | $cfg->flushAll(); 61 | return $this->echoJsonWithData($response); 62 | } 63 | 64 | /** 65 | * @return DbConfig 66 | */ 67 | public function getCfg() 68 | { 69 | return app()->make(DbConfig::class); 70 | } 71 | } -------------------------------------------------------------------------------- /app/Controllers/Api/Admin/InfoController.php: -------------------------------------------------------------------------------- 1 | echoJsonWithData($res, [ 18 | 'usersTotal' => $a->getTotalUser(), 19 | 'nodesTotal' => $a->getTotalNode(), 20 | 'trafficTotal' => $a->getTrafficTotal(), 21 | ]); 22 | } 23 | } -------------------------------------------------------------------------------- /app/Controllers/Api/Admin/InviteController.php: -------------------------------------------------------------------------------- 1 | getQueryParams()['page'])) { 19 | $pageNum = $request->getQueryParams()['page']; 20 | } 21 | $traffic = InviteCode::paginate(15, [ 22 | '*', 23 | ], 'page', $pageNum); 24 | $traffic->setPath('/api/admin/invites'); 25 | //return $this->echoJsonWithData($res,$traffic); 26 | return $this->echoJson($response, $traffic); 27 | } 28 | 29 | public function store(Request $request, Response $response, $args) 30 | { 31 | $n = $request->getParam('num'); 32 | $prefix = $request->getParam('prefix'); 33 | $uid = $request->getParam('uid'); 34 | if ($n < 1) { 35 | return $this->echoJsonError($response, []); 36 | } 37 | for ($i = 0; $i < $n; $i++) { 38 | $char = Tools::genRandomChar(32); 39 | $code = new InviteCode(); 40 | $code->code = $prefix . $char; 41 | $code->user_id = $uid; 42 | $code->save(); 43 | } 44 | return $this->echoJsonWithData($response, []); 45 | } 46 | 47 | public function delete(Request $req, Response $res, $args) 48 | { 49 | $node = InviteCode::find($args['id']); 50 | $node->delete(); 51 | return $this->echoJsonWithData($res, []); 52 | } 53 | } -------------------------------------------------------------------------------- /app/Controllers/Api/Admin/NodeController.php: -------------------------------------------------------------------------------- 1 | getQueryParams()['page'])) { 19 | $pageNum = $req->getQueryParams()['page']; 20 | } 21 | $traffic = Node::paginate(15, [ 22 | '*', 23 | ], 'page', $pageNum); 24 | $traffic->setPath('/api/admin/nodes'); 25 | //return $this->echoJsonWithData($res,$traffic); 26 | return $this->echoJson($res, $traffic); 27 | } 28 | 29 | private function saveModel(Response $response, Node $node, $arr) 30 | { 31 | foreach ($arr as $k => $v) { 32 | $node->$k = $v; 33 | } 34 | $node->save(); 35 | return $this->echoJsonWithData($response, $node); 36 | } 37 | 38 | public function store(Request $req, Response $res, $args) 39 | { 40 | $input = file_get_contents("php://input"); 41 | $arr = json_decode($input, true); 42 | return $this->saveModel($res, new Node(), $arr); 43 | } 44 | 45 | public function update(Request $req, Response $res, $args) 46 | { 47 | $input = file_get_contents("php://input"); 48 | $arr = json_decode($input, true); 49 | return $this->saveModel($res, Node::find($args['id']), $arr); 50 | } 51 | 52 | public function delete(Request $req, Response $res, $args) 53 | { 54 | $node = Node::find($args['id']); 55 | $node->delete(); 56 | return $this->echoJsonWithData($res, []); 57 | } 58 | 59 | public function trafficLogs(Request $req, Response $res, $args) 60 | { 61 | $pageNum = 1; 62 | if (isset($req->getQueryParams()['page'])) { 63 | $pageNum = $req->getQueryParams()['page']; 64 | } 65 | $traffic = TrafficLog::where('user_traffic_log.node_id', $args['id']) 66 | ->join('ss_node', 'user_traffic_log.node_id', '=', 'ss_node.id') 67 | ->orderBy('user_traffic_log.id', 'desc') 68 | ->paginate(15, [ 69 | 'user_traffic_log.*', 70 | 'ss_node.name as name' 71 | ], 'page', $pageNum); 72 | $traffic->setPath('/api/admin/trafficLogs'); 73 | //return $this->echoJsonWithData($res,$traffic); 74 | return $this->echoJson($res, $traffic); 75 | } 76 | } -------------------------------------------------------------------------------- /app/Controllers/Api/Admin/TrafficLogController.php: -------------------------------------------------------------------------------- 1 | getQueryParams()['page'])) { 17 | $pageNum = $req->getQueryParams()['page']; 18 | } 19 | $traffic = TrafficLog::where('user_traffic_log.id', '>','0') 20 | ->join('ss_node', 'user_traffic_log.node_id', '=', 'ss_node.id') 21 | ->orderBy('user_traffic_log.id', 'desc') 22 | ->paginate(15, [ 23 | 'user_traffic_log.*', 24 | 'ss_node.name as name' 25 | ], 'page', $pageNum); 26 | $traffic->setPath('/api/admin/trafficLogs'); 27 | //return $this->echoJsonWithData($res,$traffic); 28 | return $this->echoJson($res, $traffic); 29 | } 30 | } -------------------------------------------------------------------------------- /app/Controllers/Api/Admin/UserController.php: -------------------------------------------------------------------------------- 1 | getQueryParams()['page'])) { 18 | $pageNum = $req->getQueryParams()['page']; 19 | } 20 | $traffic = User::paginate(15, [ 21 | '*', 22 | ], 'page', $pageNum); 23 | $traffic->setPath('/api/admin/users'); 24 | //return $this->echoJsonWithData($res,$traffic); 25 | return $this->echoJson($res, $traffic); 26 | } 27 | } -------------------------------------------------------------------------------- /app/Controllers/Api/CodeController.php: -------------------------------------------------------------------------------- 1 | take(10)->get(); 16 | return $this->echoJsonWithData($response, $codes); 17 | } 18 | } -------------------------------------------------------------------------------- /app/Controllers/Api/ConfigController.php: -------------------------------------------------------------------------------- 1 | db_config(self::AppName,'ss-panel4'), 46 | 'index_message' => db_config(self::HomeMessage,'Like a butterfly...'), 47 | 'analyticsId' => db_config(self::GoogleAnalyticsId,''), 48 | 'version' => get_version(), 49 | ]; 50 | return $this->echoJsonWithData($response, $data); 51 | } 52 | 53 | public function ss(Request $request, Response $response, $args) 54 | { 55 | return $this->echoJsonWithData($response, [ 56 | "methods" => SsUtil::getCipher(), 57 | "protocol" => SsUtil::getProtocol(), 58 | "obfs" => SsUtil::getObfs(), 59 | ]); 60 | } 61 | } -------------------------------------------------------------------------------- /app/Controllers/Api/NodeController.php: -------------------------------------------------------------------------------- 1 | echoJson($res, [ 18 | 'data' => Node::all(), 19 | ]); 20 | } 21 | } -------------------------------------------------------------------------------- /app/Controllers/Api/PasswordController.php: -------------------------------------------------------------------------------- 1 | getParam('email'); 23 | // send email 24 | $user = User::where('email', $email)->first(); 25 | if ($user == null) { 26 | return $this->echoJson($res, [ 27 | 'error_code' => self::EmailNotExist, 28 | ], 400); 29 | } 30 | $p = new Password(); 31 | $p->sendResetEmail($email); 32 | return $this->echoJsonWithData($res, []); 33 | } 34 | 35 | public function show(Request $request, Response $response, $args) 36 | { 37 | 38 | } 39 | 40 | public function verify(Request $request, Response $response, $args) 41 | { 42 | $tokenStr = $args['token']; 43 | $password = $request->getParam('password'); 44 | // check token 45 | $token = PasswordReset::where('token', $tokenStr)->first(); 46 | if ($token == null || $token->expire_time < time()) { 47 | return $this->echoJson($response, [ 48 | 'error_code' => self::LinkExpired, 49 | ], 400); 50 | } 51 | $user = User::where('email', $token->email)->first(); 52 | if ($user == null) { 53 | return $this->echoJson($response, [ 54 | 'error_code' => self::LinkExpired, 55 | ], 400); 56 | } 57 | // reset password 58 | $hashPassword = Hash::passwordHash($password); 59 | $user->pass = $hashPassword; 60 | $user->save(); 61 | $token->delete(); 62 | return $this->echoJsonWithData($response, []); 63 | } 64 | } -------------------------------------------------------------------------------- /app/Controllers/Api/UserPayController.php: -------------------------------------------------------------------------------- 1 | getParam('fee'); 27 | 28 | $payer = new Payer(); 29 | $payer->setPaymentMethod("paypal"); 30 | 31 | $item = new Item(); 32 | $item->setName(db_config('appName') . " Payment") 33 | ->setCurrency(config('paypal.currency')) 34 | ->setQuantity(1) 35 | ->setPrice($fee); 36 | $itemList = new ItemList(); 37 | $itemList->setItems([$item]); 38 | 39 | $amount = new Amount(); 40 | $amount->setCurrency(config('paypal.currency')) 41 | ->setTotal($fee); 42 | 43 | $transaction = new Transaction(); 44 | $transaction->setAmount($amount) 45 | ->setItemList($itemList) 46 | ->setDescription("Payment description") 47 | ->setInvoiceNumber(uniqid()); 48 | 49 | $baseUrl = db_config('appUri'); 50 | $redirectUrls = new RedirectUrls(); 51 | $redirectUrls->setReturnUrl("$baseUrl/pay/paypal") 52 | ->setCancelUrl("$baseUrl/pay"); 53 | 54 | $payment = new Payment(); 55 | $payment->setIntent("order") 56 | ->setPayer($payer) 57 | ->setRedirectUrls($redirectUrls) 58 | ->setTransactions(array($transaction)); 59 | 60 | try { 61 | $payment->create(Pay::newPaypalClient()); 62 | } catch (\Exception $ex) { 63 | return $this->echoJson($res, [], 500); 64 | } 65 | 66 | $approvalUrl = $payment->getApprovalLink(); 67 | 68 | return $this->echoJson($res, [ 69 | 'url' => $approvalUrl, 70 | ]); 71 | } 72 | 73 | public function alipay(Request $req, Response $res, $args) 74 | { 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /app/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | logger = Factory::getLogger(); 32 | parent::__construct($ci); 33 | } 34 | 35 | /** 36 | * @return User 37 | */ 38 | public function getUser() 39 | { 40 | return user(); 41 | } 42 | 43 | /** 44 | * @param Response $response 45 | * @param $data 46 | * @param int $statusCode 47 | * @return mixed 48 | */ 49 | public function echoJsonWithData(Response $response, $data = [], $statusCode = 200) 50 | { 51 | return $this->echoJson($response, [ 52 | 'data' => $data, 53 | ], $statusCode); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Controllers/Mu/NodeController.php: -------------------------------------------------------------------------------- 1 | getParam('count'); 15 | $log = new NodeOnlineLog(); 16 | $log->node_id = $node_id; 17 | $log->online_user = $count; 18 | $log->log_time = time(); 19 | if (!$log->save()) { 20 | $res = [ 21 | 'ret' => 0, 22 | 'msg' => 'update failed', 23 | ]; 24 | 25 | return $this->echoJson($response, $res); 26 | } 27 | $res = [ 28 | 'ret' => 1, 29 | 'msg' => 'ok', 30 | ]; 31 | 32 | return $this->echoJson($response, $res); 33 | } 34 | 35 | public function info($request, $response, $args) 36 | { 37 | $node_id = $args['id']; 38 | $load = $request->getParam('load'); 39 | $uptime = $request->getParam('uptime'); 40 | 41 | $log = new NodeInfoLog(); 42 | $log->node_id = $node_id; 43 | $log->load = $load; 44 | $log->uptime = $uptime; 45 | $log->log_time = time(); 46 | if (!$log->save()) { 47 | $res = [ 48 | 'ret' => 0, 49 | 'msg' => 'update failed', 50 | ]; 51 | 52 | return $this->echoJson($response, $res); 53 | } 54 | $res = [ 55 | 'ret' => 1, 56 | 'msg' => 'ok', 57 | ]; 58 | 59 | return $this->echoJson($response, $res); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Controllers/Mu/UserController.php: -------------------------------------------------------------------------------- 1 | 1, 22 | 'msg' => 'ok', 23 | 'data' => $users, 24 | ]; 25 | 26 | return $this->echoJson($response, $res); 27 | } 28 | 29 | // Update Traffic 30 | public function addTraffic($request, $response, $args) 31 | { 32 | $id = $args['id']; 33 | $u = $request->getParam('u'); 34 | $d = $request->getParam('d'); 35 | $nodeId = $request->getParam('node_id'); 36 | $node = Node::find($nodeId); 37 | $rate = $node->traffic_rate; 38 | $user = User::find($id); 39 | 40 | $user->t = time(); 41 | $user->u = $user->u + ($u * $rate); 42 | $user->d = $user->d + ($d * $rate); 43 | if (!$user->save()) { 44 | $res = [ 45 | 'ret' => 0, 46 | 'msg' => 'update failed', 47 | ]; 48 | 49 | return $this->echoJson($response, $res); 50 | } 51 | // log 52 | $totalTraffic = Tools::flowAutoShow(($u + $d) * $rate); 53 | $traffic = new TrafficLog(); 54 | $traffic->user_id = $id; 55 | $traffic->u = $u; 56 | $traffic->d = $d; 57 | $traffic->node_id = $nodeId; 58 | $traffic->rate = $rate; 59 | $traffic->traffic = $totalTraffic; 60 | $traffic->log_time = time(); 61 | $traffic->save(); 62 | 63 | $res = [ 64 | 'ret' => 1, 65 | 'msg' => 'ok', 66 | ]; 67 | if (Config::get('log_traffic_dynamodb')) { 68 | try { 69 | $client = new DynamoTrafficLog(); 70 | $id = $client->store($u, $d, $nodeId, $id, $totalTraffic, $rate); 71 | $res['id'] = $id; 72 | } catch (\Exception $e) { 73 | $res['msg'] = $e->getMessage(); 74 | Logger::error($e->getMessage()); 75 | } 76 | } 77 | 78 | return $this->echoJson($response, $res); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/Controllers/MuV2/UserController.php: -------------------------------------------------------------------------------- 1 | $users, 42 | ]; 43 | 44 | return $this->echoJson($response, $res); 45 | } 46 | 47 | // Update Traffic 48 | public function addTraffic($request, $response, $args) 49 | { 50 | // $data = json_decode($request->getParsedBody(),true); 51 | $id = $args['id']; 52 | $u = $request->getParam('u'); 53 | $d = $request->getParam('d'); 54 | $nodeId = $request->getParam('node_id'); 55 | $node = Node::find($nodeId); 56 | $rate = $node->traffic_rate; 57 | $user = User::find($id); 58 | 59 | $user->t = time(); 60 | $user->u = $user->u + ($u * $rate); 61 | $user->d = $user->d + ($d * $rate); 62 | if (!$user->save()) { 63 | $res = [ 64 | 'msg' => 'update failed', 65 | ]; 66 | 67 | return $this->echoJson($response, $res, 400); 68 | } 69 | // log 70 | $totalTraffic = Tools::flowAutoShow(($u + $d) * $rate); 71 | $traffic = new TrafficLog(); 72 | $traffic->user_id = $id; 73 | $traffic->u = $u; 74 | $traffic->d = $d; 75 | $traffic->node_id = $nodeId; 76 | $traffic->rate = $rate; 77 | $traffic->traffic = $totalTraffic; 78 | $traffic->log_time = time(); 79 | $traffic->save(); 80 | 81 | $res = [ 82 | 'ret' => 1, 83 | 'msg' => 'ok', 84 | ]; 85 | 86 | // @todo 87 | $saveToDynamo = false; 88 | if ($saveToDynamo) { 89 | try { 90 | $client = new DynamoTrafficLog(); 91 | $id = $client->store($u, $d, $nodeId, $id, $totalTraffic, $rate); 92 | $res['id'] = $id; 93 | } catch (\Exception $e) { 94 | } 95 | } 96 | 97 | return $this->echoJson($response, $res); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/Controllers/ResController.php: -------------------------------------------------------------------------------- 1 | build(); 14 | //$builder->getPhrase(); 15 | $newResponse = $response->withHeader('Content-type', ' image/jpeg'); //->getBody()->write($builder->output()); 16 | $newResponse->getBody()->write($builder->output()); 17 | 18 | return $newResponse; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Exceptions/BaseException.php: -------------------------------------------------------------------------------- 1 | getUserFromReq($request); 15 | if (!$user || !$user->isLogin) { 16 | return $this->denied($response); 17 | } 18 | if (!$user->isAdmin()) { 19 | return $this->denied($response); 20 | } 21 | $response = $next($request, $response); 22 | return $response; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Middleware/Api.php: -------------------------------------------------------------------------------- 1 | getUserFromReq($request); 15 | if (!$user || !$user->isLogin) { 16 | return $this->denied($response); 17 | } 18 | if (!$user->isAdmin()) { 19 | $id = $request->getAttribute('routeInfo')[2]['id']; 20 | if ($id != $user->id) { 21 | return $this->denied($response); 22 | } 23 | } 24 | $response = $next($request, $response); 25 | 26 | return $response; 27 | } 28 | 29 | /** 30 | * @param Response $response 31 | * @return Response 32 | */ 33 | public function denied(Response $response) 34 | { 35 | $newResponse = $response->withJson([ 36 | "error_code" => 401, 37 | "message" => "Access Denied", 38 | ], 401); 39 | return $newResponse; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Middleware/ApiLimit.php: -------------------------------------------------------------------------------- 1 | init(); 16 | } 17 | 18 | public function __invoke(Request $request, Response $response, $next) 19 | { 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /app/Middleware/Cors.php: -------------------------------------------------------------------------------- 1 | getHeaderLine('origin'); 18 | $origin = rtrim($origin,'/'); 19 | $response = $next($request, $response); 20 | return $response 21 | ->withHeader('Access-Control-Allow-Origin', $this->getACAL($origin)) 22 | ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') 23 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 24 | } 25 | 26 | /** 27 | * @param $origin 28 | * @return string 29 | */ 30 | public function getACAL($origin) 31 | { 32 | $host = Http::getHostFromOrigin($origin); 33 | if (in_array($host, $this->getAllowDomainsList())) { 34 | return $origin; 35 | } 36 | return ''; 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getAllowDomainsList() 43 | { 44 | $hosts = db_config(self::CorsHosts); 45 | return explode(',', $hosts, -1); 46 | } 47 | } -------------------------------------------------------------------------------- /app/Middleware/Helper.php: -------------------------------------------------------------------------------- 1 | logger = Factory::getLogger(); 19 | } 20 | 21 | /** 22 | * @param ServerRequestInterface $request 23 | * @return UserModel 24 | */ 25 | public function getUserFromReq(ServerRequestInterface $request) 26 | { 27 | $token = Http::getTokenFromReq($request); 28 | if (!$token) { 29 | return $this->guestUser(); 30 | } 31 | $token = Factory::getTokenStorage()->get($token); 32 | if (!$token) { 33 | return $this->guestUser(); 34 | } 35 | return $token->getUser(); 36 | } 37 | 38 | /** 39 | * @return UserModel 40 | */ 41 | private function guestUser() 42 | { 43 | $user = new UserModel(); 44 | $user->isLogin = false; 45 | return $user; 46 | } 47 | 48 | /** 49 | * @param $response 50 | * @param $data 51 | * @param int $statusCode 52 | * 53 | * @return mixed 54 | */ 55 | public function echoJson(Response $response, $data = [], $statusCode = 200) 56 | { 57 | $newResponse = $response->withJson($data, $statusCode); 58 | return $newResponse; 59 | } 60 | } -------------------------------------------------------------------------------- /app/Middleware/Mu.php: -------------------------------------------------------------------------------- 1 | withJson([], 401); 17 | 18 | return $newResponse; 19 | } 20 | if ($key != db_config(self::MuKey)) { 21 | $newResponse = $response->withJson([], 401); 22 | 23 | return $newResponse; 24 | } 25 | $response = $next($request, $response); 26 | 27 | return $response; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Models/CheckInLog.php: -------------------------------------------------------------------------------- 1 | attributes['traffic']); 19 | } 20 | 21 | /** 22 | * @return mixed 23 | */ 24 | public function CheckInTime() 25 | { 26 | return Tools::toDateTime($this->attributes['checkin_at']); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Models/ConfigModel.php: -------------------------------------------------------------------------------- 1 | getConnection()->getPdo(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Models/Node.php: -------------------------------------------------------------------------------- 1 | 'boolean', 21 | 'v2ray_enable' => 'boolean', 22 | 'https_enable' => 'boolean', 23 | ]; 24 | 25 | public function getLastNodeInfoLog() 26 | { 27 | $id = $this->attributes['id']; 28 | $log = NodeInfoLog::where('node_id', $id)->orderBy('id', 'desc')->first(); 29 | if ($log == null) { 30 | return null; 31 | } 32 | 33 | return $log; 34 | } 35 | 36 | public function getNodeUptime() 37 | { 38 | $log = $this->getLastNodeInfoLog(); 39 | if ($log == null) { 40 | return lang('ss.no_data'); 41 | } 42 | 43 | return Tools::secondsToTime((int) $log->uptime); 44 | } 45 | 46 | public function getNodeLoad() 47 | { 48 | $log = $this->getLastNodeInfoLog(); 49 | if ($log == null) { 50 | return lang('ss.no_data'); 51 | } 52 | 53 | return $log->load; 54 | } 55 | 56 | public function getLastNodeOnlineLog() 57 | { 58 | $id = $this->attributes['id']; 59 | $log = NodeOnlineLog::where('node_id', $id)->orderBy('id', 'desc')->first(); 60 | if ($log == null) { 61 | return null; 62 | } 63 | 64 | return $log; 65 | } 66 | 67 | public function getOnlineUserCount() 68 | { 69 | $log = $this->getLastNodeOnlineLog(); 70 | if ($log == null) { 71 | return lang('ss.no_data'); 72 | } 73 | 74 | return $log->online_user; 75 | } 76 | 77 | public function getTrafficFromLogs() 78 | { 79 | $id = $this->attributes['id']; 80 | $traffic = TrafficLog::where('node_id', $id)->sum('u') + TrafficLog::where('node_id', $id)->sum('d'); 81 | if ($traffic == 0) { 82 | return lang('ss.no_data'); 83 | } 84 | 85 | return Tools::flowAutoShow($traffic); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Models/NodeInfoLog.php: -------------------------------------------------------------------------------- 1 | attributes['node_id']); 14 | } 15 | 16 | public function totalUsed() 17 | { 18 | return Tools::flowAutoShow($this->attributes['u'] + $this->attributes['d']); 19 | } 20 | 21 | public function logTime() 22 | { 23 | return Tools::toDateTime($this->attributes['log_time']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Providers/DbConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | singleton(DbConfig::class, function ($app) { 13 | return new DbConfig(); 14 | }); 15 | } 16 | 17 | public function boot() 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Providers/MailServiceProvider.php: -------------------------------------------------------------------------------- 1 | singleton(MailService::class, function ($app) { 15 | // @todo 16 | return new Mailgun(); 17 | }); 18 | } 19 | 20 | public function boot() 21 | { 22 | } 23 | } -------------------------------------------------------------------------------- /app/Providers/TokenStorageServiceProvider.php: -------------------------------------------------------------------------------- 1 | singleton(TokenStorageInterface::class, function () { 16 | return new TokenStorage(app()->make('cache')); 17 | }); 18 | } 19 | 20 | public function boot() 21 | { 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /app/Services/Analytic.php: -------------------------------------------------------------------------------- 1 | ', 0)->count(); 19 | } 20 | 21 | public function getTrafficTotal() 22 | { 23 | $total = User::sum('u') + USer::sum('d'); 24 | 25 | return Tools::flowAutoShow($total); 26 | } 27 | 28 | public function getOnlineUser($time) 29 | { 30 | $time = time() - $time; 31 | 32 | return User::where('t', '>', $time)->count(); 33 | } 34 | 35 | public function getTotalNode() 36 | { 37 | return Node::count(); 38 | } 39 | 40 | public function userCount() 41 | { 42 | return User::all()->count(); 43 | } 44 | 45 | public function checkinUserCount() 46 | { 47 | return User::where('last_checkin_time', '>', 1)->count(); 48 | } 49 | 50 | public function activedUserCount() 51 | { 52 | return User::where('t', '>', 1)->count(); 53 | } 54 | 55 | public function totalTraffic() 56 | { 57 | $u = User::all()->sum('u'); 58 | $d = User::all()->sum('d'); 59 | 60 | return Tools::flowAutoShow($u + $d); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Services/Auth/EmailVerify.php: -------------------------------------------------------------------------------- 1 | first(); 21 | if ($verification == null) { 22 | $verification = new EmailVerifyModel(); 23 | $verification->email = $email; 24 | } 25 | $verification->token = Tools::genRandomChar(Config::get('emailVerifyCodeLength')); 26 | $verification->expire_at = time() + $ttl * 60; 27 | if (!$verification->save()) { 28 | return false; 29 | } 30 | $appName = Config::get('appName'); 31 | $subject = $appName.' 邮箱验证'; 32 | 33 | try { 34 | Mail::send($email, $subject, 'auth/verify.tpl', [ 35 | 'verification' => $verification, 36 | 'ttl' => $ttl, 37 | ], []); 38 | } catch (Exception $e) { 39 | return false; 40 | } 41 | 42 | return true; 43 | } 44 | 45 | /** 46 | * @param string $email 47 | * @param string $verify_code 48 | * 49 | * @return bool 50 | */ 51 | public static function checkVerifyCode($email, $verify_code) 52 | { 53 | $verification = EmailVerifyModel::where('email', '=', $email)->first(); 54 | if ($verification == null || $verification->expire_at < time() || $verification->token !== $verify_code) { 55 | return false; 56 | } 57 | $verification->delete(); 58 | 59 | return true; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Services/Auth/Token.php: -------------------------------------------------------------------------------- 1 | accessToken = $accessToken; 24 | $this->cache = Factory::getCache(); 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getAccessToken() 31 | { 32 | return $this->accessToken; 33 | } 34 | 35 | /** 36 | * @return User 37 | */ 38 | public function getUser() 39 | { 40 | $uid = $this->cache->get($this->accessToken); 41 | $user = User::find($uid); 42 | $user->isLogin = true; 43 | return $user; 44 | } 45 | } -------------------------------------------------------------------------------- /app/Services/Auth/TokenStorage.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 26 | } 27 | 28 | /** 29 | * @param User $user 30 | * @param $ttl 31 | * @return TokenInterface 32 | */ 33 | public function store(User $user, $ttl) 34 | { 35 | $accessToken = Tools::genToken(); 36 | $this->cache->set($accessToken, $user->getId(), $ttl); 37 | return new Token($accessToken); 38 | } 39 | 40 | /** 41 | * @param $token string 42 | * @return TokenInterface 43 | */ 44 | public function get($token) 45 | { 46 | if (!$this->cache->has($token)) { 47 | return null; 48 | } 49 | return new Token($token); 50 | } 51 | 52 | /** 53 | * @param $token string 54 | * @return bool 55 | */ 56 | public function delete($token) 57 | { 58 | $this->cache->delete($token); 59 | return true; 60 | } 61 | 62 | 63 | } -------------------------------------------------------------------------------- /app/Services/Auth/User.php: -------------------------------------------------------------------------------- 1 | 'us-west-2', 16 | 'version' => 'latest', 17 | 'DynamoDb' => [ 18 | 'region' => 'eu-central-1', 19 | ], 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Services/Aws/Factory.php: -------------------------------------------------------------------------------- 1 | array( 17 | 'key' => Config::get('aws_access_key_id'), 18 | 'secret' => Config::get('aws_secret_access_key'), 19 | ), 20 | 'region' => Config::get('aws_region'), 21 | 'version' => 'latest', 22 | 'DynamoDb' => [ 23 | 'region' => Config::get('aws_region'), 24 | ], 25 | 'Ses' => [ 26 | 'region' => Config::get('aws_ses_region'), 27 | ], 28 | ]); 29 | 30 | return $sdk; 31 | } 32 | 33 | /** 34 | * @codeCoverageIgnore 35 | */ 36 | public static function createDynamodb() 37 | { 38 | return self::createAwsClient()->createDynamoDb(); 39 | } 40 | 41 | /** 42 | * @codeCoverageIgnore 43 | */ 44 | public static function createSes() 45 | { 46 | return self::createAwsClient()->createSes(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Services/Config/DbConfig.php: -------------------------------------------------------------------------------- 1 | keyPrefix . $key; 35 | } 36 | 37 | public function __construct() 38 | { 39 | $this->redis = Redis::getConfigRedis(); 40 | $this->db = Factory::newMysqlConfig(); 41 | $this->logger = F::getLogger(); 42 | } 43 | 44 | /** 45 | * @param $key 46 | * @param $default 47 | * @return string 48 | */ 49 | public function get($key, $default = '') 50 | { 51 | if ($this->redis->exists($this->genKey($key)) > 0) { 52 | $v = $this->redis->get($this->genKey($key)); 53 | $this->logger->debug("get config $key from cache $v"); 54 | if (!$v) { 55 | return $default; 56 | } 57 | return $v; 58 | } 59 | 60 | $value = $this->db->get($key); 61 | $this->logger->debug("get config $key from db $value"); 62 | $this->redis->set($this->genKey($key), $value); 63 | if (!$value) { 64 | return $default; 65 | } 66 | 67 | return $value; 68 | } 69 | 70 | /** 71 | * @param $key 72 | * @param $value 73 | */ 74 | public function set($key, $value) 75 | { 76 | $this->db->set($key, $value); 77 | $this->redis->set($this->genKey($key), $value); 78 | } 79 | 80 | public function flushAll() 81 | { 82 | $this->redis->flushdb(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/Services/Config/Factory.php: -------------------------------------------------------------------------------- 1 | db = new ConfigModel(); 17 | } 18 | 19 | /** 20 | * @param $key 21 | * @return null|string 22 | */ 23 | public function get($key) 24 | { 25 | $m = $this->getByKey($key); 26 | if ($m) { 27 | return $m->value; 28 | } 29 | return ''; 30 | } 31 | 32 | private function getByKey($key) 33 | { 34 | $m = ConfigModel::where('key', $key)->first(); 35 | 36 | return $m; 37 | } 38 | 39 | /** 40 | * @param $key 41 | * @param $value 42 | * 43 | * @return bool 44 | */ 45 | public function set($key, $value) 46 | { 47 | $m = $this->getByKey($key); 48 | if (!$m) { 49 | $m = new ConfigModel(); 50 | } 51 | $m->key = $key; 52 | $m->value = $value; 53 | 54 | return $m->save(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Services/Databases/Migration.php: -------------------------------------------------------------------------------- 1 | getConnectionResolver(), $table); 32 | } 33 | 34 | public function getMigrator() 35 | { 36 | $this->setFacade(); 37 | return new Migrator($this->getRepository(),$this->getConnectionResolver(),new Filesystem()); 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /app/Services/Factories/Pay.php: -------------------------------------------------------------------------------- 1 | setConfig(config('paypal')); 20 | return $apiContext; 21 | } 22 | } -------------------------------------------------------------------------------- /app/Services/Factories/Redis.php: -------------------------------------------------------------------------------- 1 | select($config['database']); 37 | 38 | return $client; 39 | } 40 | 41 | /** 42 | * @return Client 43 | */ 44 | public static function getCacheRedis() 45 | { 46 | return self::newRedis('cache'); 47 | } 48 | 49 | public static function getConfigRedis() 50 | { 51 | return self::newRedis('cache'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/Services/Factory.php: -------------------------------------------------------------------------------- 1 | make('cache'); 18 | } 19 | 20 | /** 21 | * @return LoggerInterface 22 | */ 23 | public static function getLogger() 24 | { 25 | return app()->make('log'); 26 | } 27 | 28 | 29 | 30 | /** 31 | * @return TokenStorageInterface 32 | */ 33 | public static function getTokenStorage() 34 | { 35 | return app()->make(TokenStorageInterface::class); 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /app/Services/FakeApp.php: -------------------------------------------------------------------------------- 1 | getContainer()[$offset]); 19 | } 20 | 21 | public function offsetGet($offset) 22 | { 23 | return app()->getContainer()[$offset]; 24 | } 25 | 26 | public function offsetSet($offset, $value) 27 | { 28 | app()->getContainer()[$offset] = $value; 29 | } 30 | 31 | public function offsetUnset($offset) 32 | { 33 | unset(app()->getContainer()[$offset]); 34 | } 35 | 36 | 37 | } -------------------------------------------------------------------------------- /app/Services/Logger.php: -------------------------------------------------------------------------------- 1 | pushHandler($fileHandler); 19 | 20 | return $logger; 21 | } 22 | 23 | public static function newDbLog($type, $msg) 24 | { 25 | $log = new Log(); 26 | $log->type = $type; 27 | $log->msg = $msg; 28 | $log->created_time = time(); 29 | 30 | return $log->save(); 31 | } 32 | 33 | /** 34 | * @param $msg 35 | * 36 | * @return bool 37 | */ 38 | public static function info($msg) 39 | { 40 | return self::logger()->info($msg); 41 | } 42 | 43 | /** 44 | * @param $msg 45 | * 46 | * @return bool 47 | */ 48 | public static function error($msg) 49 | { 50 | return self::logger()->err($msg); 51 | } 52 | 53 | /** 54 | * @param $msg 55 | * 56 | * @return bool 57 | */ 58 | public static function debug($msg) 59 | { 60 | return self::logger()->debug($msg); 61 | } 62 | 63 | /** 64 | * @param $msg 65 | * 66 | * @return bool 67 | */ 68 | public static function warning($msg) 69 | { 70 | return self::logger()->warning($msg); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Services/Mail.php: -------------------------------------------------------------------------------- 1 | settemplatedir(BASE_PATH.'/resources/email/'); 52 | $smarty->setcompiledir(BASE_PATH.'/storage/framework/smarty/compile/'); 53 | $smarty->setcachedir(BASE_PATH.'/storage/framework/smarty/cache/'); 54 | // add config 55 | $smarty->assign('config', Config::getPublicConfig()); 56 | $smarty->assign('analyticsCode', DbConfig::get('analytics-code')); 57 | foreach ($ary as $key => $value) { 58 | $smarty->assign($key, $value); 59 | } 60 | 61 | return $smarty->fetch($template); 62 | } 63 | 64 | /** 65 | * @param $to 66 | * @param $subject 67 | * @param $template 68 | * @param $ary 69 | * @param $file 70 | * 71 | * @return bool|void 72 | */ 73 | public static function send($to, $subject, $template, $ary = [], $file = []) 74 | { 75 | $text = self::genHtml($template, $ary); 76 | Logger::debug($text); 77 | 78 | return self::getClient()->send($to, $subject, $text, $file); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/Services/Mail/Base.php: -------------------------------------------------------------------------------- 1 | make('view'); 16 | } 17 | 18 | 19 | public function genHtml($template, $param) 20 | { 21 | return $this->getView()->render($template . ".html", $param); 22 | } 23 | 24 | 25 | public function send($to, $subject, $template, $params, $file = NULL) 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Services/Mail/File.php: -------------------------------------------------------------------------------- 1 | config = $this->getConfig(); 18 | $this->mg = new MailgunService($this->config['key']); 19 | $this->domain = $this->config['domain']; 20 | $this->sender = $this->config['sender']; 21 | } 22 | 23 | /** 24 | * @codeCoverageIgnore 25 | */ 26 | public function getConfig() 27 | { 28 | return [ 29 | 'key' => db_config(self::MailgunKey), 30 | 'domain' => db_config(self::MailgunDomain), 31 | 'sender' => db_config(self::MailgunSender), 32 | ]; 33 | } 34 | 35 | /** 36 | * @param $to 37 | * @param $subject 38 | * @param $template 39 | * @param $params 40 | * @param null $file 41 | * @codeCoverageIgnore 42 | */ 43 | public function send($to, $subject, $template, $params, $file = null) 44 | { 45 | $this->mg->sendMessage($this->domain, 46 | [ 47 | 'from' => $this->sender, 48 | 'to' => $to, 49 | 'subject' => $subject, 50 | 'html' => $this->genHtml($template,$params), 51 | ], 52 | [ 53 | 'inline' => $file, 54 | ] 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Services/Mail/SendCloud.php: -------------------------------------------------------------------------------- 1 | config = $this->getConfig(); 18 | $this->sc = new SendCloudService(); 19 | } 20 | 21 | /** 22 | * @codeCoverageIgnore 23 | */ 24 | public function getConfig() 25 | { 26 | return [ 27 | 'key' => Config::get('sendcloud_key'), 28 | 'user' => Config::get('sendcloud_user'), 29 | 'sender' => Config::get('sendcloud_sender'), 30 | ]; 31 | } 32 | 33 | /** 34 | * @codeCoverageIgnore 35 | */ 36 | public function send($to, $subject, $text, $file) 37 | { 38 | $req = $this->sc->prepare('mail', 'send', [ 39 | 'apiUser' => $this->config['user'], 40 | 'apiKey' => $this->config['key'], 41 | 'from' => $this->config['sender'], 42 | 'to' => $to, 43 | 'subject' => $subject, 44 | 'html' => $text, 45 | ]); 46 | $req->send(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Services/Mail/Ses.php: -------------------------------------------------------------------------------- 1 | client = Factory::createSes(); 18 | } 19 | 20 | /** 21 | * @codeCoverageIgnore 22 | */ 23 | public function getSender() 24 | { 25 | } 26 | 27 | /** 28 | * @codeCoverageIgnore 29 | */ 30 | public function send($to, $subject, $text,$file) 31 | { 32 | $this->client->sendEmail([ 33 | 'Destination' => [ // REQUIRED 34 | 'ToAddresses' => [$to], 35 | ], 36 | 'Message' => [ // REQUIRED 37 | 'Body' => [ // REQUIRED 38 | 'Html' => [ 39 | 'Data' => $text, // REQUIRED 40 | ], 41 | 'Text' => [ 42 | 'Data' => $text, // REQUIRED 43 | ], 44 | ], 45 | 'Subject' => [ // REQUIRED 46 | 'Data' => $subject// REQUIRED 47 | ], 48 | ], 49 | 'Source' => $this->getSender(), // REQUIRED 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Services/Mail/Smtp.php: -------------------------------------------------------------------------------- 1 | config = $this->getConfig(); 18 | $mail = new PHPMailer(); 19 | //$mail->SMTPDebug = 3; // Enable verbose debug output 20 | $mail->isSMTP(); // Set mailer to use SMTP 21 | $mail->Host = $this->config['host']; // Specify main and backup SMTP servers 22 | $mail->SMTPAuth = true; // Enable SMTP authentication 23 | $mail->Username = $this->config['username']; // SMTP username 24 | $mail->Password = $this->config['passsword']; // SMTP password 25 | $mail->SMTPSecure = 'tls'; // Enable TLS encryption, `ssl` also accepted 26 | $mail->Port = $this->config['port']; // TCP port to connect to 27 | $mail->setFrom($this->config['sender'], $this->config['name']); 28 | $mail->CharSet = 'UTF-8'; 29 | $this->mail = $mail; 30 | } 31 | 32 | public function getConfig() 33 | { 34 | return [ 35 | 'host' => Config::get('smtp_host'), 36 | 'username' => Config::get('smtp_username'), 37 | 'port' => Config::get('smtp_port'), 38 | 'sender' => Config::get('smtp_sender'), 39 | 'name' => Config::get('smtp_name'), 40 | 'passsword' => Config::get('smtp_passsword'), 41 | ]; 42 | } 43 | 44 | /** 45 | * @codeCoverageIgnore 46 | */ 47 | public function send($to, $subject, $text, $file) 48 | { 49 | $mail = $this->mail; 50 | $mail->addAddress($to); // Add a recipient 51 | $mail->isHTML(true); 52 | $mail->Subject = $subject; 53 | $mail->Body = $text; 54 | // $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; 55 | if (!$mail->send()) { 56 | return true; 57 | } 58 | 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Services/Migration.php: -------------------------------------------------------------------------------- 1 | capsule = new Capsule(); 20 | $config = app()->make('config')->get('database.connections'); 21 | foreach ($config as $k => $v) { 22 | $this->capsule->addConnection($v, $k); 23 | } 24 | $default = app()->make('config')->get('database')['default']; 25 | if ($default) { 26 | if (isset($config[$default])) { 27 | $this->capsule->addConnection($config[$default], 'default'); 28 | } 29 | } 30 | $this->capsule->bootEloquent(); 31 | $this->schema = $this->capsule->schema(); 32 | } 33 | 34 | public function init() 35 | { 36 | $this->capsule = new Capsule; 37 | $this->capsule->addConnection([ 38 | 'driver' => 'mysql', 39 | 'host' => '127.0.0.1', 40 | 'port' => 3306, 41 | 'database' => 'sspanel', 42 | 'username' => 'sspanel', 43 | 'password' => 'sspanel', 44 | 'charset' => 'utf8', 45 | 'collation' => 'utf8_unicode_ci', 46 | ]); 47 | 48 | $this->capsule->bootEloquent(); 49 | $this->capsule->setAsGlobal(); 50 | $this->schema = $this->capsule->schema(); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/Services/Password.php: -------------------------------------------------------------------------------- 1 | mail = $this->getMailService(); 25 | } 26 | 27 | /** 28 | * @return MailService 29 | */ 30 | private function getMailService() 31 | { 32 | return app()->make(MailService::class); 33 | } 34 | 35 | /** 36 | * @param $email string 37 | * 38 | * @return bool 39 | */ 40 | public function sendResetEmail($email) 41 | { 42 | $pwdRst = new PasswordReset(); 43 | $pwdRst->email = $email; 44 | $pwdRst->init_time = time(); 45 | $pwdRst->expire_time = time() + 3600 * 24; // @todo 46 | $pwdRst->token = Tools::genRandomChar(64); 47 | if (!$pwdRst->save()) { 48 | return false; 49 | } 50 | $subject = sprintf("%s %s", db_config(self::AppName), lang('auth.reset-password')); 51 | $resetUrl = self::genUri($pwdRst->token); 52 | try { 53 | // @todo trans email template 54 | $template = 'email/password/reset'; 55 | $this->mail->send($email, $subject, $template, [ 56 | 'subject' => $subject, 57 | 'resetUrl' => $resetUrl 58 | ]); 59 | } catch (Exception $e) { 60 | throw $e; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | public static function resetBy($token, $password) 67 | { 68 | } 69 | 70 | public static function genUri($token) 71 | { 72 | $uri = db_config(self::AppUri); 73 | return sprintf("%s/password/%s", rtrim($uri,'/'), $token); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Services/Token/Base.php: -------------------------------------------------------------------------------- 1 | token = $tokenStr; 15 | $token->user_id = $user->id; 16 | $token->create_time = time(); 17 | $token->expire_time = $expireTime; 18 | if ($token->save()) { 19 | return true; 20 | } 21 | 22 | return false; 23 | } 24 | 25 | public function delete($token) 26 | { 27 | $token = TokenModel::where('token', $token)->first(); 28 | if ($token == null) { 29 | return false; 30 | } 31 | $token->delete(); 32 | 33 | return true; 34 | } 35 | 36 | public function get($token) 37 | { 38 | try { 39 | $tokenModel = TokenModel::where('token', $token)->firstOrFail(); 40 | } catch (ModelNotFoundException $e) { 41 | return null; 42 | } 43 | $token = new Token(); 44 | $token->token = $tokenModel->token; 45 | $token->userId = $tokenModel->user_id; 46 | $token->createTime = $tokenModel->create_time; 47 | $token->expireTime = $tokenModel->expire_time; 48 | 49 | return $token; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Services/Token/Dynamodb.php: -------------------------------------------------------------------------------- 1 | client = Factory::createDynamodb(); 20 | $this->tableName = 'token'; 21 | } 22 | 23 | /** 24 | * @codeCoverageIgnore 25 | */ 26 | public function store($token, User $user, $expireTime) 27 | { 28 | $result = $this->client->putItem(array( 29 | 'TableName' => $this->tableName, 30 | 'Item' => array( 31 | 'token' => array('S' => $token), 32 | 'user_id' => array('N' => (string) $user->id), 33 | 'create_time' => array('N' => (string) time()), 34 | 'expire_time' => array('N' => (string) $expireTime), 35 | ), 36 | )); 37 | 38 | return true; 39 | } 40 | 41 | /** 42 | * @codeCoverageIgnore 43 | */ 44 | public function delete($token) 45 | { 46 | $this->client->deleteItem(array( 47 | 'TableName' => $this->tableName, 48 | 'Key' => array( 49 | 'token' => array('S' => $token), 50 | ), 51 | )); 52 | } 53 | 54 | /** 55 | * @codeCoverageIgnore 56 | */ 57 | public function get($token) 58 | { 59 | $result = $this->client->getItem(array( 60 | 'ConsistentRead' => true, 61 | 'TableName' => $this->tableName, 62 | 'Key' => array( 63 | 'token' => array('S' => $token), 64 | ), 65 | )); 66 | $token = new Token(); 67 | $token->token = $result['Item']['token']['S']; 68 | $token->userId = $result['Item']['user_id']['N']; 69 | $token->createTime = $result['Item']['create_time']['N']; 70 | $token->expireTime = $result['Item']['expire_time']['N']; 71 | 72 | return $token; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Services/Token/Token.php: -------------------------------------------------------------------------------- 1 | [ 14 | "access" => "/var/log/access.log", 15 | "error" => "/var/log/error.log", 16 | "loglevel" => "warning" 17 | ], 18 | "inbound" => [ 19 | "port" => 8300, 20 | "protocol" => "vmess", 21 | "settings" => [ 22 | "clients" => [ 23 | ] 24 | ] 25 | ], 26 | "outbound" => [ 27 | "protocol" => "freedom", 28 | // "settings" => , 29 | ], 30 | "inboundDetour" => [], 31 | "outboundDetour" => [ 32 | [ 33 | "protocol" => "blackhole", 34 | // "settings" => [], 35 | "tag" => "blocked" 36 | ] 37 | ], 38 | "routing" => [ 39 | "strategy" => "rules", 40 | "settings" => [ 41 | "rules" => [ 42 | [ 43 | "type" => "field", 44 | "ip" => [ 45 | "0.0.0.0/8", 46 | "10.0.0.0/8", 47 | "100.64.0.0/10", 48 | "127.0.0.0/8", 49 | "169.254.0.0/16", 50 | "172.16.0.0/12", 51 | "192.0.0.0/24", 52 | "192.0.2.0/24", 53 | "192.168.0.0/16", 54 | "198.18.0.0/15", 55 | "198.51.100.0/24", 56 | "203.0.113.0/24", 57 | "::1/128", 58 | "fc00::/7", 59 | "fe80::/10" 60 | ], 61 | "outboundTag" => "blocked" 62 | ] 63 | ] 64 | ] 65 | ] 66 | ]; 67 | 68 | 69 | public function addUser($uuid, $level, $alertId, $email) 70 | { 71 | $user = [ 72 | "id" => $uuid, 73 | "level" => $level, 74 | "alterId" => $alertId, 75 | "email" => $email 76 | ]; 77 | array_push($this->arr["inbound"]['settings']['clients'], $user); 78 | } 79 | 80 | public function setPort($port) 81 | { 82 | $this->arr['inbound']['port'] = $port; 83 | } 84 | 85 | public function getArr() 86 | { 87 | return $this->arr; 88 | } 89 | 90 | public function __construct() 91 | { 92 | $this->arr["outbound"]["settings"] = new EmptyClass(); 93 | $this->arr["outboundDetour"][0]["settings"] = new EmptyClass(); 94 | } 95 | } -------------------------------------------------------------------------------- /app/Storage/Dynamodb/TrafficLog.php: -------------------------------------------------------------------------------- 1 | client = Factory::createDynamodb(); 17 | $this->tableName = 'traffic_log'; 18 | } 19 | 20 | public function store($u, $d, $nodeId, $userId, $traffic, $rate) 21 | { 22 | $id = Tools::genUUID(); 23 | $result = $this->client->putItem(array( 24 | 'TableName' => $this->tableName, 25 | 'Item' => array( 26 | 'id' => array('S' => $id), 27 | 'u' => array('S' => (string) $u), 28 | 'd' => array('N' => (string) $d), 29 | 'node_id' => array('N' => (string) $nodeId), 30 | 'rate' => array('N' => (string) $rate), 31 | 'traffic' => array('S' => (string) $traffic), 32 | 'user_id' => array('N' => (string) $userId), 33 | 'create_at' => array('N' => (string) time()), 34 | 'create_time' => array('S' => Tools::toDateTime(time())), 35 | ), 36 | )); 37 | 38 | return $id; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Support/helper.php: -------------------------------------------------------------------------------- 1 | make(\App\Services\Config\DbConfig::class)->get($key, $default); 16 | } 17 | } 18 | 19 | 20 | if (!function_exists('user')) { 21 | 22 | /** 23 | * @return \App\Models\User 24 | */ 25 | function user() 26 | { 27 | return \App\Services\Auth\User::getUser(); 28 | } 29 | 30 | } 31 | 32 | if (!function_exists('get_version')) { 33 | /** 34 | * @return string 35 | */ 36 | function get_version() 37 | { 38 | // @todo 39 | return '4.0.0'; 40 | } 41 | } -------------------------------------------------------------------------------- /app/Utils/Check.php: -------------------------------------------------------------------------------- 1 | where('reg_date', '>', Tools::toDateTime(time() - $time))->count(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Utils/EmptyClass.php: -------------------------------------------------------------------------------- 1 | error("true: " . $truePassword . " input: " . $hashedPassword); 96 | return false; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/Utils/Helper.php: -------------------------------------------------------------------------------- 1 | hasHeader('Token')) { 25 | return $request->getHeaderLine('Token'); 26 | } 27 | $params = $request->getQueryParams(); 28 | if (!isset($params['access_token'])) { 29 | return null; 30 | } 31 | $accessToken = $params['access_token']; 32 | 33 | return $accessToken; 34 | } 35 | 36 | public static function getMuKeyFromReq(Request $request) 37 | { 38 | if ($request->hasHeader('Key')) { 39 | return $request->getHeaderLine('Key'); 40 | } 41 | if ($request->hasHeader('Token')) { 42 | return $request->getHeaderLine('Token'); 43 | } 44 | $params = $request->getQueryParams(); 45 | if (!isset($params['key'])) { 46 | return null; 47 | } 48 | $accessToken = $params['key']; 49 | 50 | return $accessToken; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Utils/Http.php: -------------------------------------------------------------------------------- 1 | getHeaderLine('Authorization'); 59 | if($auth){ 60 | if (preg_match('/Bearer\s(\S+)/', $auth, $matches)) { 61 | return $matches[1]; 62 | } 63 | } 64 | 65 | $token = $req->getHeaderLine('Token'); 66 | if ($token) { 67 | return $token; 68 | } 69 | $cookies = $req->getCookieParams(); 70 | return isset($cookies['token']) ? $cookies['token'] : null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Utils/Ss.php: -------------------------------------------------------------------------------- 1 | $server, 18 | 'server_port' => $port, 19 | 'password' => $pwd, 20 | 'method' => $method, 21 | ]; 22 | 23 | return $arr; 24 | } 25 | 26 | 27 | /** 28 | * @return array 29 | */ 30 | public static function getCipher() 31 | { 32 | return Shadowsocks::Cipher; 33 | } 34 | 35 | /** 36 | * @return array 37 | */ 38 | public static function getProtocol() 39 | { 40 | return ShadowsocksR::Protocol; 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | public static function getObfs() 47 | { 48 | return ShadowsocksR::Obfs; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | load(); 20 | } catch (Dotenv\Exception\InvalidPathException $e) { 21 | } 22 | 23 | /** 24 | * New App. 25 | */ 26 | $app = new \Pongtan\App(__DIR__ . '/../'); 27 | 28 | /* 29 | * Register Service Provider 30 | */ 31 | $app->register(\Pongtan\Providers\ConfigServiceProvider::class); 32 | $app->register(\Pongtan\Providers\LoggerServiceProvider::class); 33 | $app->register(\Pongtan\Providers\LangServiceProvider::class); 34 | $app->register(\Pongtan\Providers\ViewServiceProvider::class); 35 | $app->register(\Pongtan\Providers\EloquentServiceProvider::class); 36 | $app->register(\Pongtan\Providers\CacheServiceProvider::class); 37 | $app->register(\App\Providers\TokenStorageServiceProvider::class); 38 | $app->register(\App\Providers\DbConfigServiceProvider::class); 39 | $app->register(\App\Providers\MailServiceProvider::class); 40 | 41 | require $basePath . '/routes/web.php'; 42 | 43 | return $app; 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "pongtan/framework": "0.1.*", 4 | "pongtan/utils": "0.1.*", 5 | "ramsey/uuid": "^3.3", 6 | "china-shevy/sendcloud-email-v2": "dev-master", 7 | "tracy/tracy": "^2.4", 8 | "shadowsocks/shadowsocks": "0.1.*", 9 | "zircote/swagger-php": "^2.0", 10 | "illuminate/pagination": "^5.5", 11 | "martinlindhe/laravel-vue-i18n-generator": "^0.1.13", 12 | "illuminate/events": "^5.5", 13 | "pongtan/fake-laravel": "^5.5", 14 | "paypal/rest-api-sdk-php": "^1.12", 15 | "latrell/alipay": "^1.2" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "~6.0", 19 | "phploc/phploc": "*", 20 | "phpmd/phpmd" : "@stable", 21 | "sebastian/phpcpd": "*", 22 | "squizlabs/php_codesniffer": "2.*" 23 | 24 | }, 25 | "autoload": { 26 | "classmap": [ 27 | "databases" 28 | ], 29 | "psr-4": { 30 | "App\\": "app/", 31 | "Tests\\": "tests/" 32 | }, 33 | "files":[ 34 | "app/Support/helper.php" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env("APP_ENV", "prod"), 9 | 'key' => env("APP_KEY", ''), 10 | 'name' => env("APP_NAME", 'ss-panel 4'), 11 | 'base_url' => env("APP_BASEURL", '/'), 12 | 'time_zone' => env("APP_TIMEZONE", 'UTC'), // UTC 13 | 'theme' => env("APP_THEME", 'default'), 14 | 'log_level' => env('APP_LOG_LEVEL', 'debug'), 15 | 'lang' => env('APP_LANG', 'en'), 16 | 17 | 18 | 'mu_key' => env("APP_MU_KEY"), 19 | 20 | // hour 21 | 'checkin_time' => env("APP_CHECKIN_TIME", 24), 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | env('AUTH_PASSWORD_ENCRYPTION_TYPE', 'bcrypt'), 9 | 'salt' => env("AUTH_SALT", ''), 10 | 'session_timeout' => env("TOKEN_TTL", 3600 * 24), 11 | 'email_verify_enabled' => env('APP_EmailVerifyEnabled', false), 12 | 'is_enable_google_login' => env('AUTH_IS_ENABLE_GOOGLE_LOGIN', false), 13 | 'is_enable_facebook_login' => env('AUTH_IS_ENABLE_FACEBOOK_LOGIN', false), 14 | ]; 15 | -------------------------------------------------------------------------------- /config/paypal.php: -------------------------------------------------------------------------------- 1 | env('PAYPAL_WEBHOOK_KEY', ''), 10 | 11 | 'client_id' => env('PAYPAL_CLIENT_ID'), 12 | 'secret' => env('PAYPAL_CLIENT_SECRET'), 13 | 'url' => [ 14 | 'cancel' => env('PAYPAL_CANCEL_URL', 'https://loxcloud.com/user/pay'), 15 | 'return' => env('PAYPAL_RETURN_URL', 'https://loxcloud.com/user/pay'), 16 | 'webhook' => env('PAYPAL_WEBHOOK_URL', ''), 17 | ], 18 | 'currency' => env('PAYPAL_CURRENCY', 'USD'), 19 | /* 20 | * SDK configuration 21 | */ 22 | 'settings' => [ 23 | /* 24 | * Available option 'sandbox' or 'live' 25 | */ 26 | 'mode' => env('PAYPAL_MODE', 'live'), 27 | 28 | /* 29 | * Specify the max request time in seconds 30 | */ 31 | 'http.ConnectionTimeOut' => 30, 32 | 33 | /* 34 | * Whether want to log to a file 35 | */ 36 | 'log.LogEnabled' => true, 37 | 38 | /* 39 | * Specify the file that want to write on 40 | */ 41 | // 'log.FileName' => storage_path('logs/paypal-runtime.log'), 42 | 43 | /* 44 | * Available option 'FINE', 'INFO', 'WARN' or 'ERROR' 45 | * 46 | * Logging is most verbose in the 'FINE' level and decreases as you 47 | * proceed towards ERROR 48 | */ 49 | 'log.LogLevel' => 'FINE', 50 | ], 51 | ]; 52 | -------------------------------------------------------------------------------- /config/v2ray.php: -------------------------------------------------------------------------------- 1 | 2, 5 | 'level' => 2, 6 | ]; 7 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 6 | realpath(base_path('/resources/views/default')), 7 | ], 8 | 9 | 'compiled' => env('VIEW_CACHE_PATH', realpath(base_path('/storage/framework/views'))), 10 | 11 | 'enable_functions' => ['config', 'lang', 'conf', 'user', 'get_version', 'sprintf', 'db_config'], 12 | ]; 13 | -------------------------------------------------------------------------------- /config/vue-i18n-generator.php: -------------------------------------------------------------------------------- 1 | '/resources/lang', 11 | 12 | 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Output file 16 | |-------------------------------------------------------------------------- 17 | | 18 | | The javascript path where I will place the generated file. 19 | | Note: the path will be prepended to point to the App directory. 20 | | 21 | */ 22 | 23 | 'jsFile' => '/src/lang/vue-i18n-locales.generated.js' 24 | ]; 25 | -------------------------------------------------------------------------------- /databases/2017_07_04_205431_create_sp_config_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('key', 128); 23 | $table->text('value'); 24 | $table->timestamps(); 25 | 26 | 27 | 28 | 29 | 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | Schema::dropIfExists('sp_config'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /databases/2017_07_04_205432_create_sp_email_verify_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('email', 32); 23 | $table->string('token', 64); 24 | $table->integer('expire_at'); 25 | 26 | 27 | 28 | 29 | 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | Schema::dropIfExists('sp_email_verify'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /databases/2017_07_04_205433_create_sp_log_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('type', 16); 23 | $table->text('msg'); 24 | $table->integer('created_time'); 25 | 26 | 27 | 28 | 29 | 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | Schema::dropIfExists('sp_log'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /databases/2017_07_04_205433_create_ss_checkin_log_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->integer('user_id'); 23 | $table->integer('checkin_at'); 24 | $table->double('traffic'); 25 | $table->nullableTimestamps(); 26 | 27 | $table->index('user_id', 'user_id'); 28 | 29 | 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | Schema::dropIfExists('ss_checkin_log'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /databases/2017_07_04_205434_create_ss_invite_code_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('code', 128); 23 | $table->integer('user_id'); 24 | $table->timestamps(); 25 | 26 | $table->index('user_id', 'user_id'); 27 | 28 | 29 | 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | Schema::dropIfExists('ss_invite_code'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /databases/2017_07_04_205435_create_ss_node_info_log_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->integer('node_id'); 23 | $table->float('uptime'); 24 | $table->string('load', 32); 25 | $table->integer('log_time'); 26 | 27 | $table->index('node_id', 'node_id'); 28 | $table->index('log_time', 'log_time'); 29 | 30 | 31 | 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | * 38 | * @return void 39 | */ 40 | public function down() 41 | { 42 | Schema::dropIfExists('ss_node_info_log'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /databases/2017_07_04_205435_create_ss_node_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('name', 128); 23 | $table->integer('type'); 24 | $table->string('server', 128); 25 | $table->string('method', 64)->default('rc4-md5'); 26 | $table->string('protocol', 128)->default('origin'); 27 | $table->string('protocol_param', 128)->nullable(); 28 | $table->string('obfs', 128)->default('plain'); 29 | $table->string('obfs_param', 128)->nullable(); 30 | $table->float('traffic_rate')->default(1); 31 | $table->string('info', 128); 32 | $table->string('status', 128); 33 | $table->integer('offset'); 34 | $table->integer('sort'); 35 | 36 | 37 | 38 | 39 | 40 | }); 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | * 46 | * @return void 47 | */ 48 | public function down() 49 | { 50 | Schema::dropIfExists('ss_node'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /databases/2017_07_04_205436_create_ss_node_online_log_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->integer('node_id'); 23 | $table->integer('online_user'); 24 | $table->integer('log_time'); 25 | 26 | $table->index('node_id', 'node_id'); 27 | $table->index('log_time', 'log_time'); 28 | 29 | 30 | 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::dropIfExists('ss_node_online_log'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /databases/2017_07_04_205436_create_ss_password_reset_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('email', 32); 23 | $table->string('token', 128); 24 | $table->integer('init_time'); 25 | $table->integer('expire_time'); 26 | 27 | 28 | 29 | 30 | 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::dropIfExists('ss_password_reset'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /databases/2017_07_04_205437_create_user_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->string('user_name', 128); 23 | $table->string('email', 32); 24 | $table->string('pass', 64); 25 | $table->string('passwd', 16); 26 | $table->integer('t')->default(0); 27 | $table->bigInteger('u')->default(0); 28 | $table->bigInteger('d')->default(0); 29 | $table->bigInteger('transfer_enable')->default(0); 30 | $table->integer('port')->default(0); 31 | $table->tinyInteger('switch')->default(1); 32 | $table->tinyInteger('enable')->default(1); 33 | $table->tinyInteger('type')->default(1); 34 | $table->integer('last_get_gift_time')->default(0); 35 | $table->integer('last_check_in_time')->default(0); 36 | $table->integer('last_rest_pass_time')->default(0); 37 | $table->timestamp('reg_date')->default('1989-06-04 03:10:29'); 38 | $table->integer('invite_num')->default(0); 39 | $table->integer('is_admin')->default(0); 40 | $table->integer('ref_by')->default(0); 41 | $table->integer('expire_time')->default(0); 42 | $table->string('method', 64)->default('rc4-md5'); 43 | $table->tinyInteger('custom_method')->default(0); 44 | $table->tinyInteger('custom_rss')->default(0); 45 | $table->string('protocol', 128)->default('origin'); 46 | $table->string('protocol_param', 128)->default(""); 47 | $table->string('obfs', 128)->default('plain'); 48 | $table->string('obfs_param', 128)->default(""); 49 | $table->tinyInteger('is_email_verify')->default(0); 50 | $table->string('reg_ip', 128)->default('127.0.0.1'); 51 | 52 | $table->unique('email', 'email'); 53 | $table->unique('port', 'port'); 54 | 55 | 56 | 57 | }); 58 | } 59 | 60 | /** 61 | * Reverse the migrations. 62 | * 63 | * @return void 64 | */ 65 | public function down() 66 | { 67 | Schema::dropIfExists('user'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /databases/2017_07_04_205438_create_user_traffic_log_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 22 | $table->integer('user_id'); 23 | $table->integer('u'); 24 | $table->integer('d'); 25 | $table->integer('node_id'); 26 | $table->float('rate'); 27 | $table->string('traffic', 32); 28 | $table->integer('log_time'); 29 | 30 | $table->index('user_id', 'user_id'); 31 | $table->index('node_id', 'node_id'); 32 | $table->index('log_time', 'log_time'); 33 | 34 | 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down() 44 | { 45 | Schema::dropIfExists('user_traffic_log'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /databases/2017_11_19_205437_update_ss_node_table.php: -------------------------------------------------------------------------------- 1 | integer('ss_enable')->default(1); 22 | $table->integer('v2ray_enable')->default(0); 23 | $table->integer('https_enable')->default(0); 24 | $table->integer('v2ray_port')->default(8100); 25 | $table->string('v2ray_protocol')->default("tcp"); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /databases/2017_11_19_205437_update_user_table.php: -------------------------------------------------------------------------------- 1 | string('v2ray_uuid')->default(""); 18 | $table->integer('v2ray_level')->default(2); 19 | $table->integer('v2ray_alter_id')->default(2); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /databases/2018_02_25_015826_create_ss_chg_code_table.php: -------------------------------------------------------------------------------- 1 | integer('id'); 22 | $table->string('code', 128); 23 | $table->integer('time'); 24 | $table->bigInteger('traffic'); 25 | $table->string('tag', 64); 26 | $table->integer('add_time'); 27 | $table->integer('status'); 28 | $table->integer('user_id'); 29 | $table->integer('use_time'); 30 | 31 | 32 | 33 | 34 | 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down() 44 | { 45 | Schema::dropIfExists('ss_chg_code'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dev.md: -------------------------------------------------------------------------------- 1 | ## Dev Guide 2 | 3 | ### Commands 4 | 5 | #### Generate Vue Lang js file 6 | 7 | ``` 8 | php xcat genVueLang 9 | ``` 10 | 11 | #### Run Migrations 12 | 13 | ``` 14 | php xcat migrate 15 | ``` 16 | 17 | #### Create Admin 18 | 19 | ``` 20 | php xcat createAdmin 21 | ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | redis: 5 | restart: always 6 | image: redis:latest 7 | container_name: sspanel_redis 8 | command: 9 | - --loglevel warning 10 | volumes: 11 | - ./redis_data:/var/lib/redis:Z 12 | 13 | mysql: 14 | image: mysql 15 | container_name: sspanel_mysql 16 | environment: 17 | - MYSQL_ROOT_PASSWORD=sspanel 18 | - MYSQL_DATABASE=sspanel 19 | volumes: 20 | - ./mysql_data:/var/lib/mysql:Z 21 | 22 | ss-panel: 23 | image: orvice/ss-panel:master 24 | ports: 25 | - 8080:80 26 | container_name: ss-panel 27 | depends_on: 28 | - mysql 29 | - redis 30 | environment: 31 | - MIGRATION=true 32 | - ADMIN_EMAIL= 33 | - ADMIN_PASS= 34 | 35 | - APP_LANG=en 36 | 37 | - AUTH_SALT= 38 | - AUTH_PASSWORD_ENCRYPTION_TYPE=bcrypt 39 | 40 | - DB_HOST=mysql 41 | - DB_DATABASE=sspanel 42 | - DB_USERNAME=root 43 | - DB_PASSWORD=sspanel 44 | 45 | - REDIS_HOST=redis 46 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Entry ss-panel" 3 | if [ "$MIGRATION" = "false" ];then 4 | echo "Skip database migration" 5 | else 6 | echo "start database migration" 7 | php xcat migrate 8 | fi 9 | 10 | 11 | if [ "$ADMIN_EMAIL" != "" ] && [ "$ADMIN_PASS" != "" ];then 12 | echo "create admin" 13 | php xcat createAdmin "$ADMIN_EMAIL" "$ADMIN_PASS" 14 | else 15 | echo "skip create admin" 16 | fi 17 | 18 | chmod -R 777 /var/www/html/storage 19 | echo 'Starting Web....' 20 | exec "$@" -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/public/assets/js/app.js": "/public/assets/js/app.js", 3 | "/public/assets/js/admin.js": "/public/assets/js/admin.js", 4 | "/public/assets/js/home.js": "/public/assets/js/home.js" 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "NODE_ENV=development webpack --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 5 | "watch": "NODE_ENV=development webpack --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "hot": "NODE_ENV=development webpack-dev-server --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "prod": "NODE_ENV=production webpack --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 8 | }, 9 | "devDependencies": { 10 | "babel-plugin-add-module-exports": "^0.2.1", 11 | "babel-plugin-transform-runtime": "^6.23.0", 12 | "babel-preset-es2015": "^6.24.1", 13 | "babel-preset-stage-2": "^6.24.1", 14 | "bootstrap-sass": "^3.3.7", 15 | "jquery": "^3.2.1", 16 | "laravel-mix": "^1.1.1", 17 | "lodash": "^4.16.2", 18 | "transform-runtime": "0.0.0" 19 | }, 20 | "dependencies": { 21 | "axios": "^0.15.2", 22 | "laravel-vue-pagination-uikit": "^1.0.6", 23 | "uikit": "^3.0.0-beta.25", 24 | "vue": "^2.0.1", 25 | "vue-analytics": "^4.1.2", 26 | "vue-i18n": "^5.0.3", 27 | "vue-qr": "^1.2.1", 28 | "vue-router": "^2.7.0", 29 | "vuex": "^2.3.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | app/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /public/assets/css/app.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | height:100%; 3 | } 4 | .uk-logo, .uk-logo:hover, .uk-logo:active, .uk-logo:visited { 5 | color: #ffffff; 6 | } 7 | 8 | .uk-navbar-toggle, .uk-navbar-toggle:hover, .uk-navbar-toggle:active, .uk-navbar-toggle:visited { 9 | color: #ffffff; 10 | padding-top: 2px; 11 | } 12 | .uk-navbar-container { 13 | background-color: #1e87f0 !important; 14 | } 15 | .user > #name { 16 | font-size: 22px; 17 | } 18 | .header { 19 | box-sizing: border-box; 20 | border-bottom: 1px #e5e5e5 solid; 21 | } 22 | .content-padder { 23 | margin-left: 0px; 24 | } 25 | .content-background { 26 | min-height: calc(100% - 80px); 27 | background-color: #F0F0F0; 28 | } 29 | .statistics-text { 30 | font-size: 25px; 31 | } 32 | .statistics-number { 33 | font-size: 50px; 34 | } 35 | 36 | 37 | 38 | 39 | 40 | .tm-sidebar-left { 41 | position: fixed; 42 | z-index: 100; 43 | top: 80px; 44 | bottom: 0; 45 | box-sizing: border-box; 46 | width: 240px !important; 47 | padding: 40px 40px 60px 40px; 48 | border-right: 1px #e5e5e5 solid; 49 | overflow-y: auto; 50 | overflow-x: hidden; 51 | } 52 | .tm-sidebar-left::-webkit-scrollbar-track { 53 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 54 | background-color: #F5F5F5; 55 | } 56 | 57 | .tm-sidebar-left::-webkit-scrollbar { 58 | width: 6px; 59 | background-color: #F5F5F5; 60 | } 61 | 62 | .tm-sidebar-left::-webkit-scrollbar-thumb { 63 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 64 | background-color: #bdbdbd; 65 | } 66 | 67 | .tm-sidebar-right { 68 | position: absolute; 69 | top: 0; 70 | left: calc(100% + 0px); 71 | width: 200px 72 | } 73 | @media(max-width: 960px) { 74 | .tm-sidebar-left { 75 | opacity: 0; 76 | } 77 | } 78 | 79 | @media (min-width: 960px) { 80 | .content-padder { 81 | margin-left: 240px; 82 | } 83 | } 84 | @media (min-width: 1200px) { 85 | .tm-sidebar-right { 86 | left: calc(100% + 0px); 87 | } 88 | .tm-sidebar-left+.tm-main { 89 | padding-left: 40px; 90 | opacity: 0 !important; 91 | } 92 | } 93 | @media (min-width: 1400px) { 94 | .tm-sidebar-left { 95 | width: 300px !important; 96 | padding: 45px 45px 60px 45px 97 | } 98 | .tm-sidebar-right { 99 | left: calc(100% + 60px) 100 | } 101 | .tm-sidebar-left+.tm-main { 102 | padding-left: 40px 103 | } 104 | .content-padder { 105 | margin-left: 300px; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /public/assets/fonts/ProximaNova-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/ProximaNova-Light-webfont.ttf -------------------------------------------------------------------------------- /public/assets/fonts/ProximaNova-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/ProximaNova-Light-webfont.woff -------------------------------------------------------------------------------- /public/assets/fonts/ProximaNova-Reg-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/ProximaNova-Reg-webfont.ttf -------------------------------------------------------------------------------- /public/assets/fonts/ProximaNova-Reg-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/ProximaNova-Reg-webfont.woff -------------------------------------------------------------------------------- /public/assets/fonts/nucleo-outline.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/nucleo-outline.eot -------------------------------------------------------------------------------- /public/assets/fonts/nucleo-outline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/nucleo-outline.ttf -------------------------------------------------------------------------------- /public/assets/fonts/nucleo-outline.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/nucleo-outline.woff -------------------------------------------------------------------------------- /public/assets/fonts/nucleo-outline.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/fonts/nucleo-outline.woff2 -------------------------------------------------------------------------------- /public/assets/img/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/flag.png -------------------------------------------------------------------------------- /public/assets/img/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/header.jpg -------------------------------------------------------------------------------- /public/assets/img/header3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/header3.jpg -------------------------------------------------------------------------------- /public/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/logo.png -------------------------------------------------------------------------------- /public/assets/img/snow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/snow.jpg -------------------------------------------------------------------------------- /public/assets/img/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/ss.png -------------------------------------------------------------------------------- /public/assets/img/ssr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/assets/img/ssr.png -------------------------------------------------------------------------------- /public/assets/js/left_bar.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // Sidebar Toggler 4 | function sidebarToggle(toogle) { 5 | var sidebar = $('#sidebar'); 6 | var padder = $('.content-padder'); 7 | if( toogle ) { 8 | $('.notyf').removeAttr( 'style' ); 9 | sidebar.css({'display': 'block', 'x': -300}); 10 | sidebar.transition({opacity: 1, x: 0}, 250, 'in-out', function(){ 11 | sidebar.css('display', 'block'); 12 | }); 13 | if( $( window ).width() > 960 ) { 14 | padder.transition({marginLeft: sidebar.css('width')}, 250, 'in-out'); 15 | } 16 | } else { 17 | $('.notyf').css({width: '90%', margin: '0 auto', display:'block', right: 0, left: 0}); 18 | sidebar.css({'display': 'block', 'x': '0px'}); 19 | sidebar.transition({x: -300, opacity: 0}, 250, 'in-out', function(){ 20 | sidebar.css('display', 'none'); 21 | }); 22 | padder.transition({marginLeft: 0}, 250, 'in-out'); 23 | } 24 | } 25 | 26 | $('#sidebar_toggle').click(function() { 27 | var sidebar = $('#sidebar'); 28 | var padder = $('.content-padder'); 29 | if( sidebar.css('x') == '-300px' || sidebar.css('display') == 'none' ) { 30 | sidebarToggle(true) 31 | } else { 32 | sidebarToggle(false) 33 | } 34 | }); 35 | 36 | function resize() 37 | { 38 | var sidebar = $('#sidebar'); 39 | var padder = $('.content-padder'); 40 | padder.removeAttr( 'style' ); 41 | if( $( window ).width() < 960 && sidebar.css('display') == 'block' ) { 42 | sidebarToggle(false); 43 | } else if( $( window ).width() > 960 && sidebar.css('display') == 'none' ) { 44 | sidebarToggle(true); 45 | } 46 | } 47 | 48 | if($( window ).width() < 960) { 49 | sidebarToggle(false); 50 | } 51 | 52 | $( window ).resize(function() { 53 | resize() 54 | }); 55 | 56 | $('.content-padder').click(function() { 57 | if( $( window ).width() < 960 ) { 58 | sidebarToggle(false); 59 | } 60 | }); 61 | 62 | }) 63 | -------------------------------------------------------------------------------- /public/downloads/SSEncrypt.module: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/downloads/SSEncrypt.module -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 11 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /user 3 | Disallow: /admin 4 | Disallow: /api 5 | -------------------------------------------------------------------------------- /resources/lang/en/admin-nav.php: -------------------------------------------------------------------------------- 1 | 'Nodes', 5 | "config" => "Config", 6 | "users" => 'Users', 7 | 'mail-setting' => 'Mail Setting', 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/en/admin.php: -------------------------------------------------------------------------------- 1 | 'Add', 5 | 'delete' => 'Delete', 6 | 'action' => 'Action', 7 | 'node-add' => 'Add Node', 8 | 9 | 'num' => 'Number', 10 | 'prefix' => 'Prefix', 11 | 12 | 'home-message' => 'Home Message', 13 | 14 | 15 | 'users-total' => 'Total Users', 16 | 'nodes-total' => 'Total Nodes', 17 | 'traffic-total' => 'Total Traffic', 18 | 19 | 'mailgun-key' => 'Mailgun Key', 20 | 'mailgun-domain' => 'Mailgun Domain', 21 | 'mailgun-sender' => 'Mailgun Sender', 22 | ]; -------------------------------------------------------------------------------- /resources/lang/en/alert.php: -------------------------------------------------------------------------------- 1 | 'Success', 5 | 'error' => 'Whoops', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'Login', 6 | 'register' => 'Register', 7 | 'logout' => 'Logout', 8 | 'email' => 'Email', 9 | 'current-password' => 'Current Password', 10 | 'password' => 'Password', 11 | 'new-password' => 'New Password', 12 | 'password-repeat' => 'Repeat Password', 13 | 'update-password' => 'Update Password', 14 | 'remember-me' => 'Remember Me', 15 | 'forgot-password' => 'Forgot Password', 16 | 'send-reset-email' => 'Send Reset Mail', 17 | 'reset-password' => 'Reset Password', 18 | 'email-no-exist' => 'Email no exist', 19 | 'link-expired' => 'Link is expired', 20 | 21 | 'activation-code' => 'Activation Code', 22 | 'send-code' => 'Send Code', 23 | 24 | 'register-be-a-cat' => 'Register and Be a Cat!', 25 | 'create-account' => 'Create Account', 26 | 'username' => 'Username', 27 | 28 | 'login-fail' => "Invalid Username or Password!", 29 | 'current-password-wrong' => 'Current Password Wrong', 30 | 'password-repeat-wrong' => 'Password does not match the confirm password.', 31 | 32 | 'invite-code-invalid' => 'Invite Code Invalid', 33 | 'email-invalid' => 'Email Invalid', 34 | 'password-too-short' => 'Password Too Short', 35 | 'email-used' => 'Email Used', 36 | ]; 37 | -------------------------------------------------------------------------------- /resources/lang/en/base.php: -------------------------------------------------------------------------------- 1 | "Success", 5 | "error" => "Error", 6 | "something-wrong" => "Some Wrong!", 7 | "system-error" => "System Error", 8 | 9 | 'setting' => 'Setting', 10 | 'update' => 'Update', 11 | 12 | 'never' => "Never", 13 | 14 | 'enable' => 'Enable', 15 | 'disable' => 'Disable', 16 | 17 | 'app-name' => 'App Name', 18 | 'app-uri' => 'Site Url', 19 | 'checkInMax' => 'Check-In Max Traffic', 20 | 'checkInMin' => 'Check-In Min Traffic', 21 | 'checkInTime' => 'Check-In Time Interval', 22 | 'hour' => 'Hour', 23 | 'default-traffic' => 'Default Traffic', 24 | 'default-invite-num' => 'Default Invite Number', 25 | 26 | 'lang' => 'Language', 27 | 'mu-key' => 'MU API Key', 28 | ]; 29 | -------------------------------------------------------------------------------- /resources/lang/en/index.php: -------------------------------------------------------------------------------- 1 | 'Sign Up Now', 5 | 'info' => 'over the wall!', 6 | 'enter-user-center' => 'User Center', 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/lang/en/nav.php: -------------------------------------------------------------------------------- 1 | 'Home', 5 | 'user-center' => 'User Center', 6 | 'download' => 'Download', 7 | 'invite-code' => 'Invite Code', 8 | 9 | 'pages' => 'Pages', 10 | 'about' => 'About', 11 | 'about-des' => "A way to share shadowsocks.", 12 | 'user' => 'User', 13 | 'tos' => 'TOS', 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/lang/en/ss.php: -------------------------------------------------------------------------------- 1 | 'Shadowsocks', 5 | // Node Info 6 | 'node_name' => 'Node Name', 7 | 'node_info' => 'Node Info', 8 | 'server_addr' => "Server Addr", 9 | 'traffic_rate' => "Traffic Rate", 10 | 'online_count' => "Online Count", 11 | 'traffic_total' => "Traffic Total", 12 | 'traffic_used' => 'Traffic Used', 13 | 'traffic_unused' => 'Unused Traffic', 14 | 'no_data' => "No Data", 15 | 16 | // Base Info 17 | 'node' => 'Node', 18 | 'port' => 'Port', 19 | 'password' => 'Password', 20 | 'method' => 'Method', 21 | 22 | // SSR 23 | 'obfs-protocol' => 'obfs Protocol', 24 | 'obfs-plugin' => 'obfs Plugin', 25 | 'obfs_param' => "obfs Param", 26 | 27 | // V2ray 28 | 'v2ray' => 'V2ray', 29 | 'v2ray-protocol' => 'V2ray Protocol', 30 | 'v2ray-port' => 'V2ray Port', 31 | 'v2ray-uuid' => 'V2ray UUID', 32 | 'v2ray-alter-id' => 'V2ray alterId', 33 | ]; -------------------------------------------------------------------------------- /resources/lang/en/user-index.php: -------------------------------------------------------------------------------- 1 | 'Announcement', 6 | 'faq' => 'FAQ', 7 | 8 | 'checkin' => 'Check-In', 9 | 'checkin-des' => "You can checkin in each %s hour.", 10 | 'cant-checkin' => "Cant't Check-In", 11 | 'last-checkin-at' => 'Last check-in at', 12 | 'traffic-got' => 'Your got ', 13 | 14 | 'traffic-info' => 'Traffic Info', 15 | 'traffic-total' => 'Total Traffic', 16 | 'traffic-used' => 'Used Traffic', 17 | 'traffic-unused' => 'Unused Traffic', 18 | 19 | 'connection-info' => 'Connection Info', 20 | 21 | 'last-used' => 'Last used at', 22 | 23 | 'gen-invite-code' => 'Generate invitation code', 24 | ]; 25 | -------------------------------------------------------------------------------- /resources/lang/en/user-nav.php: -------------------------------------------------------------------------------- 1 | 'Dashboard', 5 | 'user-home' => 'User Center', 6 | 'node-list' => 'Node List', 7 | 'my-profile' => 'My Profile', 8 | 'traffic-log' => 'Traffic Log', 9 | 'edit-profile' => 'Edit Profile', 10 | 'invite-friend' => 'Invite Friend', 11 | 'admin-panel' => 'Admin Panel', 12 | 13 | 'join-at' => 'Join At', 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/admin-nav.php: -------------------------------------------------------------------------------- 1 | '节点', 5 | "config" => "配置", 6 | "users" => '用户', 7 | 'mail-setting' => '邮件配置', 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/admin.php: -------------------------------------------------------------------------------- 1 | '添加', 5 | 'delete' => '删除', 6 | 'action' => '操作', 7 | 'node-add' => '添加节点', 8 | 9 | 'num' => '数量', 10 | 'prefix' => '前缀', 11 | 12 | 'home-message' => '首页消息公告', 13 | 14 | 'users-total' => '总用户', 15 | 'nodes-total' => '节点数量', 16 | 'traffic-total' => '产生流量', 17 | ]; -------------------------------------------------------------------------------- /resources/lang/zh_cn/alert.php: -------------------------------------------------------------------------------- 1 | '成功!', 5 | 'error' => '出错了!', 6 | ]; 7 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/auth.php: -------------------------------------------------------------------------------- 1 | '登陆', 6 | 'register' => '注册', 7 | 'logout' => '退出', 8 | 'email' => '邮箱', 9 | 'current-password' => '当前密码', 10 | 'password' => '密码', 11 | 'new-password' => '新密码', 12 | 'password-repeat' => '重复密码', 13 | 'update-password' => '更新密码', 14 | 'remember-me' => '记住我', 15 | 'forgot-password' => '忘记密码', 16 | 'send-reset-email' => '发送重置邮件', 17 | 'reset-password' => '重置密码', 18 | 'email-no-exist' => '邮箱不存在', 19 | 'link-expired' => '链接已经失效', 20 | 21 | 'activation-code' => '激活码', 22 | 'send-code' => '发送激活码', 23 | 24 | 'register-be-a-cat' => '注册,然后变成一只猫', 25 | 'create-account' => '创建账户', 26 | 'accept-tos-register' => '接受用户协议并注册', 27 | 'username' => '用户名', 28 | 29 | 'login-fail' => "用户名或密码错误!", 30 | 'current-password-wrong' => '当前密码错误', 31 | 'password-repeat-wrong' => '两次密码输入不一致', 32 | 33 | 'invite-code-invalid' => '邀请码错误', 34 | 'email-invalid' => '邮箱不合法', 35 | 'password-too-short' => '密码太短', 36 | 'email-used' => '邮箱已经被使用', 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/base.php: -------------------------------------------------------------------------------- 1 | "成功", 5 | "error" => "错误", 6 | "something-wrong" => "有东西出错了", 7 | "system-error" => "系统错误", 8 | 9 | 'setting' => "设置", 10 | 'update' => "更新", 11 | 12 | 'never' => "从不", 13 | 14 | 'enable' => '启用', 15 | 'disable' => '关闭', 16 | 17 | 'app-name' => '站点名', 18 | 'app-uri' => '网站地址', 19 | 'checkInMax' => '签到最多流量', 20 | 'checkInMin' => '签到最少流量', 21 | 'checkInTime' => '2次签到时间间隔', 22 | 'hour' => '小时', 23 | 'default-traffic' => '用户初始流量', 24 | 'default-invite-num' => '用户初始邀请码数量', 25 | 26 | 'lang' => '语言', 27 | 'mu-key' => 'MU API Key', 28 | ]; 29 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/index.php: -------------------------------------------------------------------------------- 1 | '立即注册', 5 | 'info' => 'over the wall!', 6 | 'enter-user-center' => '用户中心', 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/nav.php: -------------------------------------------------------------------------------- 1 | '主页', 5 | 'user-center' => '用户中心', 6 | 'download' => '下载', 7 | 'invite-code' => '邀请码', 8 | 9 | 'pages' => '页面', 10 | 'about' => '关于', 11 | 'about-des' => "A way to share shadowsocks.", 12 | 'user' => '用户', 13 | 'tos' => 'TOS', 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/ss.php: -------------------------------------------------------------------------------- 1 | 'Shadowsocks', 5 | // Node Info 6 | 'node_name' => '节点名字', 7 | 'node_info' => '节点信息', 8 | 'server_addr' => "节点地址", 9 | 'traffic_rate' => "流量费率", 10 | 'online_count' => "在线数", 11 | 'traffic_total' => "总流量", 12 | 'traffic_used' => '已用流量', 13 | 'traffic_unused' => '剩余流量', 14 | 'no_data' => "无数据", 15 | 16 | // Base Info 17 | 'node' => '节点', 18 | 'port' => '端口', 19 | 'password' => '密码', 20 | 'method' => '加密方式', 21 | 22 | // SSR 23 | 'obfs-protocol' => 'obfs Protocol', 24 | 'obfs-plugin' => 'obfs Plugin', 25 | 'obfs_param' => "obfs Param", 26 | ]; -------------------------------------------------------------------------------- /resources/lang/zh_cn/user-index.php: -------------------------------------------------------------------------------- 1 | '公告', 6 | 'faq' => '常见问题', 7 | 8 | 'checkin' => '签到', 9 | 'checkin-des' => "每 %s 小时可以签到一次。", 10 | 'cant-checkin' => "无法签到", 11 | 'last-checkin-at' => '上次签到时间', 12 | 'traffic-got' => '你获得了 ', 13 | 14 | 'traffic-info' => '流量使用情况', 15 | 'traffic-total' => '总流量', 16 | 'traffic-used' => '已用流量', 17 | 'traffic-unused' => '剩余流量', 18 | 19 | 'connection-info' => '链接信息', 20 | 21 | 'last-used' => '上次使用于', 22 | 'gen-invite-code' => '生成邀请码', 23 | ]; 24 | -------------------------------------------------------------------------------- /resources/lang/zh_cn/user-nav.php: -------------------------------------------------------------------------------- 1 | '控制面板', 5 | 'user-home' => '用户中心', 6 | 'node-list' => '节点列表', 7 | 'my-profile' => '个人信息', 8 | 'traffic-log' => '流量日志', 9 | 'edit-profile' => '编辑', 10 | 'invite-friend' => '邀请好友', 11 | 'admin-panel' => '管理面板', 12 | 13 | 'join-at' => '加入于', 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/views/default/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{db_config('appName')}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/views/default/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{db_config('appName')}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/views/default/email/auth/verify.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

尊敬的用户:

8 |

您的邮箱验证代码为: {$verification->token},请在网页中填写,完成验证。
(本验证代码有效期 {$ttl} 分钟)


9 |

{$config["appName"]}

10 | 11 | -------------------------------------------------------------------------------- /resources/views/default/email/test.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

This is a testing mail.

8 |

Send from {$config["appName"]}

9 |

{$time}

10 | 11 | -------------------------------------------------------------------------------- /resources/views/default/error/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 500 Internal Server Error 9 | 10 | 11 | 12 | 44 | 50 | 51 | 52 | 53 |
54 | 55 |
56 |

500 Internal Server Error

57 |

The web server is returning an internal error for .

58 | Try This Page Again 59 |
60 |
61 |
62 |
63 |
64 |
65 |

What happened?

66 |

A 500 error status implies there is a problem with the web server's software causing it to malfunction.

67 |
68 |
69 |

What can I do?

70 |

If you're a site visitor

71 |

Nothing you can do at the moment. If you need immediate assistance, please send us an email instead. We apologize for any inconvenience.

72 |

If you're the site owner

73 |

This error can only be fixed by server admins, please contact your website provider.

74 |
75 |
76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/views/default/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{db_config('appName')}} 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | php -S localhost:8000 -t public -------------------------------------------------------------------------------- /src/admin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import VueI18n from 'vue-i18n' 4 | import Admin from './Admin.vue' 5 | import router from './adminRouter' 6 | import store from './store/' 7 | import {Locales} from './lang' 8 | 9 | 10 | Vue.use(VueRouter); 11 | Vue.use(VueI18n); 12 | 13 | Vue.config.lang = store.state.lang; 14 | 15 | Object.keys(Locales).forEach(function (lang) { 16 | Vue.locale(lang, Locales[lang]); 17 | }); 18 | 19 | let lang = store.state.lang; 20 | 21 | // Ready translated locale messages 22 | // Create VueI18n instance with options 23 | // const i18n = new VueI18n({ 24 | // fallback: 'en', 25 | // locale: lang, // set locale 26 | // messages: Locales, // set locale messages 27 | // }); 28 | 29 | window.App = new Vue({ 30 | router, 31 | store, 32 | // i18n, 33 | el: '#app', 34 | extends: Admin, 35 | data: () => ({ 36 | ids: {}, 37 | page: false, 38 | component: false 39 | }) 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /src/adminRouter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | // Pages 5 | import Index from './pages/Admin/Index.vue' 6 | import Config from './pages/Admin/Config.vue' 7 | import Node from './pages/Admin/Node.vue' 8 | import NodeAdd from './pages/Admin/NodeAdd.vue' 9 | import User from './pages/Admin/User.vue' 10 | import Invite from './pages/Admin/Invite.vue' 11 | import InviteAdd from './pages/Admin/InviteAdd.vue' 12 | import TrafficLog from './pages/Admin/TrafficLog.vue' 13 | import Mail from './pages/Admin/Mail.vue' 14 | 15 | import Logout from './pages/Auth/Logout.vue' 16 | import Setting from './pages/Setting.vue' 17 | 18 | const routes = [ 19 | // Base 20 | {path: '/logout', name: 'logout', component: Logout}, 21 | {path: '/setting', name: 'setting', component: Setting}, 22 | 23 | {path: '/admin', name: 'index', component: Index}, 24 | {path: '/admin/config', name: 'config', component: Config}, 25 | {path: '/admin/mail', name: 'mail', component: Mail}, 26 | {path: '/admin/nodes', name: 'nodes', component: Node}, 27 | {path: '/admin/nodes/create', name: 'node-add', component: NodeAdd}, 28 | {path: '/admin/invites', name: 'invites', component: Invite}, 29 | {path: '/admin/invites/create', name: 'invite-add', component: InviteAdd}, 30 | {path: '/admin/trafficLogs', name: 'trafficLogs', component: TrafficLog}, 31 | {path: '/admin/users', name: 'users', component: User}, 32 | ]; 33 | 34 | const router = new VueRouter({ 35 | routes, 36 | mode: 'history', 37 | history: true, 38 | linkActiveClass: 'active' 39 | }); 40 | 41 | export default router; 42 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import VueI18n from 'vue-i18n' 4 | import App from './App.vue' 5 | import router from './router' 6 | import store from './store/' 7 | import {Locales} from './lang' 8 | 9 | window.$ = window.jQuery = require('jquery'); 10 | 11 | Vue.use(VueRouter); 12 | Vue.use(VueI18n); 13 | 14 | Vue.config.lang = store.state.lang; 15 | 16 | Object.keys(Locales).forEach(function (lang) { 17 | Vue.locale(lang, Locales[lang]); 18 | }); 19 | 20 | let lang = store.state.lang; 21 | 22 | // Ready translated locale messages 23 | // Create VueI18n instance with options 24 | // const i18n = new VueI18n({ 25 | // fallback: 'en', 26 | // locale: lang, // set locale 27 | // messages: Locales, // set locale messages 28 | // }); 29 | 30 | window.App = new Vue({ 31 | router, 32 | store, 33 | // i18n, 34 | el: '#app', 35 | extends: App, 36 | data: () => ({ 37 | ids: {}, 38 | page: false, 39 | component: false 40 | }) 41 | }); 42 | 43 | -------------------------------------------------------------------------------- /src/code/auth.js: -------------------------------------------------------------------------------- 1 | export const UserNotExists = 601; 2 | export const PasswordWrong = 602; 3 | 4 | export const CurrentPasswordWrong = 102; 5 | export const NewPasswordRepeatWrong = 103; 6 | 7 | // Register 8 | export const InviteCodeWrong = 801; 9 | export const EmailWrong = 802; 10 | export const PasswordTooShort = 803; 11 | export const EmailUsed = 804; 12 | 13 | // Password 14 | export const EmailNotExist = 901; 15 | export const LinkExpired = 902; -------------------------------------------------------------------------------- /src/components/Lang.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /src/home.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import VueI18n from 'vue-i18n' 4 | import Home from './Home.vue' 5 | import router from './homeRouter' 6 | import store from './store/' 7 | import {Locales} from './lang' 8 | 9 | window.$ = window.jQuery = require('jquery'); 10 | 11 | Vue.use(VueRouter); 12 | Vue.use(VueI18n); 13 | 14 | Vue.config.lang = store.state.lang; 15 | 16 | Object.keys(Locales).forEach(function (lang) { 17 | Vue.locale(lang, Locales[lang]); 18 | }); 19 | 20 | window.App = new Vue({ 21 | router, 22 | store, 23 | // i18n, 24 | el: '#app', 25 | extends: Home, 26 | data: () => ({ 27 | ids: {}, 28 | page: false, 29 | component: false 30 | }) 31 | }); -------------------------------------------------------------------------------- /src/homeRouter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Index from './pages/Index.vue' 4 | import Code from './pages/Code.vue' 5 | 6 | // Auth 7 | import Login from './pages/Auth/Login.vue' 8 | import Register from './pages/Auth/Register.vue' 9 | import Logout from './pages/Auth/Logout.vue' 10 | 11 | // Password 12 | import Password from './pages/Password.vue' 13 | import PasswordToken from './pages/PasswordToken.vue' 14 | 15 | 16 | const routes = [ 17 | {path: '/', name: 'index', component: Index}, 18 | {path: '/code', name: 'code', component: Code}, 19 | 20 | // Auth 21 | {path: '/auth/login', name: 'login', component: Login}, 22 | {path: '/auth/register', name: 'register', component: Register}, 23 | {path: '/auth/register/:code', name: 'registerWithCode', component: Register}, 24 | {path: '/logout', name: 'logout', component: Logout}, 25 | 26 | // Password 27 | {path: '/password', name: 'password-reset', component: Password}, 28 | {path: '/password/:token', name: 'password-token', component: PasswordToken}, 29 | 30 | ]; 31 | 32 | const router = new VueRouter({ 33 | routes, 34 | mode: 'history', 35 | history: true, 36 | linkActiveClass: 'active' 37 | }); 38 | 39 | export default router; 40 | 41 | -------------------------------------------------------------------------------- /src/http/admin.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import store from '../store' 3 | 4 | const id = sessionStorage.getItem('id'); 5 | const token = sessionStorage.getItem('token'); 6 | 7 | export default axios.create({ 8 | baseURL: `/api/admin/`, 9 | headers: { 10 | Authorization: 'Bearer ' + token, 11 | } 12 | }); -------------------------------------------------------------------------------- /src/http/base.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import store from '../store' 3 | 4 | const id = sessionStorage.getItem('id'); 5 | const token = sessionStorage.getItem('token'); 6 | 7 | export default axios.create({ 8 | baseURL: `/api/`, 9 | headers: { 10 | Authorization: 'Bearer ' + token, 11 | } 12 | }); -------------------------------------------------------------------------------- /src/http/rest.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import store from '../store' 3 | 4 | const id = sessionStorage.getItem('id'); 5 | const token = sessionStorage.getItem('token'); 6 | 7 | export default axios.create({ 8 | baseURL: `/api/users/` + id + `/`, 9 | headers: { 10 | Authorization: 'Bearer ' + token, 11 | } 12 | }); -------------------------------------------------------------------------------- /src/http/user.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | class User { 4 | constructor(id, token) { 5 | this.id = id; 6 | this.token = token; 7 | } 8 | 9 | nodes() { 10 | 11 | } 12 | 13 | info() { 14 | 15 | } 16 | } 17 | 18 | export default User -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | import lang from './vue-i18n-locales.generated' 2 | 3 | export const langs = { 4 | en: "🇺🇸English", 5 | zh_cn: "🇨🇳简体中文", 6 | }; 7 | 8 | export const Locales = { 9 | en: lang.en, 10 | zh_cn: lang.zh_cn, // @todo 11 | }; -------------------------------------------------------------------------------- /src/pages/Admin/Index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | -------------------------------------------------------------------------------- /src/pages/Auth/Logout.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/pages/Blank.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | -------------------------------------------------------------------------------- /src/pages/Code.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 54 | -------------------------------------------------------------------------------- /src/pages/Profile.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /src/pages/TrafficLog.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | -------------------------------------------------------------------------------- /src/res/config.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | let data; 4 | 5 | axios.get('/api/config') 6 | .then(function (response) { 7 | // console.log(response.data.data); 8 | data = response.data.data 9 | }) 10 | .catch(function (error) { 11 | console.log(error); 12 | }); 13 | 14 | export default data -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Index from './pages/Index.vue' 4 | import Code from './pages/Code.vue' 5 | 6 | // Auth 7 | import Login from './pages/Auth/Login.vue' 8 | import Logout from './pages/Auth/Logout.vue' 9 | 10 | // Dashboard 11 | import Dashboard from './pages/Dashboard.vue' 12 | import Node from './pages/Node.vue' 13 | import TrafficLog from './pages/TrafficLog.vue' 14 | import Invite from './pages/Invite.vue' 15 | import Setting from './pages/Setting.vue' 16 | import Profile from './pages/Profile.vue' 17 | 18 | const routes = [ 19 | {path: '/', name: 'index', component: Index}, 20 | {path: '/code', name: 'code', component: Code}, 21 | 22 | // Auth 23 | {path: '/auth/login', name: 'login', component: Login}, 24 | {path: '/logout', name: 'logout', component: Logout}, 25 | 26 | // Dashboard 27 | {path: '/dashboard', name: 'dashboard', component: Dashboard}, 28 | {path: '/trafficLogs', name: 'trafficLogs', component: TrafficLog}, 29 | {path: '/nodes', name: 'nodes', component: Node}, 30 | {path: '/profile', name: 'profile', component: Profile}, 31 | {path: '/invite', name: 'invite', component: Invite}, 32 | {path: '/setting', name: 'setting', component: Setting}, 33 | ]; 34 | 35 | const router = new VueRouter({ 36 | routes, 37 | mode: 'history', 38 | history: true, 39 | linkActiveClass: 'uk-active' 40 | }); 41 | 42 | export default router; 43 | 44 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import * as types from './types' 4 | 5 | Vue.use(Vuex); 6 | 7 | export default new Vuex.Store({ 8 | state: { 9 | token: null, 10 | isLogin: false, 11 | id: 0, 12 | lang: 'en', 13 | user: { 14 | data: { 15 | email: '', 16 | } 17 | }, 18 | }, 19 | mutations: { 20 | [types.Login]: (state, user) => { 21 | sessionStorage.token = user.token; 22 | sessionStorage.id = user.id; 23 | state.token = user.token; 24 | state.isLogin = true; 25 | state.id = user.id; 26 | }, 27 | [types.ChangeLocale]: (state, locale) => { 28 | state.lang = locale; 29 | Vue.config.lang = state.lang; 30 | }, 31 | [types.StoreUser]: (state, user) => { 32 | state.user = user; 33 | }, 34 | }, 35 | modules: {}, 36 | 37 | }) -------------------------------------------------------------------------------- /src/store/root.js: -------------------------------------------------------------------------------- 1 | 2 | export const state = { 3 | token: null, 4 | isLogin: false, 5 | id: 0, 6 | lang: 'en', 7 | }; -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | export const Login = "Login"; 2 | export const ChangeLocale = "ChangeLocale"; 3 | export const StoreUser = "StoreUser"; -------------------------------------------------------------------------------- /src/tools/util.js: -------------------------------------------------------------------------------- 1 | export const bytesToSize = function (bytes) { 2 | if (bytes === 0) return '0 B'; 3 | const k = 1000, // or 1024 4 | sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 5 | i = Math.floor(Math.log(bytes) / Math.log(k)); 6 | return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; 7 | }; 8 | 9 | export const timeFormat = function (t) { 10 | }; 11 | 12 | export const notify = function (message, status) { 13 | UIkit.notification({ 14 | message: message, 15 | status: status, 16 | pos: 'top-center', 17 | timeout: 5000 18 | }); 19 | }; 20 | 21 | export const getLang = function () { 22 | const lang = navigator.language || navigator.userLanguage; 23 | switch (lang) { 24 | case 'zh-CN': 25 | return 'zh_cn'; 26 | default: 27 | return 'en'; 28 | } 29 | }; -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.log -------------------------------------------------------------------------------- /storage/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orvice/ss-panel/e33533bc6eaa6db7226d19a19f66657f26778329/storage/logs/.gitkeep -------------------------------------------------------------------------------- /storage/ss-panel/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/HomeTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 11 | $this->assertEquals('200', $this->response->getStatusCode()); 12 | } 13 | 14 | public function testCode() 15 | { 16 | $this->get('/code'); 17 | $this->assertEquals('200', $this->response->getStatusCode()); 18 | } 19 | 20 | public function testTos() 21 | { 22 | $this->get('/tos'); 23 | $this->assertEquals('200', $this->response->getStatusCode()); 24 | } 25 | 26 | public function testDebug() 27 | { 28 | $this->get('/debug'); 29 | $this->assertEquals('200', $this->response->getStatusCode()); 30 | $arr = json_decode($this->response->getBody(), true); 31 | // test version 32 | $this->assertEquals(get_version(), $arr['version']); 33 | } 34 | 35 | public function testPost() 36 | { 37 | 38 | } 39 | 40 | public function testError() 41 | { 42 | $this->get('/404'); 43 | $this->assertEquals('404', $this->response->getStatusCode()); 44 | 45 | // $this->get('/500'); 46 | // $this->assertEquals('500', $this->response->getStatusCode()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Models/CheckInLogTest.php: -------------------------------------------------------------------------------- 1 | user_id = 1; 15 | $log->checkin_at = time(); 16 | $log->traffic = 1024; 17 | $log->save(); 18 | 19 | $this->assertEquals(Tools::toDateTime($log->checkin_at), $log->CheckInTime()); 20 | $this->assertEquals(Tools::flowAutoShow($log->traffic), $log->traffic()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Models/NodeTest.php: -------------------------------------------------------------------------------- 1 | id = 1; 14 | 15 | $node->getLastNodeInfoLog(); 16 | $node->getNodeUptime(); 17 | $node->getNodeLoad(); 18 | $node->getLastNodeOnlineLog(); 19 | $node->getOnlineUserCount(); 20 | $node->getTrafficFromLogs(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Models/TrafficLogTest.php: -------------------------------------------------------------------------------- 1 | user_id = $id; 22 | $traffic->u = $u; 23 | $traffic->d = $d; 24 | $traffic->node_id = $nodeId; 25 | $traffic->rate = $rate; 26 | $traffic->traffic = $totalTraffic; 27 | $traffic->log_time = time(); 28 | $this->assertEquals(true, $traffic->save()); 29 | } 30 | 31 | public function testTrafficLogMethod() 32 | { 33 | $log = TrafficLog::first(); 34 | $log->node(); 35 | $log->totalUsed(); 36 | $log->logTime(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Mu/MuTest.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete(); 22 | } 23 | 24 | /** 25 | * @return DbConfig 26 | */ 27 | public function getCfg() 28 | { 29 | return app()->make(DbConfig::class); 30 | } 31 | 32 | protected function setMuKey() 33 | { 34 | $this->getCfg()->set(self::MuKey,$this->muKey); 35 | } 36 | 37 | public function testUsers() 38 | { 39 | $this->get('/mu/users', [ 40 | 'query' => [ 41 | 'key' => $this->muKey, 42 | ], 43 | 'header' => [ 44 | 'Key' => $this->muKey, 45 | ], 46 | ]); 47 | $this->assertEquals('200', $this->response->getStatusCode()); 48 | 49 | $this->get('/mu/v2/users', [ 50 | 'query' => [ 51 | 'key' => $this->muKey, 52 | ], 53 | 'header' => [ 54 | 'Key' => $this->muKey, 55 | ], 56 | ]); 57 | $this->assertEquals('200', $this->response->getStatusCode()); 58 | } 59 | 60 | public function testLogTraffic() 61 | { 62 | $this->post('/mu/users/'.$this->uid.'/traffic', [ 63 | 'u' => $this->u, 64 | 'd' => $this->d, 65 | 'node_id' => $this->nodeId, 66 | ]); 67 | $this->assertEquals('200', $this->response->getStatusCode()); 68 | 69 | $this->post('/mu/v2/users/'.$this->uid.'/traffic', [ 70 | 'u' => $this->u, 71 | 'd' => $this->d, 72 | 'node_id' => $this->nodeId, 73 | ]); 74 | $this->assertEquals('200', $this->response->getStatusCode()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Mu/NodeTest.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete(); 18 | } 19 | 20 | public function testLogInfo() 21 | { 22 | $this->post('/mu/nodes/'.$this->id.'/info', [ 23 | 'load' => $this->load, 24 | 'uptime' => $this->uptime, 25 | ]); 26 | $this->assertEquals('200', $this->response->getStatusCode()); 27 | } 28 | 29 | public function testOnlineCount() 30 | { 31 | $this->post('/mu/nodes/'.$this->id.'/online_count', [ 32 | 'count' => $this->count, 33 | ]); 34 | $this->assertEquals('200', $this->response->getStatusCode()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/ResTest.php: -------------------------------------------------------------------------------- 1 | get('/api/captcha/id'); 10 | $this->assertEquals('200', $this->response->getStatusCode()); 11 | $this->assertEquals(' image/jpeg', $this->response->getHeaderLine('Content-Type')); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Services/FactoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(true, Logger::debug('debug')); 13 | $this->assertEquals(true, Logger::error('error')); 14 | $this->assertEquals(true, Logger::warning('error')); 15 | $this->assertEquals(true, Logger::info('error')); 16 | } 17 | 18 | public function testDbLogger() 19 | { 20 | $this->assertEquals(true, Logger::newDbLog('error', 'msg')); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Services/RedisClientTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(false, Check::isEmailLegal('illegalEmail')); 13 | $this->assertEquals(true, Check::isEmailLegal('sspanel@gmail.com')); 14 | } 15 | 16 | public function testGetRegIpLimit() 17 | { 18 | $count = Check::getIpRegCount('255.255.255.255'); 19 | $this->assertEquals(0, $count); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Utils/CookieTest.php: -------------------------------------------------------------------------------- 1 | $value, 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Utils/HashTest.php: -------------------------------------------------------------------------------- 1 | hashTest(); 16 | } 17 | 18 | 19 | public function hashTest() 20 | { 21 | $pwd = 'testPassword'; 22 | $hashPwd = Hash::passwordHash($pwd); 23 | $this->assertEquals(true, Hash::checkPassword($hashPwd, $pwd)); 24 | $this->assertEquals(false, Hash::checkPassword('', $pwd)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Utils/ToolsTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(100, Tools::flowAutoShow($traffic)); 14 | } 15 | 16 | public function testGetRandomChar() 17 | { 18 | $char = Tools::genRandomChar(8); 19 | $this->assertEquals(8, strlen($char)); 20 | } 21 | 22 | public function testGenSID() 23 | { 24 | Tools::genSID(); 25 | } 26 | 27 | public function testGenUUID() 28 | { 29 | Tools::genUUID(); 30 | } 31 | 32 | public function testGenToken() 33 | { 34 | Tools::genToken(); 35 | } 36 | 37 | public function testCheckHtml() 38 | { 39 | $legalText = 'xoxo'; 40 | $this->assertEquals($legalText, Tools::checkHtml($legalText)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix').mix; 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for your application, as well as bundling up your JS files. 11 | | 12 | */ 13 | 14 | mix.js('src/home.js', 'public/assets/js'); 15 | 16 | mix.js('src/app.js', 'public/assets/js'); 17 | 18 | mix.js('src/admin.js', 'public/assets/js'); -------------------------------------------------------------------------------- /xcat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 6 | --------------------------------------------------------------------------------