├── .gitattributes ├── storage ├── ss-panel │ └── .gitignore └── framework │ ├── views │ └── .gitignore │ └── smarty │ ├── cache │ └── .gitignore │ └── compile │ └── .gitignore ├── .gitignore ├── run.sh ├── app ├── Command │ ├── Job.php │ └── XCat.php ├── Services │ ├── Auth │ │ ├── File.php │ │ ├── Base.php │ │ ├── JwtToken.php │ │ ├── Cookie.php │ │ └── Redis.php │ ├── Token │ │ ├── Token.php │ │ ├── Base.php │ │ └── DB.php │ ├── Aws │ │ ├── Factory.php │ │ └── Client.php │ ├── Jwt.php │ ├── Auth.php │ ├── View.php │ ├── Analytics.php │ ├── Analytic.php │ ├── Factory.php │ ├── Mail.php │ ├── Boot.php │ ├── Mail │ │ ├── Mailgun.php │ │ └── Smtp.php │ ├── Slim.php │ ├── Config.php │ ├── RedisClient.php │ └── Password.php ├── Models │ ├── Token.php │ ├── PasswordReset.php │ ├── Node.php │ ├── Role.php │ ├── InviteCode.php │ ├── Model.php │ └── User.php ├── Utils │ ├── Response.php │ ├── Check.php │ ├── Cookie.php │ ├── Helper.php │ ├── Hash.php │ └── Tools.php ├── Middleware │ ├── Guest.php │ ├── Auth.php │ ├── Admin.php │ └── Api.php └── Controllers │ ├── BaseController.php │ ├── HomeController.php │ ├── AdminController.php │ ├── PasswordController.php │ ├── Admin │ ├── UserController.php │ └── NodeController.php │ ├── ApiController.php │ ├── AuthController.php │ └── UserController.php ├── public ├── favicon.ico ├── robots.txt ├── assets │ ├── public │ │ ├── img │ │ │ ├── blue.png │ │ │ └── blue@2x.png │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── css │ │ │ ├── sticky-footer-navbar.css │ │ │ ├── jumbotron-narrow.css │ │ │ └── blue.css │ │ ├── js │ │ │ ├── icheck.min.js │ │ │ └── app.min.js │ │ └── plugins │ │ │ └── slimScroll │ │ │ └── jquery.slimscroll.min.js │ └── materialize │ │ ├── font │ │ ├── Material_Icons.woff2 │ │ ├── roboto │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-Thin.ttf │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Thin.woff │ │ │ ├── Roboto-Thin.woff2 │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-Regular.woff │ │ │ └── Roboto-Regular.woff2 │ │ └── material-design-icons │ │ │ ├── Material-Design-Icons.eot │ │ │ ├── Material-Design-Icons.ttf │ │ │ ├── Material-Design-Icons.woff │ │ │ ├── Material-Design-Icons.woff2 │ │ │ └── cd-top-arrow.svg │ │ ├── css │ │ └── style.css │ │ └── js │ │ └── init.js ├── .htaccess └── index.php ├── .travis.yml ├── docker ├── shadowsocks.conf ├── config.json ├── 3line.sh ├── supervisor.conf ├── Config.py ├── mysql-init.sh ├── .env ├── default ├── Dockerfile └── Dockerfile_daocloud ├── install.sh ├── xcat ├── bootstrap.php ├── composer.json ├── README.md ├── db-user_token.sql ├── views └── default │ ├── user │ ├── sys.tpl │ ├── footer.tpl │ ├── profile.tpl │ ├── nodeinfo.tpl │ ├── node.tpl │ ├── kill.tpl │ ├── invite.tpl │ ├── index.tpl │ └── main.tpl │ ├── admin │ ├── footer.tpl │ ├── sys.tpl │ ├── index.tpl │ ├── invite.tpl │ ├── node │ │ ├── index.tpl │ │ ├── create.tpl │ │ └── edit.tpl │ ├── user │ │ └── index.tpl │ └── main.tpl │ ├── tos.tpl │ ├── auth │ ├── header.tpl │ ├── login.tpl │ └── register.tpl │ ├── footer.tpl │ ├── code.tpl │ ├── header.tpl │ ├── index.tpl │ └── password │ ├── reset.tpl │ └── token.tpl ├── LICENSE ├── .env.example ├── db-160212.sql └── config └── routes.php /.gitattributes: -------------------------------------------------------------------------------- 1 | *.tpl linguist-language=php -------------------------------------------------------------------------------- /storage/ss-panel/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .env 3 | vendor/ 4 | composer.phar -------------------------------------------------------------------------------- /storage/framework/smarty/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | php -S localhost:8000 -t public -------------------------------------------------------------------------------- /storage/framework/smarty/compile/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /app/Command/Job.php: -------------------------------------------------------------------------------- 1 | boot(); 10 | -------------------------------------------------------------------------------- /app/Models/Node.php: -------------------------------------------------------------------------------- 1 | > \$file; done" 7 | done -------------------------------------------------------------------------------- /app/Models/Model.php: -------------------------------------------------------------------------------- 1 | withStatus(302)->withHeader('Location',$to); 10 | return $newResponse; 11 | } 12 | } -------------------------------------------------------------------------------- /docker/supervisor.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:nginx] 5 | command=/usr/sbin/nginx 6 | 7 | [program:php5-fpm] 8 | command=/usr/sbin/php5-fpm 9 | 10 | [program:mysql] 11 | command=/usr/bin/mysqld_safe 12 | 13 | [program:redis-server] 14 | command=/usr/bin/redis-server -------------------------------------------------------------------------------- /app/Services/Token/Base.php: -------------------------------------------------------------------------------- 1 | 'us-west-2', 12 | 'version' => 'latest', 13 | 'DynamoDb' => [ 14 | 'region' => 'eu-central-1' 15 | ] 16 | ]); 17 | } 18 | } -------------------------------------------------------------------------------- /app/Utils/Cookie.php: -------------------------------------------------------------------------------- 1 | $value){ 10 | setcookie($key,$value,$time,'/'); 11 | } 12 | } 13 | 14 | public static function get($key){ 15 | if(isset($_COOKIE[$key])){ 16 | return $_COOKIE[$key]; 17 | } 18 | return ""; 19 | } 20 | } -------------------------------------------------------------------------------- /app/Utils/Helper.php: -------------------------------------------------------------------------------- 1 | getQueryParams(); 14 | if(!isset($params['access_token'])){ 15 | return null; 16 | } 17 | $accessToken = $params['access_token']; 18 | return $accessToken; 19 | } 20 | } -------------------------------------------------------------------------------- /app/Services/Jwt.php: -------------------------------------------------------------------------------- 1 | login($uid,$time); 20 | } 21 | 22 | public static function getUser(){ 23 | return self::getDriver()->getUser(); 24 | } 25 | 26 | public static function logout(){ 27 | self::getDriver()->logout(); 28 | } 29 | } -------------------------------------------------------------------------------- /public/assets/materialize/font/material-design-icons/cd-top-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "slim/slim": "^3.0", 4 | "illuminate/database": "*", 5 | "slim/twig-view": "~2.0", 6 | "slim/csrf": "0.5.*", 7 | "smarty/smarty": "~3.1", 8 | "mailgun/mailgun-php": "~1.7.2", 9 | "predis/predis": "~1.0", 10 | "phpmailer/phpmailer": "~5.2", 11 | "illuminate/pagination": "~5.1", 12 | "vlucas/phpdotenv": "~2.1", 13 | "zeuxisoo/slim-whoops": "0.4.*", 14 | "firebase/php-jwt": "~3.0", 15 | "aws/aws-sdk-php": "3.*" 16 | }, 17 | "autoload": { 18 | "classmap": [ 19 | 20 | ], 21 | "psr-4": { 22 | "App\\": "app/" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ss-panel 2 | 3 | Thanks: 4 | 5 | [LightFish](https://github.com/OzCat/LightFish) 6 | 7 | [shadowsocks manyuser](https://github.com/mengskysama/shadowsocks/tree/manyuser) 8 | 9 | [shadowsocks-go mu](https://github.com/orvice/shadowsocks-go/tree/mu) 10 | 11 | [ss-panel](https://github.com/orvice/ss-panel) 12 | 13 | 14 | 15 | ## About 16 | 17 | [完整版中文安装教程](https://github.com/maxidea-com/ss-panel/wiki/v3-Guide) 18 | 19 | ## Quick Start 20 | 21 | [专为懒人准备的Docker版本](https://github.com/maxidea-com/ss-panel/wiki/Docker) 22 | 23 | 24 | ## Requirements 25 | 26 | * PHP 5.5 or newer 27 | * Web server with URL rewriting 28 | * MySQL 29 | 30 | -------------------------------------------------------------------------------- /app/Middleware/Guest.php: -------------------------------------------------------------------------------- 1 | isLogin){ 15 | $newResponse = $response->withStatus(302)->withHeader('Location', '/user'); 16 | return $newResponse; 17 | } 18 | $response = $next($request, $response); 19 | return $response; 20 | } 21 | } -------------------------------------------------------------------------------- /app/Middleware/Auth.php: -------------------------------------------------------------------------------- 1 | isLogin){ 15 | $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login'); 16 | return $newResponse; 17 | } 18 | $response = $next($request, $response); 19 | return $response; 20 | } 21 | } -------------------------------------------------------------------------------- /app/Services/View.php: -------------------------------------------------------------------------------- 1 | settemplatedir(BASE_PATH.'/views/'.Config::get('theme').'/'); //设置模板文件存放目录 12 | $smarty->setcompiledir(BASE_PATH.'/storage/framework/smarty/compile/'); //设置生成文件存放目录 13 | $smarty->setcachedir(BASE_PATH.'/storage/framework/smarty/cache/'); //设置缓存文件存放目录 14 | // add config 15 | $smarty->assign('config',Config::getPublicConfig()); 16 | $smarty->assign('user',Auth::getUser()); 17 | return $smarty; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/Services/Analytics.php: -------------------------------------------------------------------------------- 1 | ',0)->count(); 15 | } 16 | 17 | public function getTrafficUsage(){ 18 | $total = User::sum('u') + USer::sum('d'); 19 | return Tools::flowAutoShow($total); 20 | } 21 | 22 | public function getOnlineUser($time){ 23 | $time = time() - $time; 24 | return User::where('t','>',$time)->count(); 25 | } 26 | } -------------------------------------------------------------------------------- /app/Services/Analytic.php: -------------------------------------------------------------------------------- 1 | count(); 13 | } 14 | 15 | public function checkinUserCount(){ 16 | return User::where('last_checkin_time','>', 1)->count(); 17 | } 18 | 19 | public function activedUserCount(){ 20 | return User::where('t','>', 1)->count(); 21 | } 22 | 23 | public function totalTraffic(){ 24 | $u = User::all()->sum('u'); 25 | $d = User::all()->sum('d'); 26 | return Tools::flowAutoShow($u + $d); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /docker/mysql-init.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | sudo service mysql restart 3 | if [ $? -eq 0 ]; then 4 | echo "mysql startup successful!" 5 | else 6 | echo "mysql startup failed!" 7 | exit 1 8 | fi 9 | 10 | echo "create ss-panel database, user, password" 11 | mysql -uroot -p'pw123456' -e "CREATE DATABASE sspanel character SET utf8; CREATE user 'ssuser'@'localhost' IDENTIFIED BY 'sspasswd'; GRANT ALL privileges ON sspanel.* TO 'ssuser'@'localhost'; FLUSH PRIVILEGES;" 12 | 13 | echo "input shadowsocks sql init database" 14 | cd /opt/shadowsocks/shadowsocks; mysql -u ssuser -psspasswd sspanel < ./shadowsocks.sql 15 | 16 | echo "input ss-panel sql into database" 17 | cd /opt/ss-panel; mysql -u ssuser -psspasswd sspanel < db-160212.sql -------------------------------------------------------------------------------- /db-user_token.sql: -------------------------------------------------------------------------------- 1 | -- Adminer 4.1.0 MySQL dump 2 | 3 | SET NAMES utf8; 4 | SET time_zone = '+00:00'; 5 | SET foreign_key_checks = 0; 6 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 7 | 8 | DROP TABLE IF EXISTS `user_token`; 9 | CREATE TABLE `user_token` ( 10 | `id` int(11) NOT NULL AUTO_INCREMENT, 11 | `token` varchar(256) NOT NULL, 12 | `user_id` int(11) NOT NULL, 13 | `create_time` int(11) NOT NULL, 14 | `expire_time` int(11) NOT NULL, 15 | PRIMARY KEY (`id`) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 17 | 18 | INSERT INTO `user_token` (`id`, `token`, `user_id`, `create_time`, `expire_time`) VALUES 19 | (14, 'ar9DfZZhN9eL1PbETj1YnmKsSgPaefMye8J5YPTfriHgrLmIL7hSGoy8O5w2IKQY', 1, 1455114361, 1455719161); 20 | 21 | -- 2016-02-10 15:38:15 22 | -------------------------------------------------------------------------------- /app/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | smarty = View::getSmarty(); 27 | return $this->smarty; 28 | } 29 | 30 | public function view(){ 31 | return $this->smarty(); 32 | } 33 | 34 | /** 35 | * @param $response 36 | * @param $res 37 | * @return mixed 38 | */ 39 | public function echoJson($response,$res){ 40 | return $response->getBody()->write(json_encode($res)); 41 | } 42 | } -------------------------------------------------------------------------------- /app/Services/Factory.php: -------------------------------------------------------------------------------- 1 | send($to,$subject,$text); 26 | case "smtp": 27 | $mail = new Smtp(); 28 | return $mail->send($to,$subject,$text); 29 | default: 30 | // @TODO default action 31 | } 32 | return true; 33 | } 34 | } -------------------------------------------------------------------------------- /views/default/user/sys.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 |
4 |
5 |

6 | 统计信息 7 | A 8 |

9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |

Coming soon...

20 |
21 |
22 |
23 |
24 |
25 |
26 | {include file='user/footer.tpl'} -------------------------------------------------------------------------------- /views/default/admin/footer.tpl: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /views/default/admin/sys.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 |
4 |
5 |

6 | 统计信息 7 | A 8 |

9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |

Coming soon...

20 |
21 |
22 |
23 |
24 |
25 |
26 | {include file='admin/footer.tpl'} -------------------------------------------------------------------------------- /views/default/user/footer.tpl: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/assets/public/css/sticky-footer-navbar.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer styles 2 | -------------------------------------------------- */ 3 | html { 4 | position: relative; 5 | min-height: 100%; 6 | } 7 | body { 8 | /* Margin bottom by footer height */ 9 | margin-bottom: 60px; 10 | } 11 | .footer { 12 | position: absolute; 13 | bottom: 0; 14 | width: 100%; 15 | /* Set the fixed height of the footer here */ 16 | height: 60px; 17 | background-color: #f5f5f5; 18 | } 19 | 20 | 21 | /* Custom page CSS 22 | -------------------------------------------------- */ 23 | /* Not required for template or sticky footer method. */ 24 | 25 | body > .container { 26 | padding: 60px 15px 0; 27 | } 28 | .container .text-muted { 29 | margin: 20px 0; 30 | } 31 | 32 | .footer > .container { 33 | padding-right: 15px; 34 | padding-left: 15px; 35 | } 36 | 37 | code { 38 | font-size: 80%; 39 | } 40 | -------------------------------------------------------------------------------- /app/Middleware/Admin.php: -------------------------------------------------------------------------------- 1 | getBody()->write('BEFORE'); 14 | $user = AuthService::getUser(); 15 | if(!$user->isLogin){ 16 | $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login'); 17 | return $newResponse; 18 | } 19 | 20 | if(!$user->isAdmin()){ 21 | $newResponse = $response->withStatus(302)->withHeader('Location', '/user'); 22 | return $newResponse; 23 | } 24 | $response = $next($request, $response); 25 | return $response; 26 | } 27 | } -------------------------------------------------------------------------------- /app/Services/Auth/JwtToken.php: -------------------------------------------------------------------------------- 1 | $uid, 15 | "expire_time" => $expireTime 16 | ]; 17 | $decode = Jwt::encode($ary); 18 | Utils\Cookie::set([ 19 | //"uid" => $uid, 20 | "token" => $decode 21 | ],$expireTime); 22 | } 23 | 24 | public function logout() 25 | { 26 | Utils\Cookie::set([ 27 | //"uid" => $uid, 28 | "token" => "" 29 | ],time()-3600); 30 | } 31 | 32 | public function getUser() 33 | { 34 | $token = Utils\Cookie::get('token'); 35 | $tokenInfo = Jwt::decodeArray($token); 36 | } 37 | } -------------------------------------------------------------------------------- /app/Services/Boot.php: -------------------------------------------------------------------------------- 1 | load(); 14 | } 15 | 16 | public static function setDebug(){ 17 | // debug 18 | if (Config::get('debug') == "true" ){ 19 | define("DEBUG",true); 20 | } 21 | } 22 | 23 | public static function setVersion($version){ 24 | $_ENV['version'] = $version; 25 | } 26 | 27 | public static function setTimezone(){ 28 | // config time zone 29 | date_default_timezone_set(Config::get('timeZone')); 30 | } 31 | 32 | public static function bootDb(){ 33 | // Init Eloquent ORM Connection 34 | $capsule = new Capsule; 35 | $capsule->addConnection(Config::getDbConfig()); 36 | $capsule->bootEloquent(); 37 | } 38 | } -------------------------------------------------------------------------------- /app/Services/Mail/Mailgun.php: -------------------------------------------------------------------------------- 1 | config = $this->getConfig(); 14 | $this->mg = new MailgunService($this->config["key"]); 15 | $this->domain = $this->config["domain"]; 16 | $this->sender = $this->config["sender"]; 17 | } 18 | 19 | public function getConfig(){ 20 | return [ 21 | "key" => Config::get('mailgun_key'), 22 | "domain" => Config::get('mailgun_domain'), 23 | "sender" => Config::get('mailgun_sender') 24 | ]; 25 | } 26 | 27 | public function send($to,$subject,$text){ 28 | $this->mg->sendMessage($this->domain, array('from' => $this->sender, 29 | 'to' => $to, 30 | 'subject' => $subject, 31 | 'text' => $text)); 32 | } 33 | } -------------------------------------------------------------------------------- /views/default/user/profile.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 |
4 |
5 |

6 | 个人资料 7 | Profile 8 |

9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |

用户名:{$user->user_name}

20 |

邮箱:{$user->email}

21 |

删除我的账户

22 |
23 |
24 |
25 |
26 |
27 |
28 | {include file='user/footer.tpl'} -------------------------------------------------------------------------------- /app/Services/Slim.php: -------------------------------------------------------------------------------- 1 | [ 32 | 'debug' => $debug, 33 | 'whoops.editor' => 'sublime' 34 | ] 35 | ]); 36 | $app->add(new WhoopsMiddleware); 37 | $this->app = $app; 38 | } 39 | 40 | public function run(){ 41 | $this->app->run(); 42 | } 43 | } -------------------------------------------------------------------------------- /app/Services/Config.php: -------------------------------------------------------------------------------- 1 | self::get("appName"), 15 | "version" => self::get("version"), 16 | "baseUrl" => self::get("baseUrl"), 17 | "checkinTime" => self::get("checkinTime") 18 | ]; 19 | } 20 | 21 | public static function getDbConfig(){ 22 | return [ 23 | 'driver' => self::get('db_driver'), 24 | 'host' => self::get('db_host'), 25 | 'database' => self::get('db_database'), 26 | 'username' => self::get('db_username'), 27 | 'password' => self::get('db_password'), 28 | 'charset' => self::get('db_charset'), 29 | 'collation' => self::get('db_collation'), 30 | 'prefix' => self::get('db_prefix') 31 | ]; 32 | } 33 | } -------------------------------------------------------------------------------- /app/Services/RedisClient.php: -------------------------------------------------------------------------------- 1 | Config::get('redis_scheme'), 16 | 'host' => Config::get('redis_host'), 17 | 'port' => Config::get('redis_port'), 18 | 'database' => Config::get('redis_database'), 19 | ]; 20 | $this->client = new Client($config); 21 | 22 | } 23 | 24 | public function getClient(){ 25 | return $this->client; 26 | } 27 | 28 | public function get($key){ 29 | return $this->client->get($key); 30 | } 31 | 32 | public function set($key,$value){ 33 | $this->client->set($key,$value); 34 | 35 | } 36 | 37 | public function setex($key,$time,$value){ 38 | $this->client->setex($key,$time,$value); 39 | } 40 | 41 | public function del($key){ 42 | $this->client->del($key); 43 | } 44 | } -------------------------------------------------------------------------------- /views/default/tos.tpl: -------------------------------------------------------------------------------- 1 | {include file='header.tpl'} 2 | 3 |
4 |
5 | 8 |

{$config["appName"]},以下简称本站。

9 |

隐私

10 |

11 |

15 |

16 | 17 |

使用条款

18 |

19 |

26 |

27 | 28 |

其它

29 |

30 |

34 |

35 | 36 |
37 |
38 | {include file='footer.tpl'} -------------------------------------------------------------------------------- /app/Services/Password.php: -------------------------------------------------------------------------------- 1 | email = $email; 22 | $pwdRst->init_time = time(); 23 | $pwdRst->expire_time = time() + 3600*24; // @todo 24 | $pwdRst->token = Tools::genRandomChar(64); 25 | if(!$pwdRst->save()){ 26 | return false; 27 | } 28 | $subject = Config::get('appName')."重置密码"; 29 | $text = '请访问此链接申请重置密码'.Config::get('baseUrl')."/password/token/".$pwdRst->token; 30 | try{ 31 | Mail::send($email,$subject,$text); 32 | }catch (Exception $e){ 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | public static function resetBy($token,$password){ 39 | 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /views/default/auth/header.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$config['appName']} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Middleware/Api.php: -------------------------------------------------------------------------------- 1 | getBody()->write(json_encode($res)); 20 | return $response; 21 | } 22 | $storage = Factory::createTokenStorage(); 23 | $token = $storage->get($accessToken); 24 | if ($token==null){ 25 | $res['ret'] = 0; 26 | $res['msg'] = "token is null"; 27 | $response->getBody()->write(json_encode($res)); 28 | return $response; 29 | } 30 | if ($token->expireTime < time()){ 31 | $res['ret'] = 0; 32 | $res['msg'] = "token is expire"; 33 | $response->getBody()->write(json_encode($res)); 34 | return $response; 35 | } 36 | $response = $next($request, $response); 37 | return $response; 38 | } 39 | } -------------------------------------------------------------------------------- /app/Utils/Hash.php: -------------------------------------------------------------------------------- 1 | view()->display('index.tpl'); 19 | } 20 | 21 | public function code() 22 | { 23 | $codes = InviteCode::where('user_id','=','0')->take(10)->get(); 24 | return $this->view()->assign('codes',$codes)->display('code.tpl'); 25 | } 26 | 27 | public function down(){ 28 | 29 | } 30 | 31 | public function tos(){ 32 | return $this->view()->display('tos.tpl'); 33 | } 34 | 35 | public function doCheckin($request, $response, $args){ 36 | $user = Auth::getUser(); 37 | //权限检查 38 | if(!$user->isAbleToCheckin()){ 39 | $tranferToAdd = 0; 40 | $res['msg'] = "签到过了哦"; 41 | return $response->getBody()->write(json_encode($res)); 42 | } 43 | $tranferToAdd = rand(Config::get('checkinMin'),Config::get('checkinMax')); 44 | // Add transfer 45 | $user->addTraffic($tranferToAdd); 46 | $res['msg'] = "获得了".$tranferToAdd."MB流量"; 47 | return $response->getBody()->write(json_encode($res)); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/Services/Token/DB.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 | return false; 22 | } 23 | 24 | function delete($token) 25 | { 26 | $token = TokenModel::where('token',$token)->first(); 27 | if ($token == null){ 28 | return false; 29 | } 30 | $token->delete(); 31 | return true; 32 | } 33 | 34 | function get($token) 35 | { 36 | try{ 37 | $tokenModel = TokenModel::where('token',$token)->firstOrFail(); 38 | } catch(ModelNotFoundException $e){ 39 | return null; 40 | } 41 | $token = new Token(); 42 | $token->token = $tokenModel->token; 43 | $token->userId = $tokenModel->user_id; 44 | $token->createTime = $tokenModel->create_time; 45 | $token->expireTime = $tokenModel->expire_time; 46 | return $token; 47 | } 48 | } -------------------------------------------------------------------------------- /views/default/admin/index.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 管理面板 9 | Admin Control 10 |

11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |

站点统计

21 |
22 |
23 |

总用户: {$sts->getTotalUser()}

24 |

签到用户: {$sts->getCheckinUser()}

25 |

产生流量: {$sts->getTrafficUsage()}

26 |

1小时在线用户: {$sts->getOnlineUser(3600)}

27 |
28 |
29 |
30 | 31 | 32 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | {include file='admin/footer.tpl'} -------------------------------------------------------------------------------- /app/Services/Auth/Cookie.php: -------------------------------------------------------------------------------- 1 | pass); 15 | Utils\Cookie::set([ 16 | "uid" => $uid, 17 | "email" => $user->email, 18 | "key" => $key 19 | ],$time+time()); 20 | } 21 | 22 | public function getUser(){ 23 | $uid = Utils\Cookie::get('uid'); 24 | $key = Utils\Cookie::get('key'); 25 | if ($uid == null){ 26 | $user = new User(); 27 | $user->isLogin = false; 28 | return $user; 29 | } 30 | 31 | $user = User::find($uid); 32 | if ($user == null){ 33 | $user = new User(); 34 | $user->isLogin = false; 35 | return $user; 36 | } 37 | 38 | if (Hash::cookieHash($user->pass) != $key){ 39 | $user->isLogin = false; 40 | return $user; 41 | } 42 | $user->isLogin = true; 43 | return $user; 44 | } 45 | 46 | public function logout(){ 47 | $time = time() - 1000; 48 | Utils\Cookie::set([ 49 | "uid" => null, 50 | "email" => null, 51 | "key" => null 52 | ],$time); 53 | } 54 | } -------------------------------------------------------------------------------- /views/default/footer.tpl: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | // ss-panel v3 配置 2 | // 3 | // !!! 修改此key为随机字符串确保网站安全 !!! 4 | key = 'randomKey' 5 | debug = 'false' // 正式环境请确保为false 6 | appName = 'ss-panel3' //站点名称 7 | baseUrl = 'https://www.google.com' // 站点地址 8 | timeZone = 'PRC' // RPC 天朝时间 UTC 格林时间 9 | pwdMethod = 'md5' // 密码加密 可选 md5,sha256 10 | salt = '' // 密码加密用,从旧版升级请留空 11 | theme = 'default' // 主题 12 | authDriver = 'redis' // 登录验证存储方式,推荐使用Redis 可选: cookie,redis 13 | sessionDriver = 'redis' 14 | cacheDriver = 'redis' 15 | 16 | // 邮件 17 | mailDriver = 'mailgun' // mailgun or smtp 18 | 19 | // 用户签到设置 20 | checkinTime = '22' // 签到间隔时间 单位小时 21 | checkinMin = '93' // 签到最少流量 单位MB 22 | checkinMax = '97' // 签到最多流量 23 | 24 | // 25 | defaultTraffic = '5' // 用户初始流量 单位GB 26 | 27 | // 注册后获得的邀请码数量 28 | inviteNum = '5' 29 | 30 | # database 数据库配置 31 | db_driver = 'mysql' 32 | db_host = 'localhost' 33 | db_database = 'sspanel' 34 | db_username = 'sspanel' 35 | db_password = 'sspanel' 36 | db_charset = 'utf8' 37 | db_collation = 'utf8_general_ci' 38 | db_prefix = '' 39 | 40 | # redis 41 | redis_scheme = 'tcp' 42 | redis_host = '127.0.0.1' 43 | redis_port = '6379' 44 | redis_database = '0' 45 | 46 | # mailgun 47 | mailgun_key = '' 48 | mailgun_domain = '' 49 | mailgun_sender = '' 50 | 51 | # smtp 52 | smtp_host = '' 53 | smtp_username = '' 54 | smtp_port = '' 55 | smtp_name = '' 56 | smtp_sender = '' 57 | smtp_passsword = '' 58 | smtp_ssl = 'true' -------------------------------------------------------------------------------- /app/Services/Auth/Redis.php: -------------------------------------------------------------------------------- 1 | client = $client; 17 | } 18 | 19 | public function getClient(){ 20 | $client = new RedisClient(); 21 | return $client; 22 | } 23 | 24 | public function login($uid,$time){ 25 | $sid = Tools::genSID(); 26 | Cookie::set([ 27 | 'sid' => $sid 28 | ],$time+time()); 29 | $value = $uid; 30 | $this->client->setex($sid,$time,$value); 31 | } 32 | 33 | public function logout(){ 34 | $sid = Cookie::get('sid'); 35 | $this->client->del($sid); 36 | } 37 | 38 | public function getUser(){ 39 | $sid = Cookie::get('sid'); 40 | $value = $this->client->get($sid); 41 | if($value == null ){ 42 | $user = new User(); 43 | $user->isLogin = false; 44 | return $user; 45 | } 46 | $uid = $value; 47 | $user = User::find($uid); 48 | if($user == null ){ 49 | $user = new User(); 50 | $user->isLogin = false; 51 | return $user; 52 | } 53 | $user->isLogin = true; 54 | return $user; 55 | } 56 | } -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | // ss-panel v3 配置 2 | // 3 | // !!! 修改此key为随机字符串确保网站安全 !!! 4 | key = 'randomKeytestsiteforsspanel' 5 | debug = 'false' // 正式环境请确保为false 6 | #debug = 'true' // 正式环境请确保为false 7 | appName = 'ss控制平台v3.0' //站点名称 8 | baseUrl = 'http://127.0.0.1' // 站点地址 9 | timeZone = 'PRC' // RPC 天朝时间 UTC 格林时间 10 | pwdMethod = 'sha256' // 密码加密 可选 md5,sha256 11 | salt = '' // 密码加密用,从旧版升级请留空 12 | theme = 'default' // 主题 13 | authDriver = 'redis' // 登录验证存储方式,推荐使用Redis 可选: cookie,redis 14 | sessionDriver = 'redis' 15 | cacheDriver = 'redis' 16 | 17 | // 邮件 18 | mailDriver = 'mailgun' // mailgun or smtp 19 | 20 | // 用户签到设置 21 | checkinTime = '22' // 签到间隔时间 单位小时 22 | checkinMin = '93' // 签到最少流量 单位MB 23 | checkinMax = '97' // 签到最多流量 24 | 25 | // 26 | defaultTraffic = '50' // 用户初始流量 单位GB 27 | 28 | // 注册后获得的邀请码数量 29 | inviteNum = '0' 30 | 31 | # database 数据库配置 32 | db_driver = 'mysql' 33 | db_host = 'localhost' 34 | db_database = 'sspanel' 35 | db_username = 'ssuser' 36 | db_password = 'sspasswd' 37 | db_charset = 'utf8' 38 | db_collation = 'utf8_general_ci' 39 | db_prefix = '' 40 | 41 | # redis 42 | redis_scheme = 'tcp' 43 | redis_host = '127.0.0.1' 44 | redis_port = '6379' 45 | redis_database = '0' 46 | 47 | # mailgun 48 | mailgun_key = '' 49 | mailgun_domain = '' 50 | mailgun_sender = '' 51 | 52 | # smtp 53 | smtp_host = '' 54 | smtp_username = '' 55 | smtp_port = '' 56 | smtp_name = '' 57 | smtp_sender = '' 58 | smtp_passsword = '' 59 | smtp_ssl = 'true' -------------------------------------------------------------------------------- /views/default/code.tpl: -------------------------------------------------------------------------------- 1 | {include file='header.tpl'} 2 |
3 |
4 |

5 |
6 |
邀请码实时刷新
7 |
如遇到无邀请码请找已经注册的用户获取。
8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 |

邀请码

18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {foreach $codes as $code} 29 | 30 | 31 | 32 | 33 | 34 | {/foreach} 35 | 36 |
###邀请码 (点击邀请码进入注册页面)状态
{$code->id}{$code->code}可用
37 |
38 |
39 |
40 |
41 |
42 |
43 | {include file='footer.tpl'} -------------------------------------------------------------------------------- /docker/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server ipv6only=on; 4 | 5 | root /opt/ss-panel/public; 6 | index index.php index.html index.htm; 7 | 8 | # Make site accessible from http://localhost/ 9 | server_name localhost; 10 | 11 | location / { 12 | try_files $uri $uri/ /index.php$is_args$args; 13 | # First attempt to serve request as file, then 14 | # as directory, then fall back to displaying a 404. 15 | #try_files $uri $uri/ =404; 16 | # Uncomment to enable naxsi on this location 17 | # include /etc/nginx/naxsi.rules 18 | } 19 | 20 | # Only for nginx-naxsi used with nginx-naxsi-ui : process denied requests 21 | #location /RequestDenied { 22 | # proxy_pass http://127.0.0.1:8080; 23 | #} 24 | 25 | #error_page 404 /404.html; 26 | 27 | # redirect server error pages to the static page /50x.html 28 | # 29 | #error_page 500 502 503 504 /50x.html; 30 | #location = /50x.html { 31 | # root /usr/share/nginx/html; 32 | #} 33 | 34 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 35 | # 36 | location ~ \.php$ { 37 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 38 | # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini 39 | # 40 | # # With php5-cgi alone: 41 | # fastcgi_pass 127.0.0.1:9000; 42 | # # With php5-fpm: 43 | fastcgi_pass unix:/var/run/php5-fpm.sock; 44 | fastcgi_index index.php; 45 | include fastcgi_params; 46 | } 47 | 48 | # deny access to .htaccess files, if Apache's document root 49 | # concurs with nginx's one 50 | # 51 | #location ~ /\.ht { 52 | # deny all; 53 | #} 54 | } -------------------------------------------------------------------------------- /app/Controllers/AdminController.php: -------------------------------------------------------------------------------- 1 | view()->assign('sts',$sts)->display('admin/index.tpl'); 21 | } 22 | 23 | public function node(){ 24 | $nodes = Node::all(); 25 | return $this->view()->assign('nodes',$nodes)->display('admin/node.tpl'); 26 | } 27 | 28 | public function sys() 29 | { 30 | return $this->view()->display('admin/index.tpl'); 31 | } 32 | 33 | public function invite() 34 | { 35 | $codes = InviteCode::where('user_id','=','0')->get(); 36 | return $this->view()->assign('codes',$codes)->display('admin/invite.tpl'); 37 | } 38 | 39 | public function addInvite($request, $response, $args) 40 | { 41 | $n = $request->getParam('num'); 42 | $prefix = $request->getParam('prefix'); 43 | $uid = $request->getParam('uid'); 44 | if ($n < 1){ 45 | $res['ret'] = 0; 46 | return $response->getBody()->write(json_encode($res)); 47 | } 48 | for ($i = 0; $i < $n; $i++ ){ 49 | $char = Tools::genRandomChar(32); 50 | $code = new InviteCode(); 51 | $code->code = $prefix.$char; 52 | $code->user_id = $uid; 53 | $code->save(); 54 | } 55 | $res['ret'] = 1; 56 | $res['msg'] = "邀请码添加成功"; 57 | return $response->getBody()->write(json_encode($res)); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /public/assets/public/css/jumbotron-narrow.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 20px; 4 | padding-bottom: 20px; 5 | } 6 | 7 | /* Everything but the jumbotron gets side spacing for mobile first views */ 8 | .header, 9 | .marketing, 10 | .footer { 11 | padding-right: 15px; 12 | padding-left: 15px; 13 | } 14 | 15 | /* Custom page header */ 16 | .header { 17 | border-bottom: 1px solid #e5e5e5; 18 | } 19 | /* Make the masthead heading the same height as the navigation */ 20 | .header h3 { 21 | padding-bottom: 19px; 22 | margin-top: 0; 23 | margin-bottom: 0; 24 | line-height: 40px; 25 | } 26 | 27 | /* Custom page footer */ 28 | .footer { 29 | padding-top: 19px; 30 | color: #777; 31 | border-top: 1px solid #e5e5e5; 32 | } 33 | 34 | /* Customize container */ 35 | @media (min-width: 768px) { 36 | .container { 37 | max-width: 730px; 38 | } 39 | } 40 | .container-narrow > hr { 41 | margin: 30px 0; 42 | } 43 | 44 | /* Main marketing message and sign up button */ 45 | .jumbotron { 46 | text-align: center; 47 | border-bottom: 1px solid #e5e5e5; 48 | } 49 | .jumbotron .btn { 50 | padding: 14px 24px; 51 | font-size: 21px; 52 | } 53 | 54 | /* Supporting marketing content */ 55 | .marketing { 56 | margin: 40px 0; 57 | } 58 | .marketing p + h4 { 59 | margin-top: 28px; 60 | } 61 | 62 | /* Responsive: Portrait tablets and up */ 63 | @media screen and (min-width: 768px) { 64 | /* Remove the padding we set earlier */ 65 | .header, 66 | .marketing, 67 | .footer { 68 | padding-right: 0; 69 | padding-left: 0; 70 | } 71 | /* Space out the masthead */ 72 | .header { 73 | margin-bottom: 30px; 74 | } 75 | /* Remove the bottom border on the jumbotron for visual effect */ 76 | .jumbotron { 77 | border-bottom: 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /public/assets/public/css/blue.css: -------------------------------------------------------------------------------- 1 | /* iCheck plugin Square skin, blue 2 | ----------------------------------- */ 3 | .icheckbox_square-blue, 4 | .iradio_square-blue { 5 | display: inline-block; 6 | *display: inline; 7 | vertical-align: middle; 8 | margin: 0; 9 | padding: 0; 10 | width: 22px; 11 | height: 22px; 12 | background: url(../img/blue.png) no-repeat; 13 | border: none; 14 | cursor: pointer; 15 | } 16 | 17 | .icheckbox_square-blue { 18 | background-position: 0 0; 19 | } 20 | .icheckbox_square-blue.hover { 21 | background-position: -24px 0; 22 | } 23 | .icheckbox_square-blue.checked { 24 | background-position: -48px 0; 25 | } 26 | .icheckbox_square-blue.disabled { 27 | background-position: -72px 0; 28 | cursor: default; 29 | } 30 | .icheckbox_square-blue.checked.disabled { 31 | background-position: -96px 0; 32 | } 33 | 34 | .iradio_square-blue { 35 | background-position: -120px 0; 36 | } 37 | .iradio_square-blue.hover { 38 | background-position: -144px 0; 39 | } 40 | .iradio_square-blue.checked { 41 | background-position: -168px 0; 42 | } 43 | .iradio_square-blue.disabled { 44 | background-position: -192px 0; 45 | cursor: default; 46 | } 47 | .iradio_square-blue.checked.disabled { 48 | background-position: -216px 0; 49 | } 50 | 51 | /* Retina support */ 52 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 53 | only screen and (-moz-min-device-pixel-ratio: 1.5), 54 | only screen and (-o-min-device-pixel-ratio: 3/2), 55 | only screen and (min-device-pixel-ratio: 1.5) { 56 | .icheckbox_square-blue, 57 | .iradio_square-blue { 58 | background-image: url(../img/blue@2x.png); 59 | -webkit-background-size: 240px 24px; 60 | background-size: 240px 24px; 61 | } 62 | } -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # ss-panel 2 | # 3 | # VERSION 3.0 4 | 5 | # auto build from my github project: https://github.com/maxidea-com/ss-panel 6 | 7 | FROM ubuntu:14.04 8 | 9 | # make sure the package repository is up to date 10 | RUN apt-get -y update && apt-get install -y redis-server 11 | RUN echo "mysql-server-5.6 mysql-server/root_password password pw123456" | sudo debconf-set-selections 12 | RUN echo "mysql-server-5.6 mysql-server/root_password_again password pw123456" | sudo debconf-set-selections 13 | RUN apt-get -y install mysql-server-5.6 14 | RUN apt-get -y install git curl php5 php-guzzle php5-mysql nginx php5-fpm 15 | RUN apt-get install -y python-pip python-m2crypto 16 | RUN pip install cymysql 17 | RUN cd /opt; git clone -b manyuser https://github.com/mengskysama/shadowsocks.git 18 | RUN rm -f /opt/shadowsocks/shadowsocks/Config.py 19 | RUN rm -f /opt/shadowsocks/shadowsocks/config.json 20 | RUN apt-get -y install supervisor 21 | RUN cd /opt; git clone https://github.com/maxidea-com/ss-panel.git 22 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/bin --filename=composer 23 | RUN cd /opt/ss-panel/; composer install 24 | RUN chmod -R 777 /opt/ss-panel/storage 25 | RUN rm -f /etc/nginx/sites-available/default 26 | 27 | ADD Config.py /opt/shadowsocks/shadowsocks/Config.py 28 | ADD config.json /opt/shadowsocks/shadowsocks/config.json 29 | ADD shadowsocks.conf /etc/supervisor/conf.d/shadowsocks.conf 30 | ADD supervisor.conf /etc/supervisor/conf.d/supervisor.conf 31 | ADD 3line.sh /opt/3line.sh 32 | ADD mysql-init.sh /opt/mysql-init.sh 33 | ADD .env /opt/ss-panel/.env 34 | ADD default /etc/nginx/sites-available/default 35 | 36 | RUN /bin/bash /opt/3line.sh 37 | RUN service mysql start 38 | RUN /bin/bash /opt/mysql-init.sh 39 | 40 | RUN apt-get clean && apt-get autoclean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 41 | 42 | EXPOSE 80 1025 1026 1027 1028 43 | 44 | 45 | CMD ["/usr/bin/supervisord"] 46 | 47 | 48 | # contact 49 | MAINTAINER SimonXu, maxidea@gmail.com 50 | -------------------------------------------------------------------------------- /app/Services/Mail/Smtp.php: -------------------------------------------------------------------------------- 1 | config = $this->getConfig(); 16 | $mail = new PHPMailer; 17 | //$mail->SMTPDebug = 3; // Enable verbose debug output 18 | $mail->isSMTP(); // Set mailer to use SMTP 19 | $mail->Host = $this->config['host']; // Specify main and backup SMTP servers 20 | $mail->SMTPAuth = true; // Enable SMTP authentication 21 | $mail->Username = $this->config['username']; // SMTP username 22 | $mail->Password = $this->config['passsword']; // SMTP password 23 | $mail->SMTPSecure = 'tls'; // Enable TLS encryption, `ssl` also accepted 24 | $mail->Port = $this->config['port']; // TCP port to connect to 25 | $mail->setFrom($this->config['sender'], $this->config['name']); 26 | $this->mail = $mail; 27 | } 28 | 29 | public function getConfig(){ 30 | return [ 31 | "host" => Config::get('smtp_host'), 32 | "username" => Config::get('smtp_username'), 33 | "port" => Config::get('smtp_port'), 34 | "sender" => Config::get('smtp_sender'), 35 | "name" => Config::get('smtp_name'), 36 | "passsword" => Config::get('smtp_passsword') 37 | ]; 38 | } 39 | 40 | public function send($to,$subject,$text){ 41 | $mail = $this->mail; 42 | $mail->addAddress($to); // Add a recipient 43 | // $mail->isHTML(true); 44 | $mail->Subject = $subject; 45 | $mail->Body = $text; 46 | // $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; 47 | if(!$mail->send()) { 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /views/default/header.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {$config["appName"]} 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docker/Dockerfile_daocloud: -------------------------------------------------------------------------------- 1 | # ss-panel ###DaoCloud version, for using in CHINA network 2 | # 3 | # VERSION 3.0 4 | 5 | # auto build from my github project: https://github.com/maxidea-com/ss-panel 6 | 7 | FROM daocloud.io/ubuntu:14.04 8 | #FROM ubuntu:14.04 9 | 10 | # make sure the package repository is up to date 11 | RUN apt-get -y update && apt-get install -y redis-server 12 | RUN echo "mysql-server-5.6 mysql-server/root_password password pw123456" | sudo debconf-set-selections 13 | RUN echo "mysql-server-5.6 mysql-server/root_password_again password pw123456" | sudo debconf-set-selections 14 | RUN apt-get -y install mysql-server-5.6 15 | RUN apt-get -y install git curl php5 php-guzzle php5-mysql nginx php5-fpm 16 | RUN apt-get install -y python-pip python-m2crypto 17 | RUN pip install cymysql 18 | #RUN cd /opt; git clone -b manyuser https://github.com/mengskysama/shadowsocks.git 19 | RUN cd /opt; git clone -b manyuser https://git.oschina.net/maxidea/shadowsocks.git 20 | RUN rm -f /opt/shadowsocks/shadowsocks/Config.py 21 | RUN rm -f /opt/shadowsocks/shadowsocks/config.json 22 | RUN apt-get -y install supervisor 23 | #RUN cd /opt; git clone https://github.com/maxidea-com/ss-panel.git 24 | RUN cd /opt; git clone https://git.oschina.net/maxidea/ss-panel.git 25 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/bin --filename=composer 26 | RUN cd /opt/ss-panel/; composer install 27 | RUN chmod -R 777 /opt/ss-panel/storage 28 | RUN rm -f /etc/nginx/sites-available/default 29 | 30 | ADD Config.py /opt/shadowsocks/shadowsocks/Config.py 31 | ADD config.json /opt/shadowsocks/shadowsocks/config.json 32 | ADD shadowsocks.conf /etc/supervisor/conf.d/shadowsocks.conf 33 | ADD supervisor.conf /etc/supervisor/conf.d/supervisor.conf 34 | ADD 3line.sh /opt/3line.sh 35 | ADD mysql-init.sh /opt/mysql-init.sh 36 | ADD .env /opt/ss-panel/.env 37 | ADD default /etc/nginx/sites-available/default 38 | 39 | RUN /bin/bash /opt/3line.sh 40 | RUN service mysql start 41 | RUN /bin/bash /opt/mysql-init.sh 42 | 43 | RUN apt-get clean && apt-get autoclean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 44 | 45 | EXPOSE 80 1025 1026 1027 1028 46 | 47 | 48 | CMD ["/usr/bin/supervisord"] 49 | 50 | 51 | # contact 52 | MAINTAINER SimonXu, maxidea@gmail.com 53 | -------------------------------------------------------------------------------- /views/default/user/nodeinfo.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 |
4 |
5 |

6 | 节点列表 7 | Node List 8 |

9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 |

Config file

19 |
20 |
21 |
22 |

注意!

23 |

配置文件以及二维码请勿泄露!

24 |
25 | 26 | 27 | {$json} 28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |

二维码

38 |
39 |
40 |

{$ssqr}

41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | 49 | 52 | 53 |
54 | 55 |
56 |
57 | {include file='user/footer.tpl'} 58 | -------------------------------------------------------------------------------- /app/Controllers/PasswordController.php: -------------------------------------------------------------------------------- 1 | view()->display('password/reset.tpl'); 20 | } 21 | 22 | public function handleReset($request, $response, $args){ 23 | $email = $request->getParam('email'); 24 | // check limit 25 | 26 | // send email 27 | $user = User::where('email',$email)->first(); 28 | if ($user == null){ 29 | $rs['ret'] = 0; 30 | $rs['msg'] = '此邮箱不存在.'; 31 | return $response->getBody()->write(json_encode($rs)); 32 | } 33 | Password::sendResetEmail($email); 34 | $rs['ret'] = 1; 35 | $rs['msg'] = '重置邮件已经发送,请检查邮箱.'; 36 | return $response->getBody()->write(json_encode($rs)); 37 | } 38 | 39 | public function token($request, $response, $args){ 40 | $token = $args['token']; 41 | return $this->view()->assign('token',$token)->display('password/token.tpl'); 42 | } 43 | 44 | public function handleToken($request, $response, $args){ 45 | $tokenStr = $args['token']; 46 | $password = $request->getParam('password'); 47 | // check token 48 | $token = PasswordReset::where('token',$tokenStr)->first(); 49 | if ($token == null || $token->expire_time < time() ){ 50 | $rs['ret'] = 0; 51 | $rs['msg'] = '链接已经失效,请重新获取'; 52 | return $response->getBody()->write(json_encode($rs)); 53 | } 54 | 55 | $user = User::where('email',$token->email)->first(); 56 | if ($user == null){ 57 | $rs['ret'] = 0; 58 | $rs['msg'] = '链接已经失效,请重新获取'; 59 | return $response->getBody()->write(json_encode($rs)); 60 | } 61 | 62 | // reset password 63 | $hashPassword = Hash::passwordHash($password); 64 | $user->pass = $hashPassword; 65 | if(!$user->save()){ 66 | $rs['ret'] = 0; 67 | $rs['msg'] = '重置失败,请重试'; 68 | return $response->getBody()->write(json_encode($rs)); 69 | } 70 | $rs['ret'] = 1; 71 | $rs['msg'] = '重置成功'; 72 | return $response->getBody()->write(json_encode($rs)); 73 | } 74 | } -------------------------------------------------------------------------------- /views/default/index.tpl: -------------------------------------------------------------------------------- 1 | {include file='header.tpl'} 2 |
3 |
4 |

5 |

{$config["appName"]}

6 |
7 |
轻松科学上网 保护个人隐私
8 |
9 | {if $user->isLogin} 10 |
11 | 进入用户中心 12 |
13 | {else} 14 |
15 | 立即注册 16 |
17 | {/if} 18 |

19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 |
30 |

flash_on

31 |
Super Fast
32 | 33 |

34 | Bleeding edge techniques using Asynchronous I/O and Event-driven programming. 35 |

36 |
37 |
38 | 39 |
40 |
41 |

group

42 |
Open Source
43 | 44 |

45 | Totally free and open source. A worldwide community devoted to deliver bug-free code and long-term support. 46 |

47 |
48 |
49 | 50 |
51 |
52 |

settings

53 |
Easy to work with
54 | 55 |

56 | Avaliable on multiple platforms, including PC, MAC, Mobile (Android and iOS) and Routers (OpenWRT). 57 |

58 |
59 |
60 |
61 | 62 |
63 |

64 | 65 |
66 | 67 |
68 |
69 | {include file='footer.tpl'} -------------------------------------------------------------------------------- /app/Controllers/Admin/UserController.php: -------------------------------------------------------------------------------- 1 | getQueryParams()["page"])){ 14 | $pageNum = $request->getQueryParams()["page"]; 15 | } 16 | $users = User::paginate(15,['*'],'page',$pageNum); 17 | $users->setPath('/admin/user'); 18 | return $this->view()->assign('users',$users)->display('admin/user/index.tpl'); 19 | } 20 | 21 | public function edit($request, $response, $args){ 22 | $id = $args['id']; 23 | $user = User::find($id); 24 | if ($user == null){ 25 | 26 | } 27 | return $this->view()->assign('user',$user)->display('admin/user/edit.tpl'); 28 | } 29 | 30 | public function update($request, $response, $args){ 31 | $id = $args['id']; 32 | $user = User::find($id); 33 | 34 | $user->email = $request->getParam('email'); 35 | if ($request->getParam('pass') != '') { 36 | $user->pass = Hash::passwordHash($request->getParam('pass')); 37 | } 38 | $user->port = $request->getParam('port'); 39 | $user->passwd = $request->getParam('passwd'); 40 | $user->transfer_enable = $request->getParam('transfer_enable'); 41 | $user->invite_num = $request->getParam('invite_num'); 42 | $user->method = $request->getParam('method'); 43 | $user->enable = $request->getParam('enable'); 44 | $user->is_admin = $request->getParam('is_admin'); 45 | $user->ref_by = $request->getParam('ref_by'); 46 | if(!$user->save()){ 47 | $rs['ret'] = 0; 48 | $rs['msg'] = "修改失败"; 49 | return $response->getBody()->write(json_encode($rs)); 50 | } 51 | $rs['ret'] = 1; 52 | $rs['msg'] = "修改成功"; 53 | return $response->getBody()->write(json_encode($rs)); 54 | } 55 | 56 | public function delete($request, $response, $args){ 57 | $id = $args['id']; 58 | $user = User::find($id); 59 | if(!$user->delete()){ 60 | $rs['ret'] = 0; 61 | $rs['msg'] = "删除失败"; 62 | return $response->getBody()->write(json_encode($rs)); 63 | } 64 | $rs['ret'] = 1; 65 | $rs['msg'] = "删除成功"; 66 | return $response->getBody()->write(json_encode($rs)); 67 | } 68 | 69 | public function deleteGet($request, $response, $args){ 70 | $id = $args['id']; 71 | $user = User::find($id); 72 | $user->delete(); 73 | $newResponse = $response->withStatus(302)->withHeader('Location', '/admin/user'); 74 | return $newResponse; 75 | } 76 | } -------------------------------------------------------------------------------- /app/Command/XCat.php: -------------------------------------------------------------------------------- 1 | argv = $argv; 20 | } 21 | 22 | public function boot(){ 23 | switch($this->argv[1]){ 24 | case("install"): 25 | return $this->install(); 26 | case("createAdmin"): 27 | return $this->createAdmin(); 28 | case("resetTraffic"): 29 | return $this->resetTraffic(); 30 | default: 31 | return $this->defaultAction(); 32 | } 33 | } 34 | 35 | public function defaultAction(){ 36 | echo "Memo"; 37 | } 38 | 39 | public function install(){ 40 | echo "x cat will install ss-panel v3...../n"; 41 | } 42 | 43 | public function createAdmin(){ 44 | echo "add admin/ 创建管理员帐号....."; 45 | // ask for input 46 | fwrite(STDOUT, "Enter your email/输入管理员邮箱: "); 47 | // get input 48 | $email = trim(fgets(STDIN)); 49 | // write input back 50 | fwrite(STDOUT, "Enter password for: $email / 为 $email 添加密码 "); 51 | $passwd = trim(fgets(STDIN)); 52 | echo "Email: $email, Password: $passwd! "; 53 | fwrite(STDOUT, "Press [Y] to create admin..... 按下[Y]确认来确认创建管理员账户..... "); 54 | $y = trim(fgets(STDIN)); 55 | if ( strtolower($y) == "y" ){ 56 | echo "start create admin account"; 57 | // create admin user 58 | // do reg user 59 | $user = new User(); 60 | $user->user_name = "admin"; 61 | $user->email = $email; 62 | $user->pass = Hash::passwordHash($passwd); 63 | $user->passwd = Tools::genRandomChar(6); 64 | $user->port = Tools::getLastPort()+1; 65 | $user->t = 0; 66 | $user->u = 0; 67 | $user->d = 0; 68 | $user->transfer_enable = Tools::toGB(Config::get('defaultTraffic')); 69 | $user->invite_num = Config::get('inviteNum'); 70 | $user->ref_by = 0; 71 | $user->is_admin = 1; 72 | if ($user->save()){ 73 | echo "Successful/添加成功!"; 74 | return true; 75 | } 76 | echo "添加失败"; 77 | return false; 78 | } 79 | echo "cancel"; 80 | return false; 81 | } 82 | 83 | public function resetTraffic(){ 84 | try{ 85 | User::where("enable",1)->update([ 86 | 'd' => 0, 87 | 'u' => 0, 88 | ]); 89 | }catch (\Exception $e){ 90 | echo $e->getMessage(); 91 | return false; 92 | } 93 | return "reset traffic successful"; 94 | } 95 | } -------------------------------------------------------------------------------- /views/default/user/node.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 节点列表 9 | Node List 10 |

11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 |

节点

22 |
23 |
24 |
25 |

注意!

26 |

请勿在任何地方公开节点地址!

27 |
28 | {foreach $nodes as $node} 29 | 52 | {/foreach} 53 |
54 | 55 | 56 |
57 |
58 | 59 |
60 |
61 | 62 |
63 | 64 |
65 |
66 | 67 | 68 | {include file='user/footer.tpl'} -------------------------------------------------------------------------------- /app/Controllers/ApiController.php: -------------------------------------------------------------------------------- 1 | get($accessToken); 24 | if ($token==null){ 25 | $res['ret'] = 0; 26 | $res['msg'] = "token is null"; 27 | return $this->echoJson($response,$res); 28 | } 29 | $res['ret'] = 1; 30 | $res['msg'] = "ok"; 31 | $res['data'] = $token; 32 | return $this->echoJson($response,$res); 33 | } 34 | 35 | public function newToken($request, $response, $args){ 36 | // $data = $request->post('sdf'); 37 | $email = $request->getParam('email'); 38 | $email = strtolower($email); 39 | $passwd = $request->getParam('passwd'); 40 | 41 | // Handle Login 42 | $user = User::where('email','=',$email)->first(); 43 | 44 | if ($user == null){ 45 | $res['ret'] = 0; 46 | $res['msg'] = "401 邮箱或者密码错误"; 47 | return $this->echoJson($response,$res); 48 | } 49 | 50 | if (!Hash::checkPassword($user->pass,$passwd)){ 51 | $res['ret'] = 0; 52 | $res['msg'] = "402 邮箱或者密码错误"; 53 | return $this->echoJson($response,$res); 54 | } 55 | $tokenStr = Tools::genToken(); 56 | $storage = Factory::createTokenStorage(); 57 | $expireTime = time() + 3600*24*7; 58 | if($storage->store($tokenStr,$user,$expireTime)){ 59 | $res['ret'] = 1; 60 | $res['msg'] = "ok"; 61 | $res['data']['token'] = $tokenStr; 62 | $res['data']['user_id'] = $user->id; 63 | return $this->echoJson($response,$res); 64 | } 65 | $res['ret'] = 0; 66 | $res['msg'] = "system error"; 67 | return $this->echoJson($response,$res); 68 | } 69 | 70 | public function node($request, $response, $args){ 71 | $nodes = Node::where('type',1)->orderBy('sort')->get(); 72 | $res['ret'] = 1; 73 | $res['msg'] = "ok"; 74 | $res['data'] = $nodes; 75 | return $this->echoJson($response,$res); 76 | } 77 | 78 | public function userInfo($request, $response, $args){ 79 | $id = $args['id']; 80 | $accessToken = Helper::getTokenFromReq($request); 81 | $storage = Factory::createTokenStorage(); 82 | $token = $storage->get($accessToken); 83 | if($id != $token->userId){ 84 | $res['ret'] = 0; 85 | $res['msg'] = "access denied"; 86 | return $this->echoJson($response,$res); 87 | } 88 | $user = User::find($token->userId); 89 | $user->pass = null; 90 | $data = $user; 91 | $res['ret'] = 1; 92 | $res['msg'] = "ok"; 93 | $res['data'] = $data; 94 | return $this->echoJson($response,$res); 95 | 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /public/assets/materialize/js/init.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | $(function(){ 3 | 4 | var window_width = $(window).width(); 5 | 6 | // convert rgb to hex value string 7 | function rgb2hex(rgb) { 8 | if (/^#[0-9A-F]{6}$/i.test(rgb)) { return rgb; } 9 | 10 | rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); 11 | 12 | if (rgb === null) { return "N/A"; } 13 | 14 | function hex(x) { 15 | return ("0" + parseInt(x).toString(16)).slice(-2); 16 | } 17 | 18 | return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); 19 | } 20 | 21 | $('.dynamic-color .col').each(function () { 22 | $(this).children().each(function () { 23 | var color = $(this).css('background-color'), 24 | classes = $(this).attr('class'); 25 | $(this).html(rgb2hex(color) + " " + classes); 26 | if (classes.indexOf("darken") >= 0 || $(this).hasClass('black')) { 27 | $(this).css('color', 'rgba(255,255,255,.9'); 28 | } 29 | }); 30 | }); 31 | 32 | // Floating-Fixed table of contents 33 | if ($('nav').length) { 34 | $('.toc-wrapper').pushpin({ top: $('nav').height() }); 35 | } 36 | else if ($('#index-banner').length) { 37 | $('.toc-wrapper').pushpin({ top: $('#index-banner').height() }); 38 | } 39 | else { 40 | $('.toc-wrapper').pushpin({ top: 0 }); 41 | } 42 | 43 | // Toggle Flow Text 44 | var toggleFlowTextButton = $('#flow-toggle'); 45 | toggleFlowTextButton.click( function(){ 46 | $('#flow-text-demo').children('p').each(function(){ 47 | $(this).toggleClass('flow-text'); 48 | }); 49 | }); 50 | 51 | // Toggle Containers on page 52 | var toggleContainersButton = $('#container-toggle-button'); 53 | toggleContainersButton.click(function(){ 54 | $('body .browser-window .container, .had-container').each(function(){ 55 | $(this).toggleClass('had-container'); 56 | $(this).toggleClass('container'); 57 | if ($(this).hasClass('container')) { 58 | toggleContainersButton.text("Turn off Containers"); 59 | } 60 | else { 61 | toggleContainersButton.text("Turn on Containers"); 62 | } 63 | }); 64 | }); 65 | 66 | // Detect touch screen and enable scrollbar if necessary 67 | function is_touch_device() { 68 | try { 69 | document.createEvent("TouchEvent"); 70 | return true; 71 | } catch (e) { 72 | return false; 73 | } 74 | } 75 | if (is_touch_device()) { 76 | $('#nav-mobile').css({ overflow: 'auto'}); 77 | } 78 | 79 | // Set checkbox on forms.html to indeterminate 80 | var indeterminateCheckbox = document.getElementById('indeterminate-checkbox'); 81 | if (indeterminateCheckbox !== null) 82 | indeterminateCheckbox.indeterminate = true; 83 | 84 | 85 | // Plugin initialization 86 | $('.slider').slider({full_width: true}); 87 | $('.parallax').parallax(); 88 | $('.modal-trigger').leanModal(); 89 | $('.scrollspy').scrollSpy(); 90 | $('.button-collapse').sideNav({'edge': 'left'}); 91 | $('.datepicker').pickadate({selectYears: 20}); 92 | $('select').not('.disabled').material_select(); 93 | 94 | 95 | }); // end of document ready 96 | })(jQuery); // end of jQuery name space 97 | -------------------------------------------------------------------------------- /app/Controllers/Admin/NodeController.php: -------------------------------------------------------------------------------- 1 | view()->assign('nodes',$nodes)->display('admin/node/index.tpl'); 13 | } 14 | 15 | public function create($request, $response, $args){ 16 | return $this->view()->display('admin/node/create.tpl'); 17 | } 18 | 19 | public function add($request, $response, $args){ 20 | $node = new Node(); 21 | $node->name = $request->getParam('name'); 22 | $node->server = $request->getParam('server'); 23 | $node->method = $request->getParam('method'); 24 | $node->custom_method = $request->getParam('custom_method'); 25 | $node->info = $request->getParam('info'); 26 | $node->type = $request->getParam('type'); 27 | $node->status = $request->getParam('status'); 28 | $node->sort = $request->getParam('sort'); 29 | if(!$node->save()){ 30 | $rs['ret'] = 0; 31 | $rs['msg'] = "添加失败"; 32 | return $response->getBody()->write(json_encode($rs)); 33 | } 34 | $rs['ret'] = 1; 35 | $rs['msg'] = "节点添加成功"; 36 | return $response->getBody()->write(json_encode($rs)); 37 | } 38 | 39 | public function edit($request, $response, $args){ 40 | $id = $args['id']; 41 | $node = Node::find($id); 42 | if ($node == null){ 43 | 44 | } 45 | return $this->view()->assign('node',$node)->display('admin/node/edit.tpl'); 46 | } 47 | 48 | public function update($request, $response, $args){ 49 | $id = $args['id']; 50 | $node = Node::find($id); 51 | 52 | $node->name = $request->getParam('name'); 53 | $node->server = $request->getParam('server'); 54 | $node->method = $request->getParam('method'); 55 | $node->custom_method = $request->getParam('custom_method'); 56 | $node->info = $request->getParam('info'); 57 | $node->type = $request->getParam('type'); 58 | $node->status = $request->getParam('status'); 59 | $node->sort = $request->getParam('sort'); 60 | if(!$node->save()){ 61 | $rs['ret'] = 0; 62 | $rs['msg'] = "修改失败"; 63 | return $response->getBody()->write(json_encode($rs)); 64 | } 65 | $rs['ret'] = 1; 66 | $rs['msg'] = "修改成功"; 67 | return $response->getBody()->write(json_encode($rs)); 68 | } 69 | 70 | 71 | public function delete($request, $response, $args){ 72 | $id = $args['id']; 73 | $node = Node::find($id); 74 | if(!$node->delete()){ 75 | $rs['ret'] = 0; 76 | $rs['msg'] = "删除失败"; 77 | return $response->getBody()->write(json_encode($rs)); 78 | } 79 | $rs['ret'] = 1; 80 | $rs['msg'] = "删除成功"; 81 | return $response->getBody()->write(json_encode($rs)); 82 | } 83 | 84 | public function deleteGet($request, $response, $args){ 85 | $id = $args['id']; 86 | $node = Node::find($id); 87 | $node->delete(); 88 | $newResponse = $response->withStatus(302)->withHeader('Location', '/admin/node'); 89 | return $newResponse; 90 | } 91 | } -------------------------------------------------------------------------------- /views/default/user/kill.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 |
4 | 5 |
6 |

7 | 删除我的帐号 8 | Deactive my account 9 |

10 |
11 | 12 | 13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 | 25 | 26 | 31 | 32 |
33 | 34 |
35 | 36 | 37 |
38 | 39 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 |
50 | 51 | 56 | 57 | 85 | 86 | {include file='user/footer.tpl'} -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | attributes['email']))); 26 | return "https://secure.gravatar.com/avatar/$hash"; 27 | } 28 | 29 | public function isAdmin(){ 30 | return $this->attributes['is_admin']; 31 | } 32 | 33 | public function lastSsTime(){ 34 | return Tools::toDateTime($this->attributes['t']); 35 | } 36 | 37 | public function lastCheckInTime(){ 38 | if($this->attributes['last_check_in_time'] == 0){ 39 | return "从未签到"; 40 | } 41 | return Tools::toDateTime($this->attributes['last_check_in_time']); 42 | } 43 | 44 | public function regDate(){ 45 | return $this->attributes['reg_date']; 46 | } 47 | 48 | public function updatePassword($pwd){ 49 | $this->pass = Hash::passwordHash($pwd); 50 | $this->save(); 51 | } 52 | 53 | public function updateSsPwd($pwd){ 54 | $this->passwd = $pwd; 55 | $this->save(); 56 | } 57 | 58 | public function updateMethod($method){ 59 | $this->method = $method; 60 | $this->save(); 61 | } 62 | 63 | public function addInviteCode(){ 64 | $uid = $this->attributes['id']; 65 | $code = new InviteCode(); 66 | $code->code = Tools::genRandomChar(32); 67 | $code->user = $uid; 68 | $code->save(); 69 | } 70 | 71 | public function addManyInviteCodes($num){ 72 | for($i = 0; $i < $num; $i++){ 73 | $this->addInviteCode(); 74 | } 75 | } 76 | 77 | public function trafficUsagePercent(){ 78 | $total = $this->attributes['u'] + $this->attributes['d']; 79 | $transfer_enable = $this->attributes['transfer_enable']; 80 | $percent = $total/$transfer_enable; 81 | $percent = round($percent,2); 82 | $percent = $percent*100; 83 | return $percent; 84 | } 85 | 86 | public function enableTraffic(){ 87 | $transfer_enable = $this->attributes['transfer_enable']; 88 | return Tools::flowAutoShow($transfer_enable); 89 | } 90 | 91 | public function usedTraffic(){ 92 | $total = $this->attributes['u'] + $this->attributes['d']; 93 | return Tools::flowAutoShow($total); 94 | } 95 | 96 | public function unusedTraffic(){ 97 | $total = $this->attributes['u'] + $this->attributes['d']; 98 | $transfer_enable = $this->attributes['transfer_enable']; 99 | return Tools::flowAutoShow($transfer_enable-$total); 100 | } 101 | 102 | public function isAbleToCheckin(){ 103 | $last = $this->attributes['last_check_in_time']; 104 | $hour = Config::get('checkinTime'); 105 | if($last + $hour*3600 < time() ){ 106 | return true; 107 | } 108 | return false; 109 | } 110 | /* 111 | * @param traffic 单位 MB 112 | */ 113 | public function addTraffic($traffic){ 114 | } 115 | 116 | public function inviteCodes(){ 117 | $uid = $this->attributes['id']; 118 | return InviteCode::where('user_id',$uid)->get(); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /db-160212.sql: -------------------------------------------------------------------------------- 1 | -- Adminer 4.1.0 MySQL dump 2 | 3 | SET NAMES utf8; 4 | SET time_zone = '+00:00'; 5 | SET foreign_key_checks = 0; 6 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 7 | 8 | DROP TABLE IF EXISTS `ss_chg_code`; 9 | CREATE TABLE `ss_chg_code` ( 10 | `id` int(11) NOT NULL AUTO_INCREMENT, 11 | `code` varchar(128) NOT NULL, 12 | `time` int(11) NOT NULL, 13 | `traffic` bigint(20) NOT NULL, 14 | `tag` varchar(64) NOT NULL, 15 | `add_time` int(11) NOT NULL, 16 | `status` int(11) NOT NULL, 17 | `user_id` int(11) NOT NULL, 18 | `use_time` int(11) NOT NULL, 19 | PRIMARY KEY (`id`) 20 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 21 | 22 | 23 | DROP TABLE IF EXISTS `ss_invite_code`; 24 | CREATE TABLE `ss_invite_code` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `code` varchar(128) NOT NULL, 27 | `user_id` int(11) NOT NULL, 28 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 29 | `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 30 | PRIMARY KEY (`id`), 31 | KEY `user_id` (`user_id`) 32 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 33 | 34 | 35 | DROP TABLE IF EXISTS `ss_node`; 36 | CREATE TABLE `ss_node` ( 37 | `id` int(11) NOT NULL AUTO_INCREMENT, 38 | `name` varchar(128) NOT NULL, 39 | `type` int(3) NOT NULL, 40 | `server` varchar(128) NOT NULL, 41 | `method` varchar(64) NOT NULL, 42 | `custom_method` tinyint(1) NOT NULL DEFAULT '0', 43 | `info` varchar(128) NOT NULL, 44 | `status` varchar(128) NOT NULL, 45 | `sort` int(3) NOT NULL, 46 | PRIMARY KEY (`id`) 47 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 48 | 49 | 50 | DROP TABLE IF EXISTS `ss_password_reset`; 51 | CREATE TABLE `ss_password_reset` ( 52 | `id` int(11) NOT NULL AUTO_INCREMENT, 53 | `email` varchar(32) NOT NULL, 54 | `token` varchar(128) NOT NULL, 55 | `init_time` int(11) NOT NULL, 56 | `expire_time` int(11) NOT NULL, 57 | PRIMARY KEY (`id`) 58 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 59 | 60 | 61 | DROP TABLE IF EXISTS `user`; 62 | CREATE TABLE `user` ( 63 | `id` int(11) NOT NULL AUTO_INCREMENT, 64 | `user_name` varchar(128) CHARACTER SET utf8mb4 NOT NULL, 65 | `email` varchar(32) NOT NULL, 66 | `pass` varchar(64) NOT NULL, 67 | `passwd` varchar(16) NOT NULL, 68 | `t` int(11) NOT NULL DEFAULT '0', 69 | `u` bigint(20) NOT NULL, 70 | `d` bigint(20) NOT NULL, 71 | `transfer_enable` bigint(20) NOT NULL, 72 | `port` int(11) NOT NULL, 73 | `switch` tinyint(4) NOT NULL DEFAULT '1', 74 | `enable` tinyint(4) NOT NULL DEFAULT '1', 75 | `type` tinyint(4) NOT NULL DEFAULT '1', 76 | `last_get_gift_time` int(11) NOT NULL DEFAULT '0', 77 | `last_check_in_time` int(11) NOT NULL DEFAULT '0', 78 | `last_rest_pass_time` int(11) NOT NULL DEFAULT '0', 79 | `reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 80 | `invite_num` int(8) NOT NULL DEFAULT '0', 81 | `is_admin` int(2) NOT NULL DEFAULT '0', 82 | `ref_by` int(11) NOT NULL DEFAULT '0', 83 | `expire_time` int(11) NOT NULL DEFAULT '0', 84 | `method` varchar(64) NOT NULL DEFAULT 'aes-256-cfb', 85 | `is_email_verify` tinyint(4) NOT NULL DEFAULT '0', 86 | PRIMARY KEY (`id`) 87 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 88 | 89 | 90 | DROP TABLE IF EXISTS `user_token`; 91 | CREATE TABLE `user_token` ( 92 | `id` int(11) NOT NULL AUTO_INCREMENT, 93 | `token` varchar(256) NOT NULL, 94 | `user_id` int(11) NOT NULL, 95 | `create_time` int(11) NOT NULL, 96 | `expire_time` int(11) NOT NULL, 97 | PRIMARY KEY (`id`) 98 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 99 | 100 | 101 | -- 2016-02-12 11:40:01 102 | -------------------------------------------------------------------------------- /views/default/admin/invite.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 邀请 9 | Invite 10 |

11 |
12 | 13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |

添加邀请码

22 |
23 | 24 |
25 | 26 | 31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 | 40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 |
49 | 50 | 53 | 54 | 57 | 58 | 59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 | 93 | 94 | {include file='admin/footer.tpl'} -------------------------------------------------------------------------------- /views/default/admin/node/index.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 节点列表 9 | Node List 10 |

11 |
12 | 13 | 14 |
15 |
16 |
17 |

添加

18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {foreach $nodes as $node} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | {/foreach} 42 |
ID节点加密描述排序操作
#{$node->id} {$node->name}{$node->method}{$node->info}{$node->sort} 37 | 编辑 38 | 删除 39 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 | 51 | 52 | 97 | 98 | {include file='admin/footer.tpl'} -------------------------------------------------------------------------------- /views/default/admin/user/index.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 用户列表 9 | User List 10 |

11 |
12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 | {$users->render()} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {foreach $users as $user} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | {/foreach} 44 |
ID邮箱端口加密方式已用流量/总流量最后在线时间操作
#{$user->id}{$user->email}{$user->port}{$user->method}{$user->usedTraffic()}/{$user->enableTraffic()}{$user->lastSsTime()} 39 | 编辑 40 | 删除 41 |
45 | {$users->render()} 46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 | 54 | 55 | 100 | 101 | {include file='admin/footer.tpl'} -------------------------------------------------------------------------------- /views/default/password/reset.tpl: -------------------------------------------------------------------------------- 1 | {include file='auth/header.tpl'} 2 | 3 |
4 | 7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 | 28 | 33 | 登陆
34 | 注册个帐号 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /views/default/password/token.tpl: -------------------------------------------------------------------------------- 1 | {include file='auth/header.tpl'} 2 | 3 |
4 | 7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | 16 | 17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 | 29 | 34 | 登陆
35 | 注册个帐号 36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 57 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /views/default/user/invite.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 邀请 9 | Invite 10 |

11 |
12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |

邀请

23 |
24 |
25 |

当前您可以生成{$user->invite_num}个邀请码。

26 | {if $user->invite_num } 27 | 28 | {/if} 29 | 34 | 35 |
36 | 37 |
38 |

我的邀请码

39 |
40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {foreach $codes as $code} 53 | 54 | 55 | 56 | 57 | 58 | {/foreach} 59 | 60 |
###邀请码(点右键复制链接)状态
{$code->id}{$code->code}可用
61 |
62 |
63 |
64 | 65 |
66 |
67 |
68 | 69 |
70 |

注意!

71 |

邀请码请给认识的需要的人。

72 |

邀请有记录,若被邀请的人违反用户协议,您将会有连带责任。

73 |
74 | 75 |
76 |

说明

77 |

用户注册48小时后,才可以生成邀请码。

78 |

邀请码暂时无法购买,请珍惜。

79 |

公共页面不定期发放邀请码,如果用完邀请码可以关注公共邀请。

80 |
81 | 82 |
83 |
84 |
85 |
86 |
87 |
88 | 89 | 106 | 107 | {include file='user/footer.tpl'} -------------------------------------------------------------------------------- /public/assets/public/js/icheck.min.js: -------------------------------------------------------------------------------- 1 | /*! iCheck v1.0.1 by Damir Sultanov, http://git.io/arlzeA, MIT Licensed */ 2 | (function(h){function F(a,b,d){var c=a[0],e=/er/.test(d)?m:/bl/.test(d)?s:l,f=d==H?{checked:c[l],disabled:c[s],indeterminate:"true"==a.attr(m)||"false"==a.attr(w)}:c[e];if(/^(ch|di|in)/.test(d)&&!f)D(a,e);else if(/^(un|en|de)/.test(d)&&f)t(a,e);else if(d==H)for(e in f)f[e]?D(a,e,!0):t(a,e,!0);else if(!b||"toggle"==d){if(!b)a[p]("ifClicked");f?c[n]!==u&&t(a,e):D(a,e)}}function D(a,b,d){var c=a[0],e=a.parent(),f=b==l,A=b==m,B=b==s,K=A?w:f?E:"enabled",p=k(a,K+x(c[n])),N=k(a,b+x(c[n]));if(!0!==c[b]){if(!d&& 3 | b==l&&c[n]==u&&c.name){var C=a.closest("form"),r='input[name="'+c.name+'"]',r=C.length?C.find(r):h(r);r.each(function(){this!==c&&h(this).data(q)&&t(h(this),b)})}A?(c[b]=!0,c[l]&&t(a,l,"force")):(d||(c[b]=!0),f&&c[m]&&t(a,m,!1));L(a,f,b,d)}c[s]&&k(a,y,!0)&&e.find("."+I).css(y,"default");e[v](N||k(a,b)||"");B?e.attr("aria-disabled","true"):e.attr("aria-checked",A?"mixed":"true");e[z](p||k(a,K)||"")}function t(a,b,d){var c=a[0],e=a.parent(),f=b==l,h=b==m,q=b==s,p=h?w:f?E:"enabled",t=k(a,p+x(c[n])), 4 | u=k(a,b+x(c[n]));if(!1!==c[b]){if(h||!d||"force"==d)c[b]=!1;L(a,f,p,d)}!c[s]&&k(a,y,!0)&&e.find("."+I).css(y,"pointer");e[z](u||k(a,b)||"");q?e.attr("aria-disabled","false"):e.attr("aria-checked","false");e[v](t||k(a,p)||"")}function M(a,b){if(a.data(q)){a.parent().html(a.attr("style",a.data(q).s||""));if(b)a[p](b);a.off(".i").unwrap();h(G+'[for="'+a[0].id+'"]').add(a.closest(G)).off(".i")}}function k(a,b,d){if(a.data(q))return a.data(q).o[b+(d?"":"Class")]}function x(a){return a.charAt(0).toUpperCase()+ 5 | a.slice(1)}function L(a,b,d,c){if(!c){if(b)a[p]("ifToggled");a[p]("ifChanged")[p]("if"+x(d))}}var q="iCheck",I=q+"-helper",u="radio",l="checked",E="un"+l,s="disabled",w="determinate",m="in"+w,H="update",n="type",v="addClass",z="removeClass",p="trigger",G="label",y="cursor",J=/ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);h.fn[q]=function(a,b){var d='input[type="checkbox"], input[type="'+u+'"]',c=h(),e=function(a){a.each(function(){var a=h(this);c=a.is(d)? 6 | c.add(a):c.add(a.find(d))})};if(/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(a))return a=a.toLowerCase(),e(this),c.each(function(){var c=h(this);"destroy"==a?M(c,"ifDestroyed"):F(c,!0,a);h.isFunction(b)&&b()});if("object"!=typeof a&&a)return this;var f=h.extend({checkedClass:l,disabledClass:s,indeterminateClass:m,labelHover:!0,aria:!1},a),k=f.handle,B=f.hoverClass||"hover",x=f.focusClass||"focus",w=f.activeClass||"active",y=!!f.labelHover,C=f.labelHoverClass|| 7 | "hover",r=(""+f.increaseArea).replace("%","")|0;if("checkbox"==k||k==u)d='input[type="'+k+'"]';-50>r&&(r=-50);e(this);return c.each(function(){var a=h(this);M(a);var c=this,b=c.id,e=-r+"%",d=100+2*r+"%",d={position:"absolute",top:e,left:e,display:"block",width:d,height:d,margin:0,padding:0,background:"#fff",border:0,opacity:0},e=J?{position:"absolute",visibility:"hidden"}:r?d:{position:"absolute",opacity:0},k="checkbox"==c[n]?f.checkboxClass||"icheckbox":f.radioClass||"i"+u,m=h(G+'[for="'+b+'"]').add(a.closest(G)), 8 | A=!!f.aria,E=q+"-"+Math.random().toString(36).replace("0.",""),g='
")[p]("ifCreated").parent().append(f.insert);d=h('').css(d).appendTo(g);a.data(q,{o:f,s:a.attr("style")}).css(e);f.inheritClass&&g[v](c.className||"");f.inheritID&&b&&g.attr("id",q+"-"+b);"static"==g.css("position")&&g.css("position","relative");F(a,!0,H); 9 | if(m.length)m.on("click.i mouseover.i mouseout.i touchbegin.i touchend.i",function(b){var d=b[n],e=h(this);if(!c[s]){if("click"==d){if(h(b.target).is("a"))return;F(a,!1,!0)}else y&&(/ut|nd/.test(d)?(g[z](B),e[z](C)):(g[v](B),e[v](C)));if(J)b.stopPropagation();else return!1}});a.on("click.i focus.i blur.i keyup.i keydown.i keypress.i",function(b){var d=b[n];b=b.keyCode;if("click"==d)return!1;if("keydown"==d&&32==b)return c[n]==u&&c[l]||(c[l]?t(a,l):D(a,l)),!1;if("keyup"==d&&c[n]==u)!c[l]&&D(a,l);else if(/us|ur/.test(d))g["blur"== 10 | d?z:v](x)});d.on("click mousedown mouseup mouseover mouseout touchbegin.i touchend.i",function(b){var d=b[n],e=/wn|up/.test(d)?w:B;if(!c[s]){if("click"==d)F(a,!1,!0);else{if(/wn|er|in/.test(d))g[v](e);else g[z](e+" "+w);if(m.length&&y&&e==B)m[/ut|nd/.test(d)?z:v](C)}if(J)b.stopPropagation();else return!1}})})}})(window.jQuery||window.Zepto); 11 | -------------------------------------------------------------------------------- /views/default/user/index.tpl: -------------------------------------------------------------------------------- 1 | {include file='user/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 用户中心 9 | User Center 10 |

11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |

公告&FAQ

21 |
22 |
23 |

流量不会重置,可以通过签到获取流量。

24 |

流量可以通过签到获取,基本每天可以用1G流量。

25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |

流量使用情况

33 |
34 |
35 |
36 |
37 | Transfer 38 |
39 |
40 |

总流量:{$user->enableTraffic()}

41 |

已用流量:{$user->usedTraffic()}

42 |

剩余流量: {$user->unusedTraffic()}

43 |
44 |
45 |
46 | 47 | 48 | 49 |
50 |
51 |
52 |

签到获取流量

53 |
54 |
55 |

{$config["checkinTime"]}小时内可以签到一次。

56 | {if $user->isAbleToCheckin() } 57 |

58 | {else} 59 |

不能签到

60 | {/if} 61 |

62 |

上次签到时间:{$user->lastCheckInTime()}

63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |

连接信息

71 |
72 |
73 |

端口:{$user->port}

74 |

密码:{$user->passwd}

75 |

自定义加密:{$user->method}

76 |

最后使用时间:{$user->lastSsTime()}

77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 | 85 | 103 | 104 | 105 | {include file='user/footer.tpl'} -------------------------------------------------------------------------------- /views/default/auth/login.tpl: -------------------------------------------------------------------------------- 1 | {include file='auth/header.tpl'} 2 | 3 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 65 | 112 | 113 | -------------------------------------------------------------------------------- /app/Controllers/AuthController.php: -------------------------------------------------------------------------------- 1 | view()->display('auth/login.tpl'); 26 | } 27 | 28 | public function loginHandle($request, $response, $args) 29 | { 30 | // $data = $request->post('sdf'); 31 | $email = $request->getParam('email'); 32 | $email = strtolower($email); 33 | $passwd = $request->getParam('passwd'); 34 | $rememberMe = $request->getParam('remember_me'); 35 | 36 | // Handle Login 37 | $user = User::where('email','=',$email)->first(); 38 | 39 | if ($user == null){ 40 | $rs['ret'] = 0; 41 | $rs['msg'] = "401 邮箱或者密码错误"; 42 | return $response->getBody()->write(json_encode($rs)); 43 | } 44 | 45 | if (!Hash::checkPassword($user->pass,$passwd)){ 46 | $rs['ret'] = 0; 47 | $rs['msg'] = "402 邮箱或者密码错误"; 48 | return $response->getBody()->write(json_encode($rs)); 49 | } 50 | // @todo 51 | $time = 3600*24; 52 | if($rememberMe){ 53 | $time = 3600*24*7; 54 | } 55 | Auth::login($user->id,$time); 56 | $rs['ret'] = 1; 57 | $rs['msg'] = "欢迎回来"; 58 | return $response->getBody()->write(json_encode($rs)); 59 | } 60 | 61 | public function register($request, $response, $next) 62 | { 63 | $ary = $request->getQueryParams(); 64 | $code = ""; 65 | if(isset($ary['code'])){ 66 | $code = $ary['code']; 67 | } 68 | return $this->view()->assign('code',$code)->display('auth/register.tpl'); 69 | } 70 | 71 | public function registerHandle($request, $response, $next) 72 | { 73 | $name = $request->getParam('name'); 74 | $email = $request->getParam('email'); 75 | $email = strtolower($email); 76 | $passwd = $request->getParam('passwd'); 77 | $repasswd = $request->getParam('repasswd'); 78 | $code = $request->getParam('code'); 79 | // check code 80 | $c = InviteCode::where('code',$code)->first(); 81 | if ( $c == null) { 82 | $res['ret'] = 0; 83 | $res['msg'] = "邀请码无效"; 84 | return $response->getBody()->write(json_encode($res)); 85 | } 86 | 87 | // check email format 88 | if(!Check::isEmailLegal($email)){ 89 | $res['ret'] = 0; 90 | $res['msg'] = "邮箱无效"; 91 | return $response->getBody()->write(json_encode($res)); 92 | } 93 | // check pwd length 94 | if(strlen($passwd)<8){ 95 | $res['ret'] = 0; 96 | $res['msg'] = "密码太短"; 97 | return $response->getBody()->write(json_encode($res)); 98 | } 99 | 100 | // check pwd re 101 | if($passwd != $repasswd){ 102 | $res['ret'] = 0; 103 | $res['msg'] = "两次密码输入不符"; 104 | return $response->getBody()->write(json_encode($res)); 105 | } 106 | 107 | // check email 108 | $user = User::where('email',$email)->first(); 109 | if ( $user != null) { 110 | $res['ret'] = 0; 111 | $res['msg'] = "邮箱已经被注册了"; 112 | return $response->getBody()->write(json_encode($res)); 113 | } 114 | 115 | // do reg user 116 | $user = new User(); 117 | $user->user_name = $name; 118 | $user->email = $email; 119 | $user->pass = Hash::passwordHash($passwd); 120 | $user->passwd = Tools::genRandomChar(6); 121 | $user->port = Tools::getLastPort()+1; 122 | $user->t = 0; 123 | $user->u = 0; 124 | $user->d = 0; 125 | $user->transfer_enable = Tools::toGB(Config::get('defaultTraffic')); 126 | $user->invite_num = Config::get('inviteNum'); 127 | $user->ref_by = $c->user_id; 128 | 129 | if($user->save()){ 130 | $res['ret'] = 1; 131 | $res['msg'] = "注册成功"; 132 | $c->delete(); 133 | return $response->getBody()->write(json_encode($res)); 134 | } 135 | $res['ret'] = 0; 136 | $res['msg'] = "未知错误"; 137 | return $response->getBody()->write(json_encode($res)); 138 | } 139 | 140 | public function logout($request, $response, $next){ 141 | Auth::logout(); 142 | $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login'); 143 | return $newResponse; 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /app/Utils/Tools.php: -------------------------------------------------------------------------------- 1 | $gb) { 20 | return round($value / $gb, 2) . "GB"; 21 | } else if (abs($value) > $mb) { 22 | return round($value / $mb, 2) . "MB"; 23 | } else if (abs($value) > $kb) { 24 | return round($value / $kb, 2) . "KB"; 25 | } else { 26 | return round($value, 2); 27 | } 28 | } 29 | 30 | static function toMB($traffic){ 31 | $mb = 1048576; 32 | return $traffic*$mb; 33 | } 34 | 35 | static function toGB($traffic){ 36 | $gb = 1048576*1024; 37 | return $traffic*$gb; 38 | } 39 | 40 | //获取随机字符串 41 | public static function genRandomChar( $length = 8 ) { 42 | // 密码字符集,可任意添加你需要的字符 43 | $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 44 | $char = ''; 45 | for ( $i = 0; $i < $length; $i++ ) 46 | { 47 | $char .= $chars[ mt_rand(0, strlen($chars) - 1) ]; 48 | } 49 | return $char; 50 | } 51 | 52 | public static function genToken(){ 53 | return self::genRandomChar(64); 54 | } 55 | 56 | 57 | // Unix time to Date Time 58 | public static function toDateTime($time){ 59 | return date('Y-m-d H:i:s',$time); 60 | } 61 | 62 | // check html 63 | static function checkHtml($html) { 64 | $html = stripslashes($html); 65 | preg_match_all("/<([^<]+)>/is", $html, $ms); 66 | $searchs[] = '<'; 67 | $replaces[] = '<'; 68 | $searchs[] = '>'; 69 | $replaces[] = '>'; 70 | if($ms[1]) { 71 | $allowtags = 'img|a|font|div|table|tbody|caption|tr|td|th|br 72 | |p|b|strong|i|u|em|span|ol|ul|li|blockquote 73 | |object|param|embed';//允许的标签 74 | $ms[1] = array_unique($ms[1]); 75 | foreach ($ms[1] as $value) { 76 | $searchs[] = "<".$value.">"; 77 | $value = shtmlspecialchars($value); 78 | $value = str_replace(array('/','/*'), array('.','/.'), $value); 79 | $skipkeys = array( 80 | 'onabort','onactivate','onafterprint','onafterupdate', 81 | 'onbeforeactivate','onbeforecopy','onbeforecut', 82 | 'onbeforedeactivate','onbeforeeditfocus','onbeforepaste', 83 | 'onbeforeprint','onbeforeunload','onbeforeupdate', 84 | 'onblur','onbounce','oncellchange','onchange', 85 | 'onclick','oncontextmenu','oncontrolselect', 86 | 'oncopy','oncut','ondataavailable', 87 | 'ondatasetchanged','ondatasetcomplete','ondblclick', 88 | 'ondeactivate','ondrag','ondragend', 89 | 'ondragenter','ondragleave','ondragover', 90 | 'ondragstart','ondrop','onerror','onerrorupdate', 91 | 'onfilterchange','onfinish','onfocus','onfocusin', 92 | 'onfocusout','onhelp','onkeydown','onkeypress', 93 | 'onkeyup','onlayoutcomplete','onload', 94 | 'onlosecapture','onmousedown','onmouseenter', 95 | 'onmouseleave','onmousemove','onmouseout', 96 | 'onmouseover','onmouseup','onmousewheel', 97 | 'onmove','onmoveend','onmovestart','onpaste', 98 | 'onpropertychange','onreadystatechange','onreset', 99 | 'onresize','onresizeend','onresizestart', 100 | 'onrowenter','onrowexit','onrowsdelete', 101 | 'onrowsinserted','onscroll','onselect', 102 | 'onselectionchange','onselectstart','onstart', 103 | 'onstop','onsubmit','onunload','javascript', 104 | 'script','eval','behaviour','expression', 105 | 'style','class' 106 | ); 107 | $skipstr = implode('|', $skipkeys); 108 | $value = preg_replace(array("/($skipstr)/i"), '.', $value); 109 | if(!preg_match("/^[/|s]?($allowtags)(s+|$)/is", $value)) { 110 | $value = ''; 111 | } 112 | $replaces[] = empty($value)?'':"<".str_replace('"', '"', $value).">"; 113 | } 114 | } 115 | $html = str_replace($searchs, $replaces, $html); 116 | $html = addslashes($html); 117 | return $html; 118 | } 119 | 120 | public static function genSID(){ 121 | $unid = uniqid(Config::get('key')); 122 | return Hash::sha256WithSalt($unid); 123 | } 124 | 125 | public static function getLastPort(){ 126 | $user = User::orderBy('id', 'desc')->first(); 127 | if ($user == null){ 128 | return 1024; // @todo 129 | } 130 | return $user->port; 131 | } 132 | } -------------------------------------------------------------------------------- /config/routes.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'displayErrorDetails' => $debug, 25 | ] 26 | ]; 27 | $c = new Container($configuration); 28 | ***/ 29 | 30 | // Make a Slim App 31 | // $app = new App($c); 32 | $app = new App([ 33 | 'settings' => [ 34 | 'debug' => $debug, 35 | 'whoops.editor' => 'sublime' 36 | ] 37 | ]); 38 | $app->add(new WhoopsMiddleware); 39 | 40 | 41 | // Home 42 | $app->get('/', 'App\Controllers\HomeController:index'); 43 | $app->get('/code', 'App\Controllers\HomeController:code'); 44 | $app->get('/tos', 'App\Controllers\HomeController:tos'); 45 | 46 | // User Center 47 | $app->group('/user', function () { 48 | $this->get('', 'App\Controllers\UserController:index'); 49 | $this->get('/', 'App\Controllers\UserController:index'); 50 | $this->post('/checkin', 'App\Controllers\UserController:doCheckin'); 51 | $this->get('/node', 'App\Controllers\UserController:node'); 52 | $this->get('/node/{id}', 'App\Controllers\UserController:nodeInfo'); 53 | $this->get('/profile', 'App\Controllers\UserController:profile'); 54 | $this->get('/invite', 'App\Controllers\UserController:invite'); 55 | $this->post('/invite', 'App\Controllers\UserController:doInvite'); 56 | $this->get('/edit', 'App\Controllers\UserController:edit'); 57 | $this->post('/password', 'App\Controllers\UserController:updatePassword'); 58 | $this->post('/sspwd', 'App\Controllers\UserController:updateSsPwd'); 59 | $this->post('/method', 'App\Controllers\UserController:updateMethod'); 60 | $this->get('/sys', 'App\Controllers\UserController:sys'); 61 | $this->get('/kill', 'App\Controllers\UserController:kill'); 62 | $this->post('/kill', 'App\Controllers\UserController:handleKill'); 63 | $this->get('/logout', 'App\Controllers\UserController:logout'); 64 | })->add(new Auth()); 65 | 66 | // Auth 67 | $app->group('/auth', function () { 68 | $this->get('/login', 'App\Controllers\AuthController:login'); 69 | $this->post('/login', 'App\Controllers\AuthController:loginHandle'); 70 | $this->get('/register', 'App\Controllers\AuthController:register'); 71 | $this->post('/register', 'App\Controllers\AuthController:registerHandle'); 72 | $this->get('/logout', 'App\Controllers\AuthController:logout'); 73 | })->add(new Guest()); 74 | 75 | // Password 76 | $app->group('/password', function () { 77 | $this->get('/reset', 'App\Controllers\PasswordController:reset'); 78 | $this->post('/reset', 'App\Controllers\PasswordController:handleReset'); 79 | $this->get('/token/{token}', 'App\Controllers\PasswordController:token'); 80 | $this->post('/token/{token}', 'App\Controllers\PasswordController:handleToken'); 81 | })->add(new Guest()); 82 | 83 | // Admin 84 | $app->group('/admin', function () { 85 | $this->get('', 'App\Controllers\AdminController:index'); 86 | $this->get('/', 'App\Controllers\AdminController:index'); 87 | // Node Mange 88 | $this->get('/node', 'App\Controllers\Admin\NodeController:index'); 89 | $this->get('/node/create', 'App\Controllers\Admin\NodeController:create'); 90 | $this->post('/node', 'App\Controllers\Admin\NodeController:add'); 91 | $this->get('/node/{id}/edit', 'App\Controllers\Admin\NodeController:edit'); 92 | $this->put('/node/{id}', 'App\Controllers\Admin\NodeController:update'); 93 | $this->delete('/node/{id}','App\Controllers\Admin\NodeController:delete'); 94 | $this->get('/node/{id}/delete', 'App\Controllers\Admin\NodeController:deleteGet'); 95 | 96 | // User Mange 97 | $this->get('/user', 'App\Controllers\Admin\UserController:index'); 98 | $this->get('/user/{id}/edit', 'App\Controllers\Admin\UserController:edit'); 99 | $this->put('/user/{id}', 'App\Controllers\Admin\UserController:update'); 100 | $this->delete('/user/{id}','App\Controllers\Admin\UserController:delete'); 101 | $this->get('/user/{id}/delete', 'App\Controllers\Admin\UserController:deleteGet'); 102 | 103 | $this->get('/profile', 'App\Controllers\AdminController:profile'); 104 | $this->get('/invite', 'App\Controllers\AdminController:invite'); 105 | $this->post('/invite', 'App\Controllers\AdminController:addInvite'); 106 | $this->get('/sys', 'App\Controllers\AdminController:sys'); 107 | $this->get('/logout', 'App\Controllers\AdminController:logout'); 108 | })->add(new Admin()); 109 | 110 | // API 111 | $app->group('/api', function () { 112 | $this->get('/token/{token}', 'App\Controllers\ApiController:token'); 113 | $this->post('/token', 'App\Controllers\ApiController:newToken'); 114 | $this->get('/node', 'App\Controllers\ApiController:node')->add(new Api()); 115 | $this->get('/user/{id}', 'App\Controllers\ApiController:userInfo')->add(new Api()); 116 | }); 117 | 118 | // Run Slim Routes for App 119 | $app->run(); 120 | 121 | -------------------------------------------------------------------------------- /views/default/auth/register.tpl: -------------------------------------------------------------------------------- 1 | {include file='auth/header.tpl'} 2 | 3 |
4 | 7 | 8 |
9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 |

注册即代表同意服务条款

35 |
36 | 37 |
38 | 39 |
40 | 41 | 46 | 47 | 52 | 53 | 已经注册?请登录 54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 75 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /views/default/admin/main.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$config["appName"]} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 36 | 74 |
75 | 76 | 77 | 78 | 79 | 132 | -------------------------------------------------------------------------------- /public/assets/public/plugins/slimScroll/jquery.slimscroll.min.js: -------------------------------------------------------------------------------- 1 | (function($){$.fn.extend({slimScroll:function(options){var defaults={width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:false,disableFadeOut:false,railVisible:false,railColor:"#333",railOpacity:.2,railDraggable:true,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:false,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"};var o=$.extend(defaults,options);this.each(function(){var isOverPanel,isOverBar,isDragg,queueHide,touchDif,barHeight,percentScroll,lastScroll,divS="
",minBarHeight=30,releaseScroll=false;var me=$(this);if(me.parent().hasClass(o.wrapperClass)){var offset=me.scrollTop();bar=me.parent().find("."+o.barClass);rail=me.parent().find("."+o.railClass);getBarHeight();if($.isPlainObject(options)){if("height"in options&&options.height=="auto"){me.parent().css("height","auto");me.css("height","auto");var height=me.parent().parent().height();me.parent().css("height",height);me.css("height",height)}if("scrollTo"in options){offset=parseInt(o.scrollTo)}else if("scrollBy"in options){offset+=parseInt(o.scrollBy)}else if("destroy"in options){bar.remove();rail.remove();me.unwrap();return}scrollContent(offset,false,true)}return}else if($.isPlainObject(options)){if("destroy"in options){return}}o.height=o.height=="auto"?me.parent().height():o.height;var wrapper=$(divS).addClass(o.wrapperClass).css({position:"relative",overflow:"hidden",width:o.width,height:o.height});me.css({overflow:"hidden",width:o.width,height:o.height,"-ms-touch-action":"none"});var rail=$(divS).addClass(o.railClass).css({width:o.size,height:"100%",position:"absolute",top:0,display:o.alwaysVisible&&o.railVisible?"block":"none","border-radius":o.railBorderRadius,background:o.railColor,opacity:o.railOpacity,zIndex:90});var bar=$(divS).addClass(o.barClass).css({background:o.color,width:o.size,position:"absolute",top:0,opacity:o.opacity,display:o.alwaysVisible?"block":"none","border-radius":o.borderRadius,BorderRadius:o.borderRadius,MozBorderRadius:o.borderRadius,WebkitBorderRadius:o.borderRadius,zIndex:99});var posCss=o.position=="right"?{right:o.distance}:{left:o.distance};rail.css(posCss);bar.css(posCss);me.wrap(wrapper);me.parent().append(bar);me.parent().append(rail);if(o.railDraggable){bar.bind("mousedown",function(e){var $doc=$(document);isDragg=true;t=parseFloat(bar.css("top"));pageY=e.pageY;$doc.bind("mousemove.slimscroll",function(e){currTop=t+e.pageY-pageY;bar.css("top",currTop);scrollContent(0,bar.position().top,false)});$doc.bind("mouseup.slimscroll",function(e){isDragg=false;hideBar();$doc.unbind(".slimscroll")});return false}).bind("selectstart.slimscroll",function(e){e.stopPropagation();e.preventDefault();return false})}rail.hover(function(){showBar()},function(){hideBar()});bar.hover(function(){isOverBar=true},function(){isOverBar=false});me.hover(function(){isOverPanel=true;showBar();hideBar()},function(){isOverPanel=false;hideBar()});if(window.navigator.msPointerEnabled){me.bind("MSPointerDown",function(e,b){if(e.originalEvent.targetTouches.length){touchDif=e.originalEvent.targetTouches[0].pageY}});me.bind("MSPointerMove",function(e){e.originalEvent.preventDefault();if(e.originalEvent.targetTouches.length){var diff=(touchDif-e.originalEvent.targetTouches[0].pageY)/o.touchScrollStep;scrollContent(diff,true);touchDif=e.originalEvent.targetTouches[0].pageY}})}else{me.bind("touchstart",function(e,b){if(e.originalEvent.touches.length){touchDif=e.originalEvent.touches[0].pageY}});me.bind("touchmove",function(e){if(!releaseScroll){e.originalEvent.preventDefault()}if(e.originalEvent.touches.length){var diff=(touchDif-e.originalEvent.touches[0].pageY)/o.touchScrollStep;scrollContent(diff,true);touchDif=e.originalEvent.touches[0].pageY}})}getBarHeight();if(o.start==="bottom"){bar.css({top:me.outerHeight()-bar.outerHeight()});scrollContent(0,true)}else if(o.start!=="top"){scrollContent($(o.start).position().top,null,true);if(!o.alwaysVisible){bar.hide()}}attachWheel();function _onWheel(e){if(!isOverPanel){return}var e=e||window.event;var delta=0;if(e.wheelDelta){delta=-e.wheelDelta/120}if(e.detail){delta=e.detail/3}var target=e.target||e.srcTarget||e.srcElement;if($(target).closest("."+o.wrapperClass).is(me.parent())){scrollContent(delta,true)}if(e.preventDefault&&!releaseScroll){e.preventDefault()}if(!releaseScroll){e.returnValue=false}}function scrollContent(y,isWheel,isJump){releaseScroll=false;var delta=y;var maxTop=me.outerHeight()-bar.outerHeight();if(isWheel){delta=parseInt(bar.css("top"))+y*parseInt(o.wheelStep)/100*bar.outerHeight();delta=Math.min(Math.max(delta,0),maxTop);delta=y>0?Math.ceil(delta):Math.floor(delta);bar.css({top:delta+"px"})}percentScroll=parseInt(bar.css("top"))/(me.outerHeight()-bar.outerHeight());delta=percentScroll*(me[0].scrollHeight-me.outerHeight());if(isJump){delta=y;var offsetTop=delta/me[0].scrollHeight*me.outerHeight();offsetTop=Math.min(Math.max(offsetTop,0),maxTop);bar.css({top:offsetTop+"px"})}me.scrollTop(delta);me.trigger("slimscrolling",~~delta);showBar();hideBar()}function attachWheel(){if(window.addEventListener){this.addEventListener("DOMMouseScroll",_onWheel,false);this.addEventListener("mousewheel",_onWheel,false)}else{document.attachEvent("onmousewheel",_onWheel)}}function getBarHeight(){barHeight=Math.max(me.outerHeight()/me[0].scrollHeight*me.outerHeight(),minBarHeight);bar.css({height:barHeight+"px"});var display=barHeight==me.outerHeight()?"none":"block";bar.css({display:display})}function showBar(){getBarHeight();clearTimeout(queueHide);if(percentScroll==~~percentScroll){releaseScroll=o.allowPageScroll;if(lastScroll!=percentScroll){var msg=~~percentScroll==0?"top":"bottom";me.trigger("slimscroll",msg)}}else{releaseScroll=false}lastScroll=percentScroll;if(barHeight>=me.outerHeight()){releaseScroll=true;return}bar.stop(true,true).fadeIn("fast");if(o.railVisible){rail.stop(true,true).fadeIn("fast")}}function hideBar(){if(!o.alwaysVisible){queueHide=setTimeout(function(){if(!(o.disableFadeOut&&isOverPanel)&&!isOverBar&&!isDragg){bar.fadeOut("slow");rail.fadeOut("slow")}},1e3)}}});return this}});$.fn.extend({slimscroll:$.fn.slimScroll})})(jQuery); -------------------------------------------------------------------------------- /public/assets/public/js/app.min.js: -------------------------------------------------------------------------------- 1 | /*! AdminLTE app.js 2 | * ================ 3 | * Main JS application file for AdminLTE v2. This file 4 | * should be included in all pages. It controls some layout 5 | * options and implements exclusive AdminLTE plugins. 6 | * 7 | * @Author Almsaeed Studio 8 | * @Support 9 | * @Email 10 | * @version 2.0.3 11 | * @license MIT 12 | */ 13 | "use strict";if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");$.AdminLTE={},$.AdminLTE.options={navbarMenuSlimscroll:!0,navbarMenuSlimscrollWidth:"3px",navbarMenuHeight:"200px",sidebarToggleSelector:"[data-toggle='offcanvas']",sidebarPushMenu:!0,sidebarSlimScroll:!0,enableBoxRefresh:!0,enableBSToppltip:!0,BSTooltipSelector:"[data-toggle='tooltip']",enableFastclick:!0,enableBoxWidget:!0,boxWidgetOptions:{boxWidgetIcons:{collapse:"fa fa-minus",open:"fa fa-plus",remove:"fa fa-times"},boxWidgetSelectors:{remove:'[data-widget="remove"]',collapse:'[data-widget="collapse"]'}},directChat:{enable:!0,contactToggleSelector:'[data-widget="chat-pane-toggle"]'},colors:{lightBlue:"#3c8dbc",red:"#f56954",green:"#00a65a",aqua:"#00c0ef",yellow:"#f39c12",blue:"#0073b7",navy:"#001F3F",teal:"#39CCCC",olive:"#3D9970",lime:"#01FF70",orange:"#FF851B",fuchsia:"#F012BE",purple:"#8E24AA",maroon:"#D81B60",black:"#222222",gray:"#d2d6de"},screenSizes:{xs:480,sm:768,md:992,lg:1200}},$(function(){var a=$.AdminLTE.options;$.AdminLTE.layout.activate(),$.AdminLTE.tree(".sidebar"),a.navbarMenuSlimscroll&&"undefined"!=typeof $.fn.slimscroll&&$(".navbar .menu").slimscroll({height:"200px",alwaysVisible:!1,size:"3px"}).css("width","100%"),a.sidebarPushMenu&&$.AdminLTE.pushMenu(a.sidebarToggleSelector),a.enableBSToppltip&&$(a.BSTooltipSelector).tooltip(),a.enableBoxWidget&&$.AdminLTE.boxWidget.activate(),a.enableFastclick&&"undefined"!=typeof FastClick&&FastClick.attach(document.body),a.directChat.enable&&$(a.directChat.contactToggleSelector).click(function(){var a=$(this).parents(".direct-chat").first();a.toggleClass("direct-chat-contacts-open")}),$('.btn-group[data-toggle="btn-toggle"]').each(function(){var a=$(this);$(this).find(".btn").click(function(b){a.find(".btn.active").removeClass("active"),$(this).addClass("active"),b.preventDefault()})})}),$.AdminLTE.layout={activate:function(){var a=this;a.fix(),a.fixSidebar(),$(window,".wrapper").resize(function(){a.fix(),a.fixSidebar()})},fix:function(){var a=$(".main-header").outerHeight()+$(".main-footer").outerHeight(),b=$(window).height(),c=$(".sidebar").height();$("body").hasClass("fixed")?$(".content-wrapper, .right-side").css("min-height",b-$(".main-footer").outerHeight()):b>=c?$(".content-wrapper, .right-side").css("min-height",b-a):$(".content-wrapper, .right-side").css("min-height",c)},fixSidebar:function(){return $("body").hasClass("fixed")?("undefined"==typeof $.fn.slimScroll&&console&&console.error("Error: the fixed layout requires the slimscroll plugin!"),void($.AdminLTE.options.sidebarSlimScroll&&"undefined"!=typeof $.fn.slimScroll&&($(".sidebar").slimScroll({destroy:!0}).height("auto"),$(".sidebar").slimscroll({height:$(window).height()-$(".main-header").height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"})))):void("undefined"!=typeof $.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"))}},$.AdminLTE.pushMenu=function(a){var b=this.options.screenSizes;$(a).click(function(a){a.preventDefault(),$(window).width()>b.sm-1?$("body").toggleClass("sidebar-collapse"):$("body").hasClass("sidebar-open")?($("body").removeClass("sidebar-open"),$("body").removeClass("sidebar-collapse")):$("body").addClass("sidebar-open")}),$(".content-wrapper").click(function(){$(window).width()<=b.sm-1&&$("body").hasClass("sidebar-open")&&$("body").removeClass("sidebar-open")})},$.AdminLTE.tree=function(a){var b=this;$("li a",$(a)).click(function(a){var c=$(this),d=c.next();if(d.is(".treeview-menu")&&d.is(":visible"))d.slideUp("normal",function(){d.removeClass("menu-open")}),d.parent("li").removeClass("active");else if(d.is(".treeview-menu")&&!d.is(":visible")){var e=c.parents("ul").first(),f=e.find("ul:visible").slideUp("normal");f.removeClass("menu-open");var g=c.parent("li");d.slideDown("normal",function(){d.addClass("menu-open"),e.find("li.active").removeClass("active"),g.addClass("active"),b.layout.fix()})}d.is(".treeview-menu")&&a.preventDefault()})},$.AdminLTE.boxWidget={activate:function(){var a=$.AdminLTE.options,b=this;$(a.boxWidgetOptions.boxWidgetSelectors.collapse).click(function(a){a.preventDefault(),b.collapse($(this))}),$(a.boxWidgetOptions.boxWidgetSelectors.remove).click(function(a){a.preventDefault(),b.remove($(this))})},collapse:function(a){var b=a.parents(".box").first(),c=b.find(".box-body, .box-footer");b.hasClass("collapsed-box")?(a.children(".fa-plus").removeClass("fa-plus").addClass("fa-minus"),c.slideDown(300,function(){b.removeClass("collapsed-box")})):(a.children(".fa-minus").removeClass("fa-minus").addClass("fa-plus"),c.slideUp(300,function(){b.addClass("collapsed-box")}))},remove:function(a){var b=a.parents(".box").first();b.slideUp()},options:$.AdminLTE.options.boxWidgetOptions},function(a){a.fn.boxRefresh=function(b){function c(a){a.append(f),e.onLoadStart.call(a)}function d(a){a.find(f).remove(),e.onLoadDone.call(a)}var e=a.extend({trigger:".refresh-btn",source:"",onLoadStart:function(){},onLoadDone:function(){}},b),f=a('
');return this.each(function(){if(""===e.source)return void(console&&console.log("Please specify a source first - boxRefresh()"));var b=a(this),f=b.find(e.trigger).first();f.click(function(a){a.preventDefault(),c(b),b.find(".box-body").load(e.source,function(){d(b)})})})}}(jQuery),function(a){a.fn.todolist=function(b){var c=a.extend({onCheck:function(){},onUncheck:function(){}},b);return this.each(function(){"undefined"!=typeof a.fn.iCheck?(a("input",this).on("ifChecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)}),a("input",this).on("ifUnchecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onUncheck.call(b)})):a("input",this).on("change",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)})})}}(jQuery); -------------------------------------------------------------------------------- /views/default/admin/node/create.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 节点添加 9 | Add Node 10 |

11 |
12 | 13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 |

如何使用自定义加密?

40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 |
48 | 49 |
50 | 51 | 52 |
53 | 54 |
55 | 56 | 57 |
58 | 59 |
60 | 61 | 62 |
63 |
64 | 65 | 70 | 75 | 76 | 79 | 80 |
81 |
82 |
83 |
84 |
85 | 86 | 138 | 139 | 140 | {include file='admin/footer.tpl'} -------------------------------------------------------------------------------- /views/default/user/main.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$config["appName"]} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 36 | 74 |
75 | 76 | 77 | 78 | 79 | 141 | -------------------------------------------------------------------------------- /app/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | user = Auth::getUser(); 24 | } 25 | 26 | public function index() 27 | { 28 | return $this->view()->display('user/index.tpl'); 29 | } 30 | 31 | public function node(){ 32 | $nodes = Node::where('type',1)->orderBy('sort')->get(); 33 | return $this->view()->assign('nodes',$nodes)->display('user/node.tpl'); 34 | } 35 | 36 | 37 | public function nodeInfo($request, $response, $args){ 38 | $id = $args['id']; 39 | $node = Node::find($id); 40 | 41 | if ($node == null){ 42 | 43 | } 44 | $ary['server'] = $node->server; 45 | $ary['server_port'] = $this->user->port; 46 | $ary['password'] = $this->user->passwd; 47 | $ary['method'] = $node->method; 48 | if($node->custom_method){ 49 | $ary['method'] = $this->user->method; 50 | } 51 | $json = json_encode($ary); 52 | $ssurl = $ary['method'].":".$this->user->passwd."@".$node->server.":".$this->user->port; 53 | $ssqr = "ss://".base64_encode($ssurl); 54 | return $this->view()->assign('json',$json)->assign('ssqr',$ssqr)->display('user/nodeinfo.tpl'); 55 | } 56 | 57 | public function profile(){ 58 | return $this->view()->display('user/profile.tpl'); 59 | } 60 | 61 | public function edit(){ 62 | return $this->view()->display('user/edit.tpl'); 63 | } 64 | 65 | 66 | 67 | public function invite(){ 68 | $codes = $this->user->inviteCodes(); 69 | return $this->view()->assign('codes',$codes)->display('user/invite.tpl'); 70 | } 71 | 72 | public function doInvite($request, $response, $args){ 73 | $n = $this->user->invite_num; 74 | if ($n < 1){ 75 | $res['ret'] = 0; 76 | return $response->getBody()->write(json_encode($res)); 77 | } 78 | for ($i = 0; $i < $n; $i++ ){ 79 | $char = Tools::genRandomChar(32); 80 | $code = new InviteCode(); 81 | $code->code = $char; 82 | $code->user_id = $this->user->id; 83 | $code->save(); 84 | } 85 | $this->user->invite_num = 0; 86 | $this->user->save(); 87 | $res['ret'] = 1; 88 | return $this->echoJson($response,$res); 89 | } 90 | 91 | public function sys(){ 92 | return $this->view()->assign('ana',"")->display('user/sys.tpl'); 93 | } 94 | 95 | public function updatePassword($request, $response, $args){ 96 | $oldpwd = $request->getParam('oldpwd'); 97 | $pwd = $request->getParam('pwd'); 98 | $repwd = $request->getParam('repwd'); 99 | $user = $this->user; 100 | if (!Hash::checkPassword($user->pass,$oldpwd)){ 101 | $res['ret'] = 0; 102 | $res['msg'] = "旧密码错误"; 103 | return $response->getBody()->write(json_encode($res)); 104 | } 105 | if($pwd != $repwd){ 106 | $res['ret'] = 0; 107 | $res['msg'] = "两次输入不符合"; 108 | return $response->getBody()->write(json_encode($res)); 109 | } 110 | 111 | if(strlen($pwd) < 8){ 112 | $res['ret'] = 0; 113 | $res['msg'] = "密码太短啦"; 114 | return $response->getBody()->write(json_encode($res)); 115 | } 116 | $hashPwd = Hash::passwordHash($pwd); 117 | $user->pass = $hashPwd; 118 | $user->save(); 119 | 120 | $res['ret'] = 1; 121 | $res['msg'] = "ok"; 122 | return $this->echoJson($response,$res); 123 | } 124 | 125 | public function updateSsPwd($request, $response, $args){ 126 | $user = Auth::getUser(); 127 | $pwd = $request->getParam('sspwd'); 128 | $user->updateSsPwd($pwd); 129 | $res['ret'] = 1; 130 | return $this->echoJson($response,$res); 131 | } 132 | 133 | public function updateMethod($request, $response, $args){ 134 | $user = Auth::getUser(); 135 | $method = $request->getParam('method'); 136 | $method = strtolower($method); 137 | $user->updateMethod($method); 138 | $res['ret'] = 1; 139 | return $this->echoJson($response,$res); 140 | } 141 | 142 | public function logout($request, $response, $args){ 143 | Auth::logout(); 144 | $newResponse = $response->withStatus(302)->withHeader('Location', '/auth/login'); 145 | return $newResponse; 146 | } 147 | 148 | public function doCheckIn($request, $response, $args){ 149 | if(!$this->user->isAbleToCheckin()){ 150 | $res['msg'] = "您似乎已经签到过了..."; 151 | $res['ret'] = 1; 152 | return $response->getBody()->write(json_encode($res)); 153 | } 154 | $traffic = rand(Config::get('checkinMin'),Config::get('checkinMax')); 155 | $this->user->transfer_enable = $this->user->transfer_enable+ Tools::toMB($traffic); 156 | $this->user->last_check_in_time = time(); 157 | $this->user->save(); 158 | $res['msg'] = sprintf("获得了 %u MB流量.",$traffic); 159 | $res['ret'] = 1; 160 | return $this->echoJson($response,$res); 161 | } 162 | 163 | public function kill($request, $response, $args){ 164 | return $this->view()->display('user/kill.tpl'); 165 | } 166 | 167 | public function handleKill($request, $response, $args){ 168 | $user = Auth::getUser(); 169 | $passwd = $request->getParam('passwd'); 170 | // check passwd 171 | $res = array(); 172 | if (!Hash::checkPassword($user->pass,$passwd)){ 173 | $res['ret'] = 0; 174 | $res['msg'] = " 密码错误"; 175 | return $this->echoJson($response,$res); 176 | } 177 | Auth::logout(); 178 | $user->delete(); 179 | $res['ret'] = 1; 180 | $res['msg'] = "GG!您的帐号已经从我们的系统中删除."; 181 | return $this->echoJson($response,$res); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /views/default/admin/node/edit.tpl: -------------------------------------------------------------------------------- 1 | {include file='admin/main.tpl'} 2 | 3 | 4 |
5 | 6 |
7 |

8 | 节点编辑 #{$node->id} 9 | Edit Node 10 |

11 |
12 | 13 | 14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 |

如何使用自定义加密?

40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 |
48 | 49 |
50 | 51 | 52 |
53 | 54 |
55 | 56 | 57 |
58 | 59 |
60 | 61 | 62 |
63 |
64 | 65 | 70 | 75 | 76 | 79 | 80 |
81 |
82 |
83 |
84 |
85 | 86 | 138 | 139 | 140 | {include file='admin/footer.tpl'} --------------------------------------------------------------------------------