├── .gitignore ├── Bootstrap ├── Autoload.php ├── Provider.php ├── WebServer.php └── Worker.php ├── Config ├── Cache │ └── readme.md ├── Config.php ├── Redis.php ├── Server.php └── Worker.php ├── Core ├── Cache │ ├── FileCache.php │ └── RedisCache.php ├── Cookie.php ├── RandomKey.php ├── Response.php └── Session.php ├── Example ├── StatisticClient.php └── client.php ├── LICENSE ├── Lib ├── Cache.php └── functions.php ├── Modules ├── admin.php ├── logger.php ├── logout.php ├── main.php ├── setting.php └── statistic.php ├── README.md ├── Views ├── admin.tpl.php ├── footer.tpl.php ├── header.tpl.php ├── log.tpl.php ├── login.tpl.php ├── main.tpl.php ├── setting.tpl.php └── statistic.tpl.php ├── Web ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.min.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ └── style.css ├── img │ ├── apple-touch-icon-114-precomposed.png │ ├── apple-touch-icon-144-precomposed.png │ ├── apple-touch-icon-57-precomposed.png │ ├── apple-touch-icon-72-precomposed.png │ ├── favicon.png │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── index.php └── js │ ├── bootstrap.min.js │ ├── highcharts.js │ ├── html5shiv.js │ ├── jquery.min.js │ ├── less-1.3.3.min.js │ └── scripts.js ├── data └── readme.md ├── doc ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── statistics.conf ├── web.php └── worker.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.pid 2 | *.log 3 | *cache.php 4 | data/statistic/* 5 | -------------------------------------------------------------------------------- /Bootstrap/Autoload.php: -------------------------------------------------------------------------------- 1 | $this->getModules($module), 60 | 'statistic' => $this->getStatistic($date, $module, $interface) 61 | )) . "\n"; 62 | $serv->send($fd, $buffer); 63 | break; 64 | case 'get_log': 65 | $buffer = json_encode($this->getStasticLog($module, $interface, $start_time, $end_time, $code, $msg, $offset, $count)) . "\n"; 66 | $serv->send($fd, $buffer); 67 | break; 68 | default: 69 | $serv->send($fd, 'pack err'); 70 | } 71 | } 72 | 73 | /** 74 | * 获取模块 75 | * 76 | * @param string $current_module 77 | * 78 | * @return array 79 | */ 80 | public function getModules($current_module = '') 81 | { 82 | $st_dir = Config::$dataPath . $this->statisticDir; 83 | $modules_name_array = array(); 84 | foreach (glob($st_dir . "/*", GLOB_ONLYDIR) as $module_file) { 85 | $tmp = explode("/", $module_file); 86 | $module = end($tmp); 87 | $modules_name_array[$module] = array(); 88 | if ($current_module == $module) { 89 | $st_dir = $st_dir . $current_module . '/'; 90 | $all_interface = array(); 91 | foreach (glob($st_dir . "*") as $file) { 92 | if (is_dir($file)) { 93 | continue; 94 | } 95 | list ($interface, $date) = explode(".", basename($file)); 96 | $all_interface[$interface] = $interface; 97 | } 98 | $modules_name_array[$module] = $all_interface; 99 | } 100 | } 101 | return $modules_name_array; 102 | } 103 | 104 | /** 105 | * 获得统计数据 106 | * 107 | * @param string $module 108 | * @param string $interface 109 | * @param int $date 110 | * 111 | * @return bool/string 112 | */ 113 | protected function getStatistic($date, $module, $interface) 114 | { 115 | if (empty($module) || empty($interface)) { 116 | return ''; 117 | } 118 | // log文件 119 | $log_file = Config::$dataPath . $this->statisticDir . "{$module}/{$interface}.{$date}"; 120 | 121 | $handle = @fopen($log_file, 'r'); 122 | if (!$handle) { 123 | return ''; 124 | } 125 | 126 | // 预处理统计数据,每5分钟一行 127 | // [time=>[ip=>['suc_count'=>xx, 'suc_cost_time'=>xx, 'fail_count'=>xx, 'fail_cost_time'=>xx, 'code_map'=>[code=>count, ..], ..], ..] 128 | $statistics_data = array(); 129 | while (!feof($handle)) { 130 | $line = fgets($handle, 4096); 131 | if ($line) { 132 | $explode = explode("\t", $line); 133 | if (count($explode) < 7) { 134 | continue; 135 | } 136 | list ($ip, $time, $suc_count, $suc_cost_time, $fail_count, $fail_cost_time, $code_map) = $explode; 137 | $time = ceil($time / 300) * 300; 138 | if (!isset($statistics_data[$time])) { 139 | $statistics_data[$time] = array(); 140 | } 141 | if (!isset($statistics_data[$time][$ip])) { 142 | $statistics_data[$time][$ip] = array( 143 | 'suc_count' => 0, 144 | 'suc_cost_time' => 0, 145 | 'fail_count' => 0, 146 | 'fail_cost_time' => 0, 147 | 'code_map' => array() 148 | ); 149 | } 150 | $statistics_data[$time][$ip]['suc_count'] += $suc_count; 151 | $statistics_data[$time][$ip]['suc_cost_time'] += round($suc_cost_time, 5); 152 | $statistics_data[$time][$ip]['fail_count'] += $fail_count; 153 | $statistics_data[$time][$ip]['fail_cost_time'] += round($fail_cost_time, 5); 154 | $code_map = json_decode(trim($code_map), true); 155 | if ($code_map && is_array($code_map)) { 156 | foreach ($code_map as $code => $count) { 157 | if (!isset($statistics_data[$time][$ip]['code_map'][$code])) { 158 | $statistics_data[$time][$ip]['code_map'][$code] = 0; 159 | } 160 | $statistics_data[$time][$ip]['code_map'][$code] += $count; 161 | } 162 | } 163 | } // end if 164 | } // end while 165 | 166 | fclose($handle); 167 | ksort($statistics_data); 168 | 169 | // 整理数据 170 | $statistics_str = ''; 171 | foreach ($statistics_data as $time => $items) { 172 | foreach ($items as $ip => $item) { 173 | $statistics_str .= "$ip\t$time\t{$item['suc_count']}\t{$item['suc_cost_time']}\t{$item['fail_count']}\t{$item['fail_cost_time']}\t" 174 | . json_encode($item['code_map']) . "\n"; 175 | } 176 | } 177 | return $statistics_str; 178 | } 179 | 180 | /** 181 | * 获取指定日志 182 | */ 183 | protected function getStasticLog($module, $interface, $start_time = '', $end_time = '', $code = '', $msg = '', $offset = '', $count = 100) 184 | { 185 | // log文件 186 | $log_file = Config::$dataPath . $this->logDir . (empty($start_time) ? date('Y-m-d') : date('Y-m-d', $start_time)); 187 | if (!is_readable($log_file)) { 188 | return array( 189 | 'offset' => 0, 190 | 'data' => '' 191 | ); 192 | } 193 | // 读文件 194 | $h = fopen($log_file, 'r'); 195 | 196 | // 如果有时间,则进行二分查找,加速查询 197 | if ($start_time && $offset == 0 && ($file_size = filesize($log_file)) > 1024000) { 198 | $offset = $this->binarySearch(0, $file_size, $start_time - 1, $h); 199 | $offset = $offset < 100000 ? 0 : $offset - 100000; 200 | } 201 | 202 | // 正则表达式 203 | $pattern = "/^([\d: \-]+)\t\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\t"; 204 | 205 | if ($module && $module != 'AllData') { 206 | $pattern .= $module . "::"; 207 | } else { 208 | $pattern .= ".*::"; 209 | } 210 | 211 | if ($interface && $module != 'AllData') { 212 | $pattern .= $interface . "\t"; 213 | } else { 214 | $pattern .= ".*\t"; 215 | } 216 | 217 | if ($code !== '') { 218 | $pattern .= "code:$code\t"; 219 | } else { 220 | $pattern .= "code:\d+\t"; 221 | } 222 | 223 | if ($msg) { 224 | $pattern .= "msg:$msg"; 225 | } 226 | 227 | $pattern .= '/'; 228 | 229 | // 指定偏移位置 230 | if ($offset > 0) { 231 | fseek($h, (int)$offset - 1); 232 | } 233 | 234 | // 查找符合条件的数据 235 | $now_count = 0; 236 | $log_buffer = ''; 237 | 238 | while (1) { 239 | if (feof($h)) { 240 | break; 241 | } 242 | // 读1行 243 | $line = fgets($h); 244 | if (preg_match($pattern, $line, $match)) { 245 | // 判断时间是否符合要求 246 | $time = strtotime($match[1]); 247 | if ($start_time) { 248 | if ($time < $start_time) { 249 | continue; 250 | } 251 | } 252 | if ($end_time) { 253 | if ($time > $end_time) { 254 | break; 255 | } 256 | } 257 | // 收集符合条件的log 258 | $log_buffer .= $line; 259 | if (++$now_count >= $count) { 260 | break; 261 | } 262 | } 263 | } 264 | // 记录偏移位置 265 | $offset = ftell($h); 266 | return array( 267 | 'offset' => $offset, 268 | 'data' => $log_buffer 269 | ); 270 | } 271 | 272 | /** 273 | * 日志二分查找法 274 | * 275 | * @param int $start_point 276 | * @param int $end_point 277 | * @param int $time 278 | * @param fd $fd 279 | * 280 | * @return int 281 | */ 282 | protected function binarySearch($start_point, $end_point, $time, $fd) 283 | { 284 | if ($end_point - $start_point < 65535) { 285 | return $start_point; 286 | } 287 | 288 | // 计算中点 289 | $mid_point = (int)(($end_point + $start_point) / 2); 290 | 291 | // 定位文件指针在中点 292 | fseek($fd, $mid_point - 1); 293 | 294 | // 读第一行 295 | $line = fgets($fd); 296 | if (feof($fd) || false === $line) { 297 | return $start_point; 298 | } 299 | 300 | // 第一行可能数据不全,再读一行 301 | $line = fgets($fd); 302 | if (feof($fd) || false === $line || trim($line) == '') { 303 | return $start_point; 304 | } 305 | 306 | // 判断是否越界 307 | $current_point = ftell($fd); 308 | if ($current_point >= $end_point) { 309 | return $start_point; 310 | } 311 | 312 | // 获得时间 313 | $tmp = explode("\t", $line); 314 | $tmp_time = strtotime($tmp[0]); 315 | 316 | // 判断时间,返回指针位置 317 | if ($tmp_time > $time) { 318 | return $this->binarySearch($start_point, $current_point, $time, $fd); 319 | } elseif ($tmp_time < $time) { 320 | return $this->binarySearch($current_point, $end_point, $time, $fd); 321 | } else { 322 | return $current_point; 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /Bootstrap/WebServer.php: -------------------------------------------------------------------------------- 1 | webPidPath = \Config\Worker::$webPidPath; 43 | } 44 | register_shutdown_function(array($this, 'handleFatal')); 45 | } 46 | 47 | /** 48 | * server start的时候调用 49 | * 50 | * @param unknown $serv 51 | */ 52 | public function onStart($serv) 53 | { 54 | //设置主进程名称 55 | global $argv; 56 | $this->setProcessName("php {$argv[0]}: statistics_web"); 57 | 58 | //保存进程master_pid文件比较好操作 59 | file_put_contents(BASEDIR . $this->webPidPath, $serv->master_pid); 60 | 61 | echo "\033[1A\n\033[K-----------------------\033[47;30m SWOOLE \033[0m-----------------------------\n\033[0m"; 62 | echo 'swoole version:' . swoole_version() . " PHP version:" . PHP_VERSION . "\n"; 63 | echo "------------------------\033[47;30m WORKERS \033[0m---------------------------\n"; 64 | echo "\033[47;30mMasterPid\033[0m", str_pad('', 65 | self::$_maxMasterPidLength + 2 - strlen('MasterPid')), "\033[47;30mManagerPid\033[0m", str_pad('', 66 | self::$_maxManagerPidLength + 2 - strlen('ManagerPid')), "\033[47;30mWorkerId\033[0m", str_pad('', 67 | self::$_maxWorkerIdLength + 2 - strlen('WorkerId')), "\033[47;30mWorkerPid\033[0m\n"; 68 | } 69 | 70 | /** 71 | * worker start时调用 72 | * 73 | * @param unknown $serv 74 | * @param int $worker_id 75 | */ 76 | public function onWorkerStart($serv, $worker_id) 77 | { 78 | global $argv; 79 | $worker_num = isset($serv->setting['worker_num']) ? $serv->setting['worker_num'] : 1; 80 | 81 | if ($worker_id >= $worker_num) { 82 | $this->setProcessName("php {$argv[0]}: task"); 83 | } else { 84 | $this->setProcessName("php {$argv[0]}: worker"); 85 | } 86 | usleep($worker_id); 87 | echo str_pad($serv->master_pid, self::$_maxMasterPidLength + 2), str_pad($serv->manager_pid, 88 | self::$_maxManagerPidLength + 2), str_pad($serv->worker_id, self::$_maxWorkerIdLength + 2), str_pad($serv->worker_pid, 89 | self::$_maxWorkerIdLength), "\n";; 90 | define('APPLICATION_PATH', dirname(__DIR__)); 91 | } 92 | 93 | /** 94 | * Set process name. 95 | * 96 | * @param $processName 97 | */ 98 | protected function setProcessName($processName) 99 | { 100 | if (PHP_OS === 'Darwin') { 101 | return; 102 | } 103 | swoole_set_process_name($processName); 104 | } 105 | 106 | /** 107 | * 当request时调用 108 | * 109 | * @param \swoole\http\request $request 110 | * @param \swoole\http\response $response 111 | */ 112 | public function onRequest($request, $response) 113 | { 114 | $_GET = $_POST = $_COOKIE = array(); 115 | $resp = \Core\Response::getInstance($response); 116 | $resp->setResponse($response); 117 | if (isset($request->get)) { 118 | $_GET = $request->get; 119 | } 120 | if (isset($request->post)) { 121 | $_POST = $request->post; 122 | } 123 | if (isset($request->cookie)) { 124 | $_COOKIE = $request->cookie; 125 | } 126 | try { 127 | ob_start(); 128 | include APPLICATION_PATH . '/Web/index.php'; 129 | $result = ob_get_contents(); 130 | ob_end_clean(); 131 | $response->header("Content-Type", "text/html;charset=utf-8"); 132 | $result = empty($result) ? 'No message' : $result; 133 | $response->end($result); 134 | unset($result); 135 | } catch (Exception $e) { 136 | var_dump($e); 137 | } 138 | } 139 | 140 | /** 141 | * 致命错误处理 142 | */ 143 | public function handleFatal() 144 | { 145 | $error = error_get_last(); 146 | if (isset($error['type'])) { 147 | switch ($error['type']) { 148 | case E_ERROR : 149 | $severity = 'ERROR:Fatal run-time errors. Errors that can not be recovered from. Execution of the script is halted'; 150 | break; 151 | case E_PARSE : 152 | $severity = 'PARSE:Compile-time parse errors. Parse errors should only be generated by the parser'; 153 | break; 154 | case E_DEPRECATED: 155 | $severity = 'DEPRECATED:Run-time notices. Enable this to receive warnings about code that will not work in future versions'; 156 | break; 157 | case E_CORE_ERROR : 158 | $severity = 'CORE_ERROR :Fatal errors at PHP startup. This is like an E_ERROR in the PHP core'; 159 | break; 160 | case E_COMPILE_ERROR : 161 | $severity = 'COMPILE ERROR:Fatal compile-time errors. This is like an E_ERROR generated by the Zend Scripting Engine'; 162 | break; 163 | default: 164 | $severity = 'OTHER ERROR'; 165 | break; 166 | } 167 | $message = $error['message']; 168 | $file = $error['file']; 169 | $line = $error['line']; 170 | $log = "$message ($file:$line)\nStack trace:\n"; 171 | $trace = debug_backtrace(); 172 | foreach ($trace as $i => $t) { 173 | if (!isset($t['file'])) { 174 | $t['file'] = 'unknown'; 175 | } 176 | if (!isset($t['line'])) { 177 | $t['line'] = 0; 178 | } 179 | if (!isset($t['function'])) { 180 | $t['function'] = 'unknown'; 181 | } 182 | $log .= "#$i {$t['file']}({$t['line']}): "; 183 | if (isset($t['object']) && is_object($t['object'])) { 184 | $log .= get_class($t['object']) . '->'; 185 | } 186 | $log .= "{$t['function']}()\n"; 187 | } 188 | if (isset($_SERVER['REQUEST_URI'])) { 189 | $log .= '[QUERY] ' . $_SERVER['REQUEST_URI']; 190 | } 191 | file_put_contents('data/web_error.log', $log); 192 | } 193 | } 194 | 195 | /** 196 | * 启动 197 | * 198 | * @param string $ip 199 | * @param int $port 200 | */ 201 | public function run($ip = "0.0.0.0", $port = 6666) 202 | { 203 | $webServer = new \swoole_http_server($ip, $port); 204 | $webServer->set(\Config\Server::getWebServerConfig()); 205 | $webServer->on('WorkerStart', array($this, 'onWorkerStart')); 206 | $webServer->on('request', array($this, 'onRequest')); 207 | $webServer->on('start', array($this, 'onStart')); 208 | $webServer->on('ManagerStart', function ($serv) { 209 | global $argv; 210 | $this->setProcessName("php {$argv[0]}: manager"); 211 | }); 212 | $webServer->start(); 213 | } 214 | } -------------------------------------------------------------------------------- /Bootstrap/Worker.php: -------------------------------------------------------------------------------- 1 | modid=>interface=>['code'=>[xx=>count,xx=>count],'suc_cost_time'=>xx,'fail_cost_time'=>xx, 'suc_count'=>xx, 'fail_count'=>xx] 61 | * 62 | * @var array 63 | */ 64 | protected $statisticData = array(); 65 | 66 | /** 67 | * 日志的buffer 68 | * 69 | * @var string 70 | */ 71 | protected $logBuffer = ''; 72 | 73 | /** 74 | * 存放统计数据的目录 75 | * 76 | * @var string 77 | */ 78 | protected $statisticDir = 'statistic/statistic/'; 79 | 80 | /** 81 | * 存放统计日志的目录 82 | * 83 | * @var string 84 | */ 85 | protected $logDir = 'statistic/log/'; 86 | 87 | /** 88 | * master pid path 89 | * 90 | * @var string 91 | */ 92 | protected $masterPidPath = '/data/master.pid'; 93 | protected $handleWorkerPort = 55656; 94 | protected $handleProviderPort = 55858; 95 | protected $udpFinderPort = 55859; 96 | /** 97 | * MasterPid命令时格式化输出 98 | * ManagerPid命令时格式化输出 99 | * WorkerId命令时格式化输出 100 | * WorkerPid命令时格式化输出 101 | * 102 | * @var int 103 | */ 104 | protected static $_maxMasterPidLength = 12; 105 | protected static $_maxManagerPidLength = 12; 106 | protected static $_maxWorkerIdLength = 12; 107 | protected static $_maxWorkerPidLength = 12; 108 | 109 | public function __construct() 110 | { 111 | $initData = \Config\Worker::getInitData(); 112 | foreach ($initData as $key => $val) { 113 | $this->$key = $val; 114 | } 115 | 116 | if (isset(\Config\Worker::$masterPidPath)) { 117 | $this->masterPidPath = \Config\Worker::$masterPidPath; 118 | } 119 | 120 | if (isset(\Config\Config::$ProviderPort)) { 121 | $this->handleProviderPort = \Config\Config::$ProviderPort; 122 | } 123 | 124 | if (isset(\Config\Config::$findProviderPort)) { 125 | $this->udpFinderPort = \Config\Config::$findProviderPort; 126 | } 127 | } 128 | 129 | public function run($ip = "0.0.0.0", $port = 55656, $mode = SWOOLE_PROCESS, $type = SWOOLE_SOCK_TCP) 130 | { 131 | if (empty($port)) { 132 | $port = $this->handleWorkerPort; 133 | } else { 134 | $this->handleWorkerPort = $port; 135 | } 136 | $serv = new \swoole_server($ip, $port, $mode, $type); //处理客户端发送的数据 137 | $serv->addlistener('0.0.0.0', $this->handleProviderPort, SWOOLE_SOCK_TCP); //处理统计页面请求的数据 138 | $serv->addlistener('0.0.0.0', $this->udpFinderPort, SWOOLE_SOCK_UDP); //recv udp broadcast 139 | $serv->config = \Config\Server::getServerConfig(); 140 | $serv->set($serv->config); 141 | $serv->on('Start', array($this, 'onStart')); 142 | $serv->on('WorkerStart', array($this, 'onWorkerStart')); 143 | $serv->on('Connect', array($this, 'onConnect')); 144 | $serv->on('Receive', array($this, 'onReceive')); 145 | $serv->on('Packet', array($this, 'onPacket')); 146 | $serv->on('Task', array($this, 'onTask')); 147 | $serv->on('Finish', array($this, 'onFinish')); 148 | $serv->on('WorkerError', array($this, 'onWorkerError')); 149 | $serv->on('Close', array($this, 'onClose')); 150 | $serv->on('WorkerStop', array($this, 'onWorkerStop')); 151 | $serv->on('Shutdown', array($this, 'onShutdown')); 152 | $serv->on('ManagerStart', function ($serv) { 153 | global $argv; 154 | $this->setProcessName("php {$argv[0]}: manager"); 155 | }); 156 | $serv->start(); 157 | } 158 | 159 | /** 160 | * onStart 回调函数 161 | * 162 | * @param \swoole\server $serv 163 | */ 164 | public function onStart(\swoole\server $serv) 165 | { 166 | //设置主进程名称 167 | global $argv; 168 | $this->setProcessName("php {$argv[0]}: statistics_master"); 169 | 170 | //保存进程master_pid文件比较好操作 171 | file_put_contents(BASEDIR . $this->masterPidPath, $serv->master_pid); 172 | 173 | echo "\033[1A\n\033[K-----------------------\033[47;30m SWOOLE \033[0m-----------------------------\n\033[0m"; 174 | echo 'swoole version:' . swoole_version() . " PHP version:" . PHP_VERSION . "\n"; 175 | echo "------------------------\033[47;30m WORKERS \033[0m---------------------------\n"; 176 | echo "\033[47;30mMasterPid\033[0m", str_pad('', 177 | self::$_maxMasterPidLength + 2 - strlen('MasterPid')), "\033[47;30mManagerPid\033[0m", str_pad('', 178 | self::$_maxManagerPidLength + 2 - strlen('ManagerPid')), "\033[47;30mWorkerId\033[0m", str_pad('', 179 | self::$_maxWorkerIdLength + 2 - strlen('WorkerId')), "\033[47;30mWorkerPid\033[0m\n"; 180 | } 181 | 182 | /** 183 | * 日志 184 | * 185 | * @param string $msg 186 | */ 187 | public function log($msg) 188 | { 189 | echo "#" . $msg . PHP_EOL; 190 | } 191 | 192 | /** 193 | * 解包 194 | * 195 | * @param string $buffer 196 | * 197 | * @return mixed 198 | */ 199 | public static function decode($buffer) 200 | { 201 | $length = unpack('N', $buffer)[1]; 202 | $string = substr($buffer, -$length); 203 | $data = json_decode($string, true); 204 | return $data; 205 | } 206 | 207 | /** 208 | * 进程启动 209 | * 210 | * @param \swoole\server $serv 211 | * @param int $worker_id 212 | */ 213 | public function onWorkerStart($serv, $worker_id) 214 | { 215 | $this->processRename($serv, $worker_id); 216 | // 初始化目录 217 | umask(0); 218 | $statistic_dir = Config::$dataPath . $this->statisticDir; 219 | if (!is_dir($statistic_dir)) { 220 | mkdir($statistic_dir, 0777, true); 221 | } 222 | $log_dir = Config::$dataPath . $this->logDir; 223 | if (!is_dir($log_dir)) { 224 | mkdir($log_dir, 0777, true); 225 | } 226 | } 227 | 228 | /** 229 | * Set process name. 230 | * 231 | * @param $processName 232 | */ 233 | protected function setProcessName($processName) 234 | { 235 | if (PHP_OS === 'Darwin') { 236 | return; 237 | } 238 | swoole_set_process_name($processName); 239 | } 240 | 241 | /** 242 | * 修改进程名 243 | * 244 | * @param \Swoole\Server $serv 245 | * @param int $worker_id 246 | */ 247 | public function processRename($serv, $worker_id) 248 | { 249 | global $argv; 250 | $worker_num = isset($serv->setting['worker_num']) ? $serv->setting['worker_num'] : 1; 251 | if ($worker_id >= $worker_num) { 252 | $this->setProcessName("php {$argv[0]}: task"); 253 | } else { 254 | $this->setProcessName("php {$argv[0]}: worker"); 255 | // 定时保存统计数据(不同进程数据隔离) 256 | $that = &$this; 257 | $serv->tick($this->write_period_length, function ($id) use ($that) { 258 | // echo 'tick one #worker_id:' . $worker_id . PHP_EOL; 259 | $that->writeStatisticsToDisk(); 260 | $that->writeLogToDisk(); 261 | }); 262 | 263 | $datapath = Config::$dataPath; 264 | $expireTime = $this->expired_time; 265 | // 定时清理统计数据 266 | $serv->tick($this->clear_period_length, function ($id) use ($datapath, $expireTime, $that) { 267 | // echo 'tick two #worker_id:' . $worker_id . PHP_EOL; 268 | $that->clearDisk($datapath . $this->statisticDir, $expireTime); 269 | $that->clearDisk($datapath . $this->logDir, $expireTime); 270 | }); 271 | } 272 | usleep($worker_id * 50000);//保证顺序输出格式 273 | echo str_pad($serv->master_pid, self::$_maxMasterPidLength + 2), str_pad($serv->manager_pid, 274 | self::$_maxManagerPidLength + 2), str_pad($serv->worker_id, self::$_maxWorkerIdLength + 2), str_pad($serv->worker_pid, 275 | self::$_maxWorkerIdLength), "\n";; 276 | } 277 | 278 | /** 279 | * 建立链接 280 | * 281 | * @param \swoole\server $serv 282 | * @param int $fd 283 | * @param int $from_id 284 | */ 285 | public function onConnect(\swoole_server $serv, $fd, $from_id) 286 | { 287 | echo "Worker#{$serv->worker_pid} Client[$fd@$from_id]: Connect.\n"; 288 | } 289 | 290 | /** 291 | * 接收数据 292 | * 293 | * @param \swoole\server $serv 294 | * @param int $fd 295 | * @param int $from_id 296 | * @param string $data 297 | * 298 | * @return mixed 299 | */ 300 | public function onReceive(\swoole_server $serv, $fd, $from_id, $data) 301 | { 302 | $data = self::decode($data); 303 | $connInfo = $serv->connection_info($fd, $from_id, false); 304 | if ($connInfo['server_port'] == $this->handleWorkerPort) { 305 | $module = $data['module']; 306 | $interface = $data['interface']; 307 | $cost_time = $data['cost_time']; 308 | $success = $data['success']; 309 | $time = $data['time']; 310 | $code = $data['code']; 311 | $msg = str_replace("\n", "
", $data['msg']); 312 | $ip = $serv->connection_info($fd)['remote_ip']; 313 | // 模块接口统计 314 | $this->collectStatistics($module, $interface, $cost_time, $success, $ip, $code, $msg); 315 | // 全局统计 316 | $this->collectStatistics('AllData', 'Statistics', $cost_time, $success, $ip, $code, $msg); 317 | // 失败记录日志 318 | if (!$success) { 319 | $this->logBuffer .= date('Y-m-d H:i:s', $time) . "\t$ip\t$module::$interface\tcode:$code\tmsg:$msg\n"; 320 | if (strlen($this->logBuffer) >= $this->max_log_buffer_size) { 321 | $this->writeLogToDisk(); 322 | } 323 | } 324 | } else { 325 | if ($connInfo['server_port'] == $this->handleProviderPort) { 326 | $serv->task(array($fd, $data)); 327 | } else { 328 | if ($connInfo['server_port'] == $this->udpFinderPort) { 329 | if (empty($data)) { 330 | return false; 331 | } 332 | // 无法解析的包 333 | if (empty($data['cmd']) || $data['cmd'] != 'REPORT_IP') { 334 | return false; 335 | } 336 | return $serv->send($fd, json_encode(array('result' => 'ok'))); 337 | } else { 338 | echo '端口错误' . PHP_EOL; 339 | } 340 | } 341 | } 342 | } 343 | 344 | /** 345 | * 接收数据 UDP 346 | * 347 | * @param \swoole\server $serv 348 | * @param string $data 349 | * @param array $connInfo 350 | * 351 | * @return mixed 352 | */ 353 | public function onPacket(\swoole_server $serv, $data, $connInfo) 354 | { 355 | $data = self::decode($data); 356 | 357 | if ($connInfo['server_port'] == $this->udpFinderPort) { 358 | if (empty($data)) { 359 | return false; 360 | } 361 | // 无法解析的包 362 | if (empty($data['cmd']) || $data['cmd'] != 'REPORT_IP') { 363 | return false; 364 | } 365 | $ret = $serv->sendto($connInfo['server_socket'], $connInfo['port'], json_encode(array('result' => 'ok'))); 366 | if (!$ret) { 367 | $this->log("udp send error. code:".swoole_errno().",msg:".swoole_strerror(swoole_errno())); 368 | } 369 | } else { 370 | echo '端口错误' . PHP_EOL; 371 | } 372 | } 373 | 374 | /** 375 | * 收集统计数据 376 | * 377 | * @param string $module 378 | * @param string $interface 379 | * @param float $cost_time 380 | * @param int $success 381 | * @param string $ip 382 | * @param int $code 383 | * @param string $msg 384 | * 385 | * @return void 386 | */ 387 | protected function collectStatistics($module, $interface, $cost_time, $success, $ip, $code, $msg) 388 | { 389 | // 统计相关信息 390 | if (!isset($this->statisticData[$ip])) { 391 | $this->statisticData[$ip] = array(); 392 | } 393 | if (!isset($this->statisticData[$ip][$module])) { 394 | $this->statisticData[$ip][$module] = array(); 395 | } 396 | if (!isset($this->statisticData[$ip][$module][$interface])) { 397 | $this->statisticData[$ip][$module][$interface] = array( 398 | 'code' => array(), 399 | 'suc_cost_time' => 0, 400 | 'fail_cost_time' => 0, 401 | 'suc_count' => 0, 402 | 'fail_count' => 0 403 | ); 404 | } 405 | if (!isset($this->statisticData[$ip][$module][$interface]['code'][$code])) { 406 | $this->statisticData[$ip][$module][$interface]['code'][$code] = 0; 407 | } 408 | $this->statisticData[$ip][$module][$interface]['code'][$code]++; 409 | if ($success) { 410 | $this->statisticData[$ip][$module][$interface]['suc_cost_time'] += $cost_time; 411 | $this->statisticData[$ip][$module][$interface]['suc_count']++; 412 | } else { 413 | $this->statisticData[$ip][$module][$interface]['fail_cost_time'] += $cost_time; 414 | $this->statisticData[$ip][$module][$interface]['fail_count']++; 415 | } 416 | } 417 | 418 | /** 419 | * 将统计数据写入磁盘 420 | * 421 | * @return void 422 | */ 423 | public function writeStatisticsToDisk() 424 | { 425 | $time = time(); 426 | // 循环将每个ip的统计数据写入磁盘 427 | foreach ($this->statisticData as $ip => $mod_if_data) { 428 | foreach ($mod_if_data as $module => $items) { 429 | // 文件夹不存在则创建一个 430 | $file_dir = Config::$dataPath . $this->statisticDir . $module; 431 | if (!is_dir($file_dir)) { 432 | umask(0); 433 | mkdir($file_dir, 0777, true); 434 | } 435 | // 依次写入磁盘 436 | foreach ($items as $interface => $data) { 437 | file_put_contents($file_dir . "/{$interface}." . date('Y-m-d'), 438 | "$ip\t$time\t{$data['suc_count']}\t{$data['suc_cost_time']}\t{$data['fail_count']}\t{$data['fail_cost_time']}\t" 439 | . json_encode($data['code']) . "\n", FILE_APPEND | LOCK_EX); 440 | } 441 | } 442 | } 443 | // 清空统计 444 | $this->statisticData = array(); 445 | } 446 | 447 | /** 448 | * 将日志数据写入磁盘 449 | * 450 | * @return void 451 | */ 452 | public function writeLogToDisk() 453 | { 454 | // 没有统计数据则返回 455 | if (empty($this->logBuffer)) { 456 | return; 457 | } 458 | // 写入磁盘 459 | file_put_contents(Config::$dataPath . $this->logDir . date('Y-m-d'), $this->logBuffer, FILE_APPEND | LOCK_EX); 460 | $this->logBuffer = ''; 461 | } 462 | 463 | /** 464 | * task任务 465 | * 466 | * @param \swoole\server $serv 467 | * @param int $task_id 468 | * @param int $from_id 469 | * @param int $data 470 | * 471 | * @return mixed 472 | */ 473 | public function onTask(\swoole_server $serv, $task_id, $from_id, $data) 474 | { 475 | list($fd, $req) = $data; 476 | $provider = \Bootstrap\Provider::getInstance(); 477 | $provider->message($serv, $fd, $from_id, $req); 478 | } 479 | 480 | /** 481 | * task执行完毕调用 482 | * 483 | * @param \swoole\server $serv 484 | * @param int $task_id 485 | * @param mixed $data 486 | */ 487 | public function onFinish(\swoole_server $serv, $task_id, $data) 488 | { 489 | //保留回调函数,暂时不用 490 | } 491 | 492 | /** 493 | * worker出现问题调用 494 | * 495 | * @param \swoole\server $serv 496 | * @param int $worker_id 497 | * @param int $worker_pid 498 | * @param int $exit_code 499 | */ 500 | public function onWorkerError(\swoole_server $serv, $worker_id, $worker_pid, $exit_code) 501 | { 502 | echo "worker abnormal exit. WorkerId=$worker_id|Pid=$worker_pid|ExitCode=$exit_code\n"; 503 | } 504 | 505 | /** 506 | * 清除磁盘数据 507 | * 508 | * @param string $file 509 | * @param int $exp_time 510 | */ 511 | public function clearDisk($file = null, $exp_time = 86400) 512 | { 513 | $time_now = time(); 514 | //判断是否是文件 515 | if (is_file($file)) { 516 | $mtime = filemtime($file); 517 | if (!$mtime) { 518 | $this->notice("filemtime $file fail"); 519 | return; 520 | } 521 | if ($time_now - $mtime > $exp_time) { 522 | unlink($file); 523 | } 524 | return; 525 | } 526 | //遍历该目录下的日志文件,判断是否过期,过期删除 527 | foreach (glob($file . "/*") as $file_name) { 528 | $this->clearDisk($file_name, $exp_time); 529 | } 530 | } 531 | 532 | /** 533 | * 链接断开 534 | * 535 | * @param \swoole\swoole $serv 536 | * @param int $fd 537 | * @param int $from_id 538 | */ 539 | public function onClose($serv, $fd, $from_id) 540 | { 541 | $this->log("Worker#{$serv->worker_pid} Client[$fd@$from_id]: fd=$fd is closed"); 542 | } 543 | 544 | /** 545 | * 关闭进程 546 | * 547 | * @param \swoole\server $serv 548 | * @param int $worker_id 549 | */ 550 | public function onWorkerStop($serv, $worker_id) 551 | { 552 | echo "WorkerStop[$worker_id]|pid=" . $serv->worker_pid . ".\n"; 553 | } 554 | 555 | /** 556 | * 关闭服务器 557 | * 558 | * @param \swoole\server $serv 559 | */ 560 | public function onShutdown($serv) 561 | { 562 | echo "Server: onShutdown\n"; 563 | } 564 | 565 | } -------------------------------------------------------------------------------- /Config/Cache/readme.md: -------------------------------------------------------------------------------- 1 | 存配置缓存信息 -------------------------------------------------------------------------------- /Config/Config.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 13 | 'port' => '6379', 14 | 'database' => 0 15 | ); 16 | return $config; 17 | } 18 | 19 | public static function getSessionConfig() { 20 | $config = array( 21 | 'host' => '127.0.0.1', 22 | 'port' => '6379', 23 | 'database' => 3 24 | ); 25 | return $config; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Config/Server.php: -------------------------------------------------------------------------------- 1 | 10, 20 | // 协议 21 | 'open_length_check' => true, 22 | 'package_length_type' => 'N', 23 | 'package_length_offset' => 0, 24 | 'package_body_start' => 4, 25 | 'package_max_length' => 8192, 26 | 27 | 'task_ipc_mode' => 2, 28 | 'task_worker_num' => 2, 29 | 'task_max_request' => 500, // 防止内存泄漏 30 | 31 | 'user' => 'xmc', 32 | 'group' => 'xmc', 33 | 'log_file' => 'data/server.log', 34 | 'heartbeat_check_interval' => 60, 35 | 'heartbeat_idle_time' => 300, 36 | 'daemonize' => false // 守护进程改成true 37 | ); 38 | return $config; 39 | } 40 | 41 | /** 42 | * 获取web server配置 43 | * @return multitype:number string boolean 44 | */ 45 | public static function getWebServerConfig() 46 | { 47 | $config = array( 48 | 'worker_num' => 1, // worker进程数量 49 | 'max_request' => 1000, // 最大请求次数,当请求大于它时,将会自动重启该worker 50 | 'dispatch_mode' => 1, 51 | 'log_file' => 'data/web.log', 52 | 'daemonize' => false, // 守护进程设置成true 53 | ); 54 | return $config; 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Config/Worker.php: -------------------------------------------------------------------------------- 1 | 1024000, //最大日志buffer,大于这个值就写磁盘 19 | 'write_period_length' => 50000, //多长时间写一次数据到磁盘(单位:毫秒) 20 | 'clear_period_length' => 100000, //多长时间清理一次老的磁盘数据(单位:毫秒) 21 | 'expired_time' => 31536000, //数据多长时间过期,过期删除统计数据(单位:毫秒) 22 | 'logBufferKey' => 'logBuffer', //日志的redis buffer key 23 | 'statisticDir' => 'statistic/statistic/', //存放统计数据的目录 24 | 'logDir' => 'statistic/log/', //存放统计日志的目录 25 | 'statisticDataKey' => 'statisticData', //redis统计数据 key 26 | ); 27 | return $config; 28 | } 29 | 30 | static public $masterPidPath = '/data/master.pid'; //worker master pid path 31 | static public $webPidPath = '/data/web.pid'; //web master pid path 32 | } -------------------------------------------------------------------------------- /Core/Cache/FileCache.php: -------------------------------------------------------------------------------- 1 | config = $config; 30 | } 31 | 32 | /** 33 | * 获取文件名 34 | * @param unknown $key 35 | * @return string 36 | */ 37 | protected function getFileName($key) 38 | { 39 | $file = $this->config['cache_dir'] . '/' . trim(str_replace('_', '/', $key), '/'); 40 | $dir = dirname($file); 41 | if (! is_dir($dir)) { 42 | mkdir($dir, 0755, true); 43 | } 44 | return $file; 45 | } 46 | 47 | /** 48 | * 设置缓存 49 | * @param unknown $key 50 | * @param unknown $value 51 | * @param number $timeout 52 | * @return number 53 | */ 54 | function set($key, $value, $timeout = 0) 55 | { 56 | $file = $this->getFileName($key); 57 | $data["value"] = $value; 58 | $data["timeout"] = $timeout; 59 | $data["mktime"] = time(); 60 | $res = file_put_contents($file, serialize($data)); 61 | return $res; 62 | } 63 | 64 | /** 65 | * 获取缓存数据 66 | * @param unknown $key 67 | * @return boolean|mixed 68 | */ 69 | function get($key) 70 | { 71 | $file = $this->getFileName($key); 72 | if (! is_file($file)) 73 | return false; 74 | $data = unserialize(file_get_contents($file)); 75 | if (empty($data) or ! isset($data['timeout']) or ! isset($data["value"])) { 76 | return false; 77 | } 78 | // 已过期 79 | if ($data["timeout"] != 0 and ($data["mktime"] + $data["timeout"]) < time()) { 80 | $this->delete($key); 81 | return false; 82 | } 83 | return $data['value']; 84 | } 85 | 86 | /** 87 | * 删除 88 | * @param unknown $key 89 | * @return boolean 90 | */ 91 | function delete($key) 92 | { 93 | $file = $this->getFileName($key); 94 | return unlink($file); 95 | } 96 | } -------------------------------------------------------------------------------- /Core/Cache/RedisCache.php: -------------------------------------------------------------------------------- 1 | config = $config; 25 | } 26 | 27 | /** 28 | * 获取redis实例 29 | * 30 | * @return \Redis 31 | */ 32 | protected function getRedis() 33 | { 34 | if (empty($this->redis) || !$this->redis->info()) { 35 | $this->redis = new \Redis(); 36 | if (empty($this->config)) { 37 | $this->config = \Config\Redis::getSessionConfig(); 38 | } 39 | $res = $this->redis->connect($this->config['host'], $this->config['port']); 40 | if (isset($this->config['password'])) { 41 | $this->redis->auth($this->config['password']); 42 | } 43 | $this->redis->select($this->config['database']); 44 | if (empty($res)) { 45 | echo "connect Redis failed!\n"; 46 | } 47 | } 48 | return $this->redis; 49 | } 50 | 51 | /** 52 | * 设置缓存 53 | * 54 | * @param string $key 55 | * @param mixed $value 56 | * @param int $timeout 57 | * 58 | * @return number 59 | */ 60 | function set($key, $value, $timeout = 0) 61 | { 62 | $data["value"] = $value; 63 | $data["timeout"] = $timeout; 64 | $data["mktime"] = time(); 65 | $res = $this->getRedis()->set($key, json_encode($data)); 66 | return $res; 67 | } 68 | 69 | /** 70 | * 获取缓存数据 71 | * 72 | * @param string $key 73 | * 74 | * @return boolean|mixed 75 | */ 76 | function get($key) 77 | { 78 | $result = $this->getRedis()->get($key); 79 | $data = json_decode($result, true); 80 | 81 | if (empty($data) or !isset($data['timeout']) or !isset($data["value"])) { 82 | return false; 83 | } 84 | // 已过期 85 | if ($data["timeout"] != 0 and ($data["mktime"] + $data["timeout"]) < time()) { 86 | $this->delete($key); 87 | return false; 88 | } 89 | return $data['value']; 90 | } 91 | 92 | /** 93 | * 删除 94 | * 95 | * @param string $key 96 | * 97 | * @return boolean 98 | */ 99 | function delete($key) 100 | { 101 | $result = $this->getRedis()->del($key); 102 | return $result; 103 | } 104 | } -------------------------------------------------------------------------------- /Core/Cookie.php: -------------------------------------------------------------------------------- 1 | response = $response; 33 | } 34 | 35 | public function get($key, $default = null) 36 | { 37 | if (! isset($_COOKIE[$key])) { 38 | return $default; 39 | } else { 40 | return $_COOKIE[$key]; 41 | } 42 | } 43 | 44 | public function set($key, $value, $expire = 0) 45 | { 46 | if ($expire != 0) { 47 | $expire = time() + $expire; 48 | } 49 | $this->response->cookie($key, $value, $expire, self::$path, self::$domain, self::$secure, self::$httponly); 50 | } 51 | 52 | public function delete($key) 53 | { 54 | unset($_COOKIE[$key]); 55 | $this->set($key, null); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Core/RandomKey.php: -------------------------------------------------------------------------------- 1 | response; 29 | } 30 | 31 | public function setResponse($response) 32 | { 33 | if (!self::$instance) { 34 | self::$instance = new self(); 35 | } 36 | $this->response = $response; 37 | return true; 38 | } 39 | } -------------------------------------------------------------------------------- /Core/Session.php: -------------------------------------------------------------------------------- 1 | response 29 | * @return NULL 30 | */ 31 | public function __construct() 32 | { 33 | if (! $this->cache) { 34 | $this->cache = new \Core\Cache\FileCache(); 35 | } 36 | if (! $this->cookie) { 37 | $this->cookie = \Core\Cookie::getInstance(); 38 | } 39 | } 40 | 41 | public static function getInstance($response) 42 | { 43 | if (!self::$instance) { 44 | self::$instance = new self($response); 45 | } 46 | self::$instance->cookie->setResponse($response); 47 | return self::$instance; 48 | } 49 | 50 | 51 | public function start($readonly = false) 52 | { 53 | $this->readonly = $readonly; 54 | $this->open = true; 55 | $sessid = $this->cookie->get(self::$cookie_key); 56 | if (empty($sessid)) { 57 | $sessid = \Core\RandomKey::randmd5(40); 58 | $this->cookie->set(self::$cookie_key, $sessid, self::$cache_life); 59 | } 60 | $_SESSION = $this->load($sessid); 61 | } 62 | 63 | public function load($sessId) 64 | { 65 | $this->sessID = $sessId; 66 | $data = $this->get($sessId); 67 | if ($data) { 68 | return unserialize($data); 69 | } else { 70 | return array(); 71 | } 72 | } 73 | 74 | public function save() 75 | { 76 | return $this->set($this->sessID, serialize($_SESSION)); 77 | } 78 | 79 | /** 80 | * 打开Session 81 | * @param String $pSavePath 82 | * @param String $pSessName 83 | * @return Bool TRUE/FALSE 84 | */ 85 | public function open($save_path = '', $sess_name = '') 86 | { 87 | self::$cache_prefix = $save_path . '_' . $sess_name; 88 | return true; 89 | } 90 | 91 | /** 92 | * 关闭Session 93 | * @param NULL 94 | * @return Bool TRUE/FALSE 95 | */ 96 | public function close() 97 | { 98 | return true; 99 | } 100 | 101 | /** 102 | * 读取Session 103 | * @param String $sessId 104 | * @return Bool TRUE/FALSE 105 | */ 106 | public function get($sessId) 107 | { 108 | $session = $this->cache->get(self::$cache_prefix . $sessId); 109 | // 先读数据,如果没有,就初始化一个 110 | if (! empty($session)) { 111 | return $session; 112 | } else { 113 | return array(); 114 | } 115 | } 116 | 117 | /** 118 | * 设置Session的值 119 | * @param String $wSessId 120 | * @param String $wData 121 | * @return Bool true/FALSE 122 | */ 123 | public function set($sessId, $session = '') 124 | { 125 | $key = self::$cache_prefix . $sessId; 126 | $ret = $this->cache->set($key, $session, self::$cache_life); 127 | return $ret; 128 | } 129 | 130 | /** 131 | * 销毁Session 132 | * @param String $wSessId 133 | * @return Bool true/FALSE 134 | */ 135 | public function delete($sessId = '') 136 | { 137 | $sessId = empty($sessId) ? $this->sessID : $sessId; 138 | return $this->cache->delete(self::$cache_prefix . $sessId); 139 | } 140 | 141 | /** 142 | * 内存回收 143 | * @param NULL 144 | * @return Bool true/FALSE 145 | */ 146 | public function gc() 147 | { 148 | return true; 149 | } 150 | 151 | /** 152 | * 初始化Session,配置Session 153 | * @param NULL 154 | * @return Bool true/FALSE 155 | */ 156 | function initSess() 157 | { 158 | // 不使用 GET/POST 变量方式 159 | ini_set('session.use_trans_sid', 0); 160 | // 设置垃圾回收最大生存时间 161 | ini_set('session.gc_maxlifetime', self::$cache_life); 162 | // 使用 COOKIE 保存 SESSION ID 的方式 163 | ini_set('session.use_cookies', 1); 164 | ini_set('session.cookie_path', '/'); 165 | // 多主机共享保存 SESSION ID 的 COOKIE 166 | ini_set('session.cookie_domain', self::$sess_domain); 167 | // 将 session.save_handler 设置为 user,而不是默认的 files 168 | session_module_name('user'); 169 | // 定义 SESSION 各项操作所对应的方法名 170 | session_set_save_handler( 171 | array($this,'open'), 172 | array($this,'close'), 173 | array($this,'get'), 174 | array($this,'set'), 175 | array($this,'delete'), 176 | array($this,'gc') 177 | ); 178 | session_start(); 179 | return true; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Example/StatisticClient.php: -------------------------------------------------------------------------------- 1 | [interface=>time_start, interface=>time_start . 12 | * ..], module=>[interface=>time_start ..], ... ] 13 | * @var array 14 | */ 15 | protected static $timeMap = array(); 16 | 17 | protected static $client; 18 | 19 | /** 20 | * 模块接口上报消耗时间记时 21 | * @param string $module 22 | * @param string $interface 23 | * @return void 24 | */ 25 | public static function tick($module = '', $interface = '') 26 | { 27 | return self::$timeMap[$module][$interface] = microtime(true); 28 | } 29 | 30 | /** 31 | * 上报统计数据 32 | * 33 | * @param string $module 34 | * @param string $interface 35 | * @param bool $success 36 | * @param int $code 37 | * @param string $msg 38 | * @param string $report_address 39 | * @return boolean 40 | */ 41 | public static function report($module, $interface, $success, $code, $msg, $report_address = '') 42 | { 43 | $report_address = $report_address ? $report_address : '127.0.0.1:55656'; 44 | if (isset(self::$timeMap[$module][$interface]) && self::$timeMap[$module][$interface] > 0) { 45 | $time_start = self::$timeMap[$module][$interface]; 46 | self::$timeMap[$module][$interface] = 0; 47 | } else 48 | if (isset(self::$timeMap['']['']) && self::$timeMap[''][''] > 0) { 49 | $time_start = self::$timeMap['']['']; 50 | self::$timeMap[''][''] = 0; 51 | } else { 52 | $time_start = microtime(true); 53 | } 54 | 55 | $cost_time = microtime(true) - $time_start; 56 | $bin_data = Protocol::encode($module, $interface, $cost_time, $success, $code, $msg); 57 | if (extension_loaded('swoole')) { 58 | if (! self::$client || ! self::$client->isConnected()) { 59 | self::$client = new swoole_client(SWOOLE_TCP , SWOOLE_SOCK_SYNC); 60 | list($ip, $port) = explode(':', $report_address); 61 | self::$client->connect($ip, $port); 62 | } 63 | self::$client->send($bin_data); 64 | self::$client->close(); 65 | self::$client = null; 66 | } else { 67 | return self::sendData($report_address, $bin_data); 68 | } 69 | } 70 | 71 | /** 72 | * 发送数据给统计系统 73 | * @param string $address 74 | * @param string $buffer 75 | * @return boolean 76 | */ 77 | public static function sendData($address, $buffer, $timeout = 10) 78 | { 79 | $socket = stream_socket_client('tcp://'.$address, $errno, $errmsg, $timeout); 80 | if (! $socket) { 81 | return false; 82 | } 83 | stream_set_timeout($socket, $timeout); 84 | return stream_socket_sendto($socket, $buffer) == strlen($buffer); 85 | } 86 | } 87 | 88 | /** 89 | * 协议swoole 90 | * @author xmc 91 | */ 92 | class Protocol 93 | { 94 | /** 95 | * 编码 96 | * @param string $module 97 | * @param string $interface 98 | * @param float $cost_time 99 | * @param int $success 100 | * @param int $code 101 | * @param string $msg 102 | * @return string 103 | */ 104 | public static function encode($module, $interface, $cost_time, $success, $code = 0, $msg = '') 105 | { 106 | $data = array( 107 | 'module' => $module, 108 | 'interface' => $interface, 109 | 'cost_time' => $cost_time, 110 | 'success' => $success, 111 | 'time' => time(), 112 | 'code' => $code, 113 | 'msg' => $msg 114 | ); 115 | $string = json_encode($data); 116 | $packData = pack('N', strlen($string)).$string; 117 | // echo strlen($string).$string.PHP_EOL;//log 118 | return $packData; 119 | } 120 | 121 | /** 122 | * 解码 123 | */ 124 | public static function decode($buffer) 125 | { 126 | $length = unpack('N', $buffer)[1]; 127 | $string = substr($buffer, -$length); 128 | $data = json_decode($string, true); 129 | return $data; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Example/client.php: -------------------------------------------------------------------------------- 1 | 'xmc','password'=>'123456'); 13 | return $res; 14 | } 15 | 16 | public static function addInfo() 17 | { 18 | $res = array(); 19 | $res = array('name'=>'xmc','password'=>'123456'); 20 | return $res; 21 | } 22 | 23 | public static function getErrCode() 24 | { 25 | $errcode = 10001; 26 | return $errcode; 27 | } 28 | 29 | public static function getErrMsg() 30 | { 31 | $errmsg = '添加用户失败'; 32 | return $errmsg; 33 | } 34 | } 35 | 36 | include 'StatisticClient.php'; 37 | 38 | // 统计开始 39 | StatisticClient::tick("User", 'addInfo'); 40 | // 统计的产生,接口调用是否成功、错误码、错误日志 41 | $success = true; $code = 0; $msg = ''; 42 | // 假如有个User::getInfo方法要监控 43 | $user_info = User::addInfo(); 44 | if(!$user_info){ 45 | // 标记失败 46 | $success = false; 47 | // 获取错误码,假如getErrCode()获得 48 | $code = User::getErrCode(); 49 | // 获取错误日志,假如getErrMsg()获得 50 | $msg = User::getErrMsg(); 51 | } 52 | // 上报结果 53 | $res = StatisticClient::report('User', 'addInfo', $success, $code, $msg); 54 | 55 | echo "done over...\n"; 56 | var_dump($user_info,$res); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 小eyes 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 | -------------------------------------------------------------------------------- /Lib/Cache.php: -------------------------------------------------------------------------------- 1 | req_buf, 'ip:port'=>req_buf, ...] 19 | * @return multitype:unknown string 20 | */ 21 | function multiRequest($request_buffer_array) 22 | { 23 | \Statistics\Lib\Cache::$lastSuccessIpArray = array(); 24 | $client_array = $sock_to_ip = $ip_list = array(); 25 | foreach ($request_buffer_array as $address => $buffer) { 26 | list ($ip, $port) = explode(':', $address); 27 | $ip_list[$ip] = $ip; 28 | $client = new swoole_client(SWOOLE_TCP | SWOOLE_KEEP, SWOOLE_SOCK_SYNC); 29 | $client->connect($ip, $port); 30 | if (! $client) { 31 | continue; 32 | } 33 | $client_array[$address] = $client; 34 | $client_array[$address]->send(encode($buffer)); 35 | $sock_to_address[(int) $client->sock] = $address; 36 | } 37 | $read = $client_array; 38 | $write = $except = $read_buffer = array(); 39 | $time_start = microtime(true); 40 | $timeout = 0.99; 41 | // 轮询处理数据 42 | while (count($read) > 0) { 43 | foreach ($read as $client) { 44 | $address = $sock_to_address[(int) $client->sock]; 45 | $buf = $client->recv(); 46 | if (! $buf) { 47 | unset($client_array[$address]); 48 | continue; 49 | } 50 | if (! isset($read_buffer[$address])) { 51 | $read_buffer[$address] = $buf; 52 | } else { 53 | $read_buffer[$address] .= $buf; 54 | } 55 | // 数据接收完毕 56 | if (($len = strlen($read_buffer[$address])) && $read_buffer[$address][$len - 1] === "\n") { 57 | unset($client_array[$address]); 58 | } 59 | } 60 | // 超时了 61 | if (microtime(true) - $time_start > $timeout) { 62 | break; 63 | } 64 | $read = $client_array; 65 | } 66 | 67 | foreach ($read_buffer as $address => $buf) { 68 | list ($ip, $port) = explode(':', $address); 69 | \Statistics\Lib\Cache::$lastSuccessIpArray[$ip] = $ip; 70 | } 71 | 72 | \Statistics\Lib\Cache::$lastFailedIpArray = array_diff($ip_list, \Statistics\Lib\Cache::$lastSuccessIpArray); 73 | 74 | ksort($read_buffer); 75 | 76 | return $read_buffer; 77 | } 78 | 79 | /** 80 | * 检查是否登录 81 | */ 82 | function check_auth() 83 | { 84 | // 如果配置中管理员用户名密码为空则说明不用验证 85 | if (Config\Config::$adminName == '' && Config\Config::$adminPassword == '') { 86 | return true; 87 | } 88 | // 进入验证流程 89 | $response = \Core\Response::getInstance()->response(); 90 | $session = \Core\Session::getInstance($response); 91 | $session->start(); 92 | if (! isset($_SESSION['admin'])) { 93 | if (! isset($_POST['admin_name']) || ! isset($_POST['admin_password'])) { 94 | include ST_ROOT . '/Views/login.tpl.php'; 95 | return _exit(); 96 | } else { 97 | $admin_name = $_POST['admin_name']; 98 | $admin_password = $_POST['admin_password']; 99 | if ($admin_name != Config\Config::$adminName || $admin_password != Config\Config::$adminPassword) { 100 | $msg = "用户名或者密码不正确"; 101 | include ST_ROOT . '/Views/login.tpl.php'; 102 | return _exit(); 103 | } 104 | $_SESSION['admin'] = $admin_name; 105 | $_GET['fn'] = 'main'; 106 | } 107 | } 108 | $session->save(); 109 | return true; 110 | } 111 | 112 | /** 113 | * 退出 114 | * @param string $str 115 | */ 116 | function _exit($str = '') 117 | { 118 | echo($str); 119 | return false; 120 | } 121 | -------------------------------------------------------------------------------- /Modules/admin.php: -------------------------------------------------------------------------------- 1 | 'REPORT_IP' 26 | ))); 27 | // 广播 28 | socket_sendto($socket, $buffer, strlen($buffer), 0, '255.255.255.255', \Config\Config::$findProviderPort); 29 | // 超时相关 30 | $time_start = microtime(true); 31 | $global_timeout = 1; 32 | $ip_list = array(); 33 | $recv_timeout = array('sec' => 0, 'usec' => 8000); 34 | socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $recv_timeout); 35 | 36 | // 循环读数据 37 | while (microtime(true) - $time_start < $global_timeout) { 38 | $buf = $host = $port = ''; 39 | if (@socket_recvfrom($socket, $buf, 65535, 0, $host, $port)) { 40 | $ip_list[$host] = $host; 41 | } 42 | } 43 | 44 | // 过滤掉已经保存的ip 45 | $count = 0; 46 | foreach ($ip_list as $ip) { 47 | if (! isset(\Statistics\Lib\Cache::$ServerIpList[$ip])) { 48 | $ip_list_str .= $ip . "\r\n"; 49 | $count ++; 50 | } 51 | } 52 | $action = 'add_to_server_list'; 53 | $notice_msg = "探测到{$count}个新数据源"; 54 | break; 55 | case 'add_to_server_list': 56 | if (empty($_POST['ip_list'])) { 57 | $err_msg = "保存的ip列表为空"; 58 | break; 59 | } 60 | $ip_list = explode("\n", $_POST['ip_list']); 61 | if ($ip_list) { 62 | foreach ($ip_list as $ip) { 63 | $ip = trim($ip); 64 | if (false !== ip2long($ip)) { 65 | \Statistics\Lib\Cache::$ServerIpList[$ip] = $ip; 66 | } 67 | } 68 | } 69 | $suc_msg = "添加成功"; 70 | foreach (\Statistics\Lib\Cache::$ServerIpList as $ip) { 71 | $ip_list_str .= $ip . "\r\n"; 72 | } 73 | saveServerIpListToCache(); 74 | break; 75 | case 'save_server_list': 76 | if (empty($_POST['ip_list'])) { 77 | $err_msg = "保存的ip列表为空"; 78 | break; 79 | } 80 | \Statistics\Lib\Cache::$ServerIpList = array(); 81 | $ip_list = explode("\n", $_POST['ip_list']); 82 | if ($ip_list) { 83 | foreach ($ip_list as $ip) { 84 | $ip = trim($ip); 85 | if (false !== ip2long($ip)) { 86 | \Statistics\Lib\Cache::$ServerIpList[$ip] = $ip; 87 | } 88 | } 89 | } 90 | $suc_msg = "保存成功"; 91 | foreach (\Statistics\Lib\Cache::$ServerIpList as $ip) { 92 | $ip_list_str .= $ip . "\r\n"; 93 | } 94 | saveServerIpListToCache(); 95 | break; 96 | default: 97 | foreach (\Statistics\Lib\Cache::$ServerIpList as $ip) { 98 | $ip_list_str .= $ip . "\r\n"; 99 | } 100 | } 101 | 102 | include ST_ROOT . '/Views/header.tpl.php'; 103 | include ST_ROOT . '/Views/admin.tpl.php'; 104 | include ST_ROOT . '/Views/footer.tpl.php'; 105 | } 106 | 107 | function saveServerIpListToCache() 108 | { 109 | foreach (glob(ST_ROOT . '/Config/Cache/*.iplist.cache.php') as $php_file) { 110 | unlink($php_file); 111 | } 112 | file_put_contents(ST_ROOT . '/Config/Cache/' . time() . '.iplist.cache.php', " $interfaces) { 12 | if ($mod == 'AllData') { 13 | continue; 14 | } 15 | $module_str .= '
  • ' . $mod . '
  • '; 16 | if ($module == $mod) { 17 | foreach ($interfaces as $if) { 18 | $module_str .= '
  •   ' . $if . '
  • '; 19 | } 20 | } 21 | } 22 | 23 | $log_data_arr = getStasticLog($module, $interface, $start_time, $offset, $count); 24 | unset($_GET['fn'], $_GET['ip'], $_GET['offset']); 25 | $log_str = ''; 26 | foreach ($log_data_arr as $address => $log_data) { 27 | list ($ip, $port) = explode(':', $address); 28 | $log_str .= $log_data['data']; 29 | $_GET['ip'][] = $ip; 30 | $_GET['offset'][] = $log_data['offset']; 31 | } 32 | $log_str = nl2br(str_replace("\n", "\n\n", $log_str)); 33 | $next_page_url = http_build_query($_GET); 34 | $log_str .= "
    下一页
    "; 35 | 36 | include ST_ROOT . '/Views/header.tpl.php'; 37 | include ST_ROOT . '/Views/log.tpl.php'; 38 | include ST_ROOT . '/Views/footer.tpl.php'; 39 | } 40 | 41 | function getStasticLog($module, $interface, $start_time, $offset = '', $count = 10) 42 | { 43 | $ip_list = (! empty($_GET['ip']) && is_array($_GET['ip'])) ? $_GET['ip'] : \Statistics\Lib\Cache::$ServerIpList; 44 | $offset_list = (! empty($_GET['offset']) && is_array($_GET['offset'])) ? $_GET['offset'] : array(); 45 | $port = \Config\Config::$ProviderPort; 46 | $request_buffer_array = array(); 47 | foreach ($ip_list as $key => $ip) { 48 | $offset = isset($offset_list[$key]) ? $offset_list[$key] : 0; 49 | $request_buffer_array["$ip:$port"] = json_encode(array( 50 | 'cmd' => 'get_log', 51 | 'module' => $module, 52 | 'interface' => $interface, 53 | 'start_time' => $start_time, 54 | 'offset' => $offset, 55 | 'count' => $count 56 | )) . "\n"; 57 | } 58 | 59 | $read_buffer_array = multiRequest($request_buffer_array); 60 | ksort($read_buffer_array); 61 | foreach ($read_buffer_array as $address => $buf) { 62 | list ($ip, $port) = explode(':', $address); 63 | $body_data = json_decode(trim($buf), true); 64 | $log_data = isset($body_data['data']) ? $body_data['data'] : ''; 65 | $offset = isset($body_data['offset']) ? $body_data['offset'] : 0; 66 | $read_buffer_array[$address] = array( 67 | 'offset' => $offset, 68 | 'data' => $log_data 69 | ); 70 | } 71 | return $read_buffer_array; 72 | } 73 | -------------------------------------------------------------------------------- /Modules/logout.php: -------------------------------------------------------------------------------- 1 | response(); 12 | $session = \Core\Session::getInstance($response); 13 | $session->delete(); 14 | include ST_ROOT . '/Views/login.tpl.php'; 15 | } 16 | -------------------------------------------------------------------------------- /Modules/main.php: -------------------------------------------------------------------------------- 1 | $st_str) { 19 | $all_st_str .= $st_str; 20 | } 21 | } 22 | 23 | $code_map = array(); 24 | $data = formatSt($all_st_str, $date, $code_map); 25 | $interface_name = '整体'; 26 | $success_series_data = $fail_series_data = $success_time_series_data = $fail_time_series_data = array(); 27 | $total_count = $fail_count = 0; 28 | foreach ($data as $time_point => $item) { 29 | if ($item['total_count']) { 30 | $success_series_data[] = "[" . ($time_point * 1000) . ",{$item['total_count']}]"; 31 | $total_count += $item['total_count']; 32 | } 33 | $fail_series_data[] = "[" . ($time_point * 1000) . ",{$item['fail_count']}]"; 34 | $fail_count += $item['fail_count']; 35 | if ($item['total_avg_time']) { 36 | $success_time_series_data[] = "[" . ($time_point * 1000) . ",{$item['total_avg_time']}]"; 37 | } 38 | $fail_time_series_data[] = "[" . ($time_point * 1000) . ",{$item['fail_avg_time']}]"; 39 | } 40 | $success_series_data = implode(',', $success_series_data); 41 | $fail_series_data = implode(',', $fail_series_data); 42 | $success_time_series_data = implode(',', $success_time_series_data); 43 | $fail_time_series_data = implode(',', $fail_time_series_data); 44 | 45 | // 总体成功率 46 | $global_rate = $total_count ? round((($total_count - $fail_count) / $total_count) * 100, 4) : 100; 47 | // 返回码分布 48 | $code_pie_data = ''; 49 | $code_pie_array = array(); 50 | unset($code_map[0]); 51 | if (empty($code_map)) { 52 | $code_map[0] = $total_count > 0 ? $total_count : 1; 53 | } 54 | if (is_array($code_map)) { 55 | $total_item_count = array_sum($code_map); 56 | foreach ($code_map as $code => $count) { 57 | $code_pie_array[] = "[\"$code:{$count}个\", " . round($count * 100 / $total_item_count, 4) . "]"; 58 | } 59 | $code_pie_data = implode(',', $code_pie_array); 60 | } 61 | 62 | unset($_GET['start_time'], $_GET['end_time'], $_GET['date'], $_GET['fn']); 63 | $query = http_build_query($_GET); 64 | 65 | // 删除末尾0的记录 66 | if ($today == $date) { 67 | while (! empty($data) && ($item = end($data)) && $item['total_count'] == 0 && ($key = key($data)) && $time_now < $key) { 68 | unset($data[$key]); 69 | } 70 | } 71 | 72 | $table_data = ''; 73 | if ($data) { 74 | $first_line = true; 75 | foreach ($data as $item) { 76 | if ($first_line) { 77 | $first_line = false; 78 | if ($item['total_count'] == 0) { 79 | continue; 80 | } 81 | } 82 | $html_class = 'class="danger"'; 83 | if ($item['total_count'] == 0) { 84 | $html_class = ''; 85 | } elseif ($item['precent'] >= 99.99) { 86 | $html_class = 'class="success"'; 87 | } elseif ($item['precent'] >= 99) { 88 | $html_class = ''; 89 | } elseif ($item['precent'] >= 98) { 90 | $html_class = 'class="warning"'; 91 | } 92 | $table_data = "\n 93 | {$item['time']} 94 | {$item['total_count']} 95 | {$item['total_avg_time']} 96 | {$item['suc_count']} 97 | {$item['suc_avg_time']} 98 | " . ($item['fail_count'] > 0 ? ("{$item['fail_count']}") : $item['fail_count']) . " 99 | {$item['fail_avg_time']} 100 | {$item['precent']}% 101 | 102 | ".$table_data; 103 | } 104 | } 105 | 106 | // date btn 107 | $date_btn_str = ''; 108 | for ($i = 13; $i >= 1; $i --) { 109 | $the_time = strtotime("-$i day"); 110 | $the_date = date('Y-m-d', $the_time); 111 | $html_the_date = $date == $the_date ? "$the_date" : $the_date; 112 | $date_btn_str .= '' . $html_the_date . ''; 113 | if ($i == 7) { 114 | $date_btn_str .= '
    '; 115 | } 116 | } 117 | $the_date = date('Y-m-d'); 118 | $html_the_date = $date == $the_date ? "$the_date" : $the_date; 119 | $date_btn_str .= '' . $html_the_date . ''; 120 | 121 | if (\Statistics\Lib\Cache::$lastFailedIpArray) { 122 | $err_msg = '无法从以下数据源获取数据:'; 123 | foreach (\Statistics\Lib\Cache::$lastFailedIpArray as $ip) { 124 | $err_msg .= $ip . '::' . \Config\Config::$ProviderPort . ' '; 125 | } 126 | } 127 | 128 | if (empty(\Statistics\Lib\Cache::$ServerIpList)) { 129 | $notice_msg = <<数据源为空 131 | 您可以 探测数据源或者添加数据源 132 | EOT; 133 | } 134 | 135 | include ST_ROOT . '/Views/header.tpl.php'; 136 | include ST_ROOT . '/Views/main.tpl.php'; 137 | include ST_ROOT . '/Views/footer.tpl.php'; 138 | } 139 | 140 | function multiRequestStAndModules($module, $interface, $date) 141 | { 142 | \Statistics\Lib\Cache::$statisticDataCache['statistic'] = []; 143 | 144 | $buffer = json_encode(array( 145 | 'cmd' => 'get_statistic', 146 | 'module' => $module, 147 | 'interface' => $interface, 148 | 'date' => $date 149 | )) . "\n"; 150 | 151 | $ip_list = (! empty($_GET['ip']) && is_array($_GET['ip'])) ? $_GET['ip'] : \Statistics\Lib\Cache::$ServerIpList; 152 | $reqest_buffer_array = array(); 153 | $port = \Config\Config::$ProviderPort; 154 | 155 | foreach ($ip_list as $ip) { 156 | $reqest_buffer_array["$ip:$port"] = $buffer; 157 | } 158 | $read_buffer_array = multiRequest($reqest_buffer_array); 159 | foreach ($read_buffer_array as $address => $buf) { 160 | list ($ip, $port) = explode(':', $address); 161 | 162 | $body_data = json_decode(trim($buf), true); 163 | $statistic_data = isset($body_data['statistic']) ? $body_data['statistic'] : ''; 164 | $modules_data = isset($body_data['modules']) ? $body_data['modules'] : array(); 165 | 166 | // 整理modules 167 | foreach ($modules_data as $mod => $interfaces) { 168 | if (! isset(\Statistics\Lib\Cache::$modulesDataCache[$mod])) { 169 | \Statistics\Lib\Cache::$modulesDataCache[$mod] = array(); 170 | } 171 | foreach ($interfaces as $if) { 172 | \Statistics\Lib\Cache::$modulesDataCache[$mod][$if] = $if; 173 | } 174 | } 175 | \Statistics\Lib\Cache::$statisticDataCache['statistic'][$ip] = $statistic_data; 176 | } 177 | } 178 | 179 | function formatSt($str, $date, &$code_map) 180 | { 181 | // time:[suc_count:xx,suc_cost_time:xx,fail_count:xx,fail_cost_time:xx] 182 | $st_data = $code_map = array(); 183 | $st_explode = explode("\n", $str); 184 | // 汇总计算 185 | foreach ($st_explode as $line) { 186 | // line = IP time suc_count suc_cost_time fail_count fail_cost_time code_json 187 | $line_data = explode("\t", $line); 188 | if (! isset($line_data[5])) { 189 | continue; 190 | } 191 | $time_line = $line_data[1]; 192 | $time_line = ceil($time_line / 300) * 300; 193 | $suc_count = $line_data[2]; 194 | $suc_cost_time = $line_data[3]; 195 | $fail_count = $line_data[4]; 196 | $fail_cost_time = $line_data[5]; 197 | $tmp_code_map = json_decode($line_data[6], true); 198 | if (! isset($st_data[$time_line])) { 199 | $st_data[$time_line] = array( 200 | 'suc_count' => 0, 201 | 'suc_cost_time' => 0, 202 | 'fail_count' => 0, 203 | 'fail_cost_time' => 0 204 | ); 205 | } 206 | $st_data[$time_line]['suc_count'] += $suc_count; 207 | $st_data[$time_line]['suc_cost_time'] += $suc_cost_time; 208 | $st_data[$time_line]['fail_count'] += $fail_count; 209 | $st_data[$time_line]['fail_cost_time'] += $fail_cost_time; 210 | 211 | if (is_array($tmp_code_map)) { 212 | foreach ($tmp_code_map as $code => $count) { 213 | if (! isset($code_map[$code])) { 214 | $code_map[$code] = 0; 215 | } 216 | $code_map[$code] += $count; 217 | } 218 | } 219 | } 220 | // 按照时间排序 221 | ksort($st_data); 222 | // time => [total_count:xx,suc_count:xx,suc_avg_time:xx,fail_count:xx,fail_avg_time:xx,percent:xx] 223 | $data = array(); 224 | // 计算成功率 耗时 225 | foreach ($st_data as $time_line => $item) { 226 | $data[$time_line] = array( 227 | 'time' => date('Y-m-d H:i:s', $time_line), 228 | 'total_count' => $item['suc_count'] + $item['fail_count'], 229 | 'total_avg_time' => $item['suc_count'] + $item['fail_count'] == 0 ? 0 : round(($item['suc_cost_time'] + $item['fail_cost_time']) / ($item['suc_count'] + $item['fail_count']), 6), 230 | 'suc_count' => $item['suc_count'], 231 | 'suc_avg_time' => $item['suc_count'] == 0 ? $item['suc_count'] : round($item['suc_cost_time'] / $item['suc_count'], 6), 232 | 'fail_count' => $item['fail_count'], 233 | 'fail_avg_time' => $item['fail_count'] == 0 ? 0 : round($item['fail_cost_time'] / $item['fail_count'], 6), 234 | 'precent' => $item['suc_count'] + $item['fail_count'] == 0 ? 0 : round(($item['suc_count'] * 100 / ($item['suc_count'] + $item['fail_count'])), 4) 235 | ); 236 | } 237 | $time_point = strtotime($date); 238 | for ($i = 0; $i < 288; $i ++) { 239 | $data[$time_point] = isset($data[$time_point]) ? $data[$time_point] : array( 240 | 'time' => date('Y-m-d H:i:s', $time_point), 241 | 'total_count' => 0, 242 | 'total_avg_time' => 0, 243 | 'suc_count' => 0, 244 | 'suc_avg_time' => 0, 245 | 'fail_count' => 0, 246 | 'fail_avg_time' => 0, 247 | 'precent' => 100 248 | ); 249 | $time_point += 300; 250 | } 251 | ksort($data); 252 | return $data; 253 | } 254 | -------------------------------------------------------------------------------- /Modules/setting.php: -------------------------------------------------------------------------------- 1 | 65535) { 21 | $err_msg = "探测端口不合法"; 22 | break; 23 | } 24 | $suc_msg = "保存成功"; 25 | \Config\Config::$ProviderPort = $detect_port; 26 | saveDetectPortToCache(); 27 | break; 28 | default: 29 | $detect_port = \Config\Config::$ProviderPort; 30 | } 31 | 32 | include ST_ROOT . '/Views/header.tpl.php'; 33 | include ST_ROOT . '/Views/setting.tpl.php'; 34 | include ST_ROOT . '/Views/footer.tpl.php'; 35 | } 36 | 37 | function saveDetectPortToCache() 38 | { 39 | foreach (glob(ST_ROOT . '/Config/Cache/*detect_port.cache.php') as $php_file) { 40 | unlink($php_file); 41 | } 42 | file_put_contents(ST_ROOT . '/Config/Cache/' . time() . '.detect_port.cache.php', " $st_str) { 16 | $all_st_str .= $st_str; 17 | } 18 | } 19 | 20 | $code_map = array(); 21 | $data = formatSt($all_st_str, $date, $code_map); 22 | $interface_name = "$module::$interface"; 23 | $success_series_data = $fail_series_data = $success_time_series_data = $fail_time_series_data = array(); 24 | $total_count = $fail_count = 0; 25 | foreach ($data as $time_point => $item) { 26 | if ($item['total_count']) { 27 | $success_series_data[] = "[" . ($time_point * 1000) . ",{$item['total_count']}]"; 28 | $total_count += $item['total_count']; 29 | } 30 | $fail_series_data[] = "[" . ($time_point * 1000) . ",{$item['fail_count']}]"; 31 | $fail_count += $item['fail_count']; 32 | if ($item['total_avg_time']) { 33 | $success_time_series_data[] = "[" . ($time_point * 1000) . ",{$item['total_avg_time']}]"; 34 | } 35 | $fail_time_series_data[] = "[" . ($time_point * 1000) . ",{$item['fail_avg_time']}]"; 36 | } 37 | $success_series_data = implode(',', $success_series_data); 38 | $fail_series_data = implode(',', $fail_series_data); 39 | $success_time_series_data = implode(',', $success_time_series_data); 40 | $fail_time_series_data = implode(',', $fail_time_series_data); 41 | 42 | unset($_GET['start_time'], $_GET['end_time'], $_GET['date'], $_GET['fn']); 43 | $query = http_build_query($_GET); 44 | 45 | // 删除末尾0的记录 46 | if ($today == $date) { 47 | while (! empty($data) && ($item = end($data)) && $item['total_count'] == 0 && ($key = key($data)) && $time_now < $key) { 48 | unset($data[$key]); 49 | } 50 | } 51 | 52 | $table_data = $html_class = ''; 53 | if ($data) { 54 | $first_line = true; 55 | foreach ($data as $item) { 56 | if ($first_line) { 57 | $first_line = false; 58 | if ($item['total_count'] == 0) { 59 | continue; 60 | } 61 | } 62 | $html_class = 'class="danger"'; 63 | if ($item['total_count'] == 0) { 64 | $html_class = ''; 65 | } elseif ($item['precent'] >= 99.99) { 66 | $html_class = 'class="success"'; 67 | } elseif ($item['precent'] >= 99) { 68 | $html_class = ''; 69 | } elseif ($item['precent'] >= 98) { 70 | $html_class = 'class="warning"'; 71 | } 72 | $table_data = "\n 73 | {$item['time']} 74 | {$item['total_count']} 75 | {$item['total_avg_time']} 76 | {$item['suc_count']} 77 | {$item['suc_avg_time']} 78 | " . ($item['fail_count'] > 0 ? ("{$item['fail_count']}") : $item['fail_count']) . " 79 | {$item['fail_avg_time']} 80 | {$item['precent']}% 81 | ".$table_data; 82 | } 83 | } 84 | 85 | // date btn 86 | $date_btn_str = ''; 87 | for ($i = 13; $i >= 1; $i --) { 88 | $the_time = strtotime("-$i day"); 89 | $the_date = date('Y-m-d', $the_time); 90 | $html_the_date = $date == $the_date ? "$the_date" : $the_date; 91 | $date_btn_str .= '' . $html_the_date . ''; 92 | if ($i == 7) { 93 | $date_btn_str .= '
    '; 94 | } 95 | } 96 | $the_date = date('Y-m-d'); 97 | $html_the_date = $date == $the_date ? "$the_date" : $the_date; 98 | $date_btn_str .= '' . $html_the_date . ''; 99 | 100 | $module_str = ''; 101 | foreach (\Statistics\Lib\Cache::$modulesDataCache as $mod => $interfaces) { 102 | if ($mod == 'AllData') { 103 | continue; 104 | } 105 | $module_str .= '
  • ' . $mod . '
  • '; 106 | if ($module == $mod) { 107 | foreach ($interfaces as $if) { 108 | $module_str .= '
  •   ' . $if . '
  • '; 109 | } 110 | } 111 | } 112 | 113 | if (\Statistics\Lib\Cache::$lastFailedIpArray) { 114 | $err_msg = '无法从以下数据源获取数据:'; 115 | foreach (\Statistics\Lib\Cache::$lastFailedIpArray as $ip) { 116 | $err_msg .= $ip . '::' . \Config\Config::$ProviderPort . ' '; 117 | } 118 | } 119 | 120 | include ST_ROOT . '/Views/header.tpl.php'; 121 | include ST_ROOT . '/Views/statistic.tpl.php'; 122 | include ST_ROOT . '/Views/footer.tpl.php'; 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # statistics 2 | 一个运用php与swoole实现的统计监控系统 3 | 4 | > 如果有使用`laravel` 的朋友,推荐另外一个项目[fast-laravel](https://github.com/toxmc/fast-laravel)。欢迎使用,喜欢的话给个star鼓励下。谢谢各位 5 | 6 | ## 界面截图 7 | ![Swoole statistics screenshot one](https://raw.githubusercontent.com/smalleyes/statistics/master/doc/1.png) 8 | 9 | ![Swoole statistics screenshot two](https://raw.githubusercontent.com/smalleyes/statistics/master/doc/2.png) 10 | 11 | ![Swoole statistics screenshot three](https://raw.githubusercontent.com/smalleyes/statistics/master/doc/3.png) 12 | 13 | ![Swoole statistics screenshot four](https://raw.githubusercontent.com/smalleyes/statistics/master/doc/4.png) 14 | 15 | ## 说明 16 | * statistics是一个以swoole作为服务器容器的统计监控系统。 17 | * statisitcs使用PHP开发,无需安装Mysql等数据库,无需安装php-fpm等软件。 18 | * statistics包含了客户端和服务端,客户端是一个类库,通过函数调用的方式以UDP协议上报数据给服务端。 19 | * statistics服务端接收上报数据然后汇总展示。 20 | * statistics以曲线图、饼图和表格的方式展示请求量、耗时、成功率、错误日志等。 21 | * workerman版本实现statistics [https://github.com/walkor/workerman-statistics](https://github.com/walkor/workerman-statistics) 22 | 23 | ## 依赖 24 | 25 | * PHP 5.3+ 26 | * Swoole 1.7.18 27 | * Linux, OS X and basic Windows support (Thanks to cygwin) 28 | 29 | ## 安装 Swoole扩展 30 | 31 | 1. Install swoole extension from pecl 32 | 33 | ``` 34 | pecl install swoole 35 | ``` 36 | 37 | 2. Install swoole extension from source 38 | 39 | ``` 40 | sudo apt-get install php5-dev 41 | git clone https://github.com/swoole/swoole-src.git 42 | cd swoole-src 43 | phpize 44 | ./configure 45 | make && make install 46 | ``` 47 | 48 | ## 安装 49 | 50 | ### 1. 下载 Swoole statistics 51 | 52 | linux shell Clone the git repo: 53 | ``` 54 | git clone https://github.com/smalleyes/statistics.git 55 | ``` 56 | linux wget the zip file: 57 | ``` 58 | wget https://github.com/smalleyes/statistics/archive/master.zip 59 | unzip master.zip 60 | ``` 61 | ### 2. 安全 62 | 63 | 管理员用户名密码默认都为admin。 64 | 如果不需要登录验证,在applications/Statistics/Config/Config.php里面设置管理员密码留空。 65 | 请自行做好安全相关的限制. 66 | 67 | ## 运行 68 | 69 | * 配置NGINX虚拟主机 70 | * 配置文件位于doc/statistics.conf 71 | * 复制文件statistics.conf到nginx,虚拟主机配置文件目录下(默认为nginx/conf.d目录下) 72 | * 重启nginx或重新加载nginx配置文件(nginx -s reload) 73 | * 配置hoshs文件,绑定ip域名对应关系 74 | * 使用swoole需要启动服务,php web.php与php worker.php再打开浏览器访问绑定的域名。 75 | * 配置信息都在Config目录下。 76 | * 开启守护进程模式,请修改配置Config/Server.php的daemonize选项为TRUE。 77 | 78 | ## 客户端使用方法 79 | 80 | ```php 81 | 82 | 'xmc','password'=>'123456'); 94 | return $res; 95 | } 96 | 97 | public static function addInfo() 98 | { 99 | $res = array(); 100 | $res = array('name'=>'xmc','password'=>'123456'); 101 | return $res; 102 | } 103 | 104 | public static function getErrCode() 105 | { 106 | $errcode = 10001; 107 | return $errcode; 108 | } 109 | 110 | public static function getErrMsg() 111 | { 112 | $errmsg = '添加用户失败'; 113 | return $errmsg; 114 | } 115 | } 116 | 117 | include 'StatisticClient.php'; 118 | 119 | // 统计开始 120 | StatisticClient::tick("User", 'addInfo'); 121 | // 统计的产生,接口调用是否成功、错误码、错误日志 122 | $success = true; $code = 0; $msg = ''; 123 | // 假如有个User::getInfo方法要监控 124 | $user_info = User::addInfo(); 125 | if(!$user_info){ 126 | // 标记失败 127 | $success = false; 128 | // 获取错误码,假如getErrCode()获得 129 | $code = User::getErrCode(); 130 | // 获取错误日志,假如getErrMsg()获得 131 | $msg = User::getErrMsg(); 132 | } 133 | // 上报结果 134 | $res = StatisticClient::report('User', 'addInfo', $success, $code, $msg); 135 | 136 | echo "done over...\n"; 137 | var_dump($user_info,$res); 138 | 139 | ``` 140 | -------------------------------------------------------------------------------- /Views/admin.tpl.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 34 |
    35 |
    36 |
    37 |
    38 | 46 | 47 |
    48 | 49 | 50 |
    51 | 52 |
    53 | 54 | 55 |
    56 | 57 |
    58 | 59 | 60 |
    61 | 62 |
    63 |
    64 |
    65 |
    66 |
    67 |
    68 | 69 |
    70 |
    71 | 72 |
    73 |
    74 |
    75 | 76 |
    77 |
    78 |
    79 | 80 | 返回主页 继续添加 81 | 82 |
    83 |
    84 |
    85 |
    86 |
    87 | -------------------------------------------------------------------------------- /Views/footer.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Views/header.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 统计与监控 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Views/log.tpl.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 34 |
    35 |
    36 |
    37 |
    38 | 39 |
    40 |
    41 |
    42 | -------------------------------------------------------------------------------- /Views/login.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 统计与监控 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 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 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Views/main.tpl.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 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 |
    66 |
    67 |
    68 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 |
    时间调用总数平均耗时成功调用总数成功平均耗时失败调用总数失败平均耗时成功率
    283 |
    284 |
    285 |
    286 | -------------------------------------------------------------------------------- /Views/setting.tpl.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 34 |
    35 |
    36 |
    37 |
    38 | 46 | 47 |
    48 | 49 | 50 |
    51 | 52 |
    53 | 54 | 55 |
    56 | 57 |
    58 | 59 | 60 |
    61 | 62 |
    63 |
    64 |
    65 |
    66 |
    67 |
    68 |
    69 |
    70 | 71 |
    72 | 73 |
    74 |
    75 |
    76 |
    77 | 78 |
    79 |
    80 |
    81 |
    82 |
    83 |
    84 |
    85 |
    86 | -------------------------------------------------------------------------------- /Views/statistic.tpl.php: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 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 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
    时间调用总数平均耗时成功调用总数成功平均耗时失败调用总数失败平均耗时成功率
    195 | 196 |
    197 |
    198 |
    199 | -------------------------------------------------------------------------------- /Web/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.1 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | .btn-default, 10 | .btn-primary, 11 | .btn-success, 12 | .btn-info, 13 | .btn-warning, 14 | .btn-danger { 15 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 16 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 17 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 18 | } 19 | 20 | .btn-default:active, 21 | .btn-primary:active, 22 | .btn-success:active, 23 | .btn-info:active, 24 | .btn-warning:active, 25 | .btn-danger:active, 26 | .btn-default.active, 27 | .btn-primary.active, 28 | .btn-success.active, 29 | .btn-info.active, 30 | .btn-warning.active, 31 | .btn-danger.active { 32 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 33 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 34 | } 35 | 36 | .btn:active, 37 | .btn.active { 38 | background-image: none; 39 | } 40 | 41 | .btn-default { 42 | text-shadow: 0 1px 0 #fff; 43 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0)); 44 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 45 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 46 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); 47 | background-repeat: repeat-x; 48 | border-color: #dbdbdb; 49 | border-color: #ccc; 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 52 | } 53 | 54 | .btn-default:hover, 55 | .btn-default:focus { 56 | background-color: #e0e0e0; 57 | background-position: 0 -15px; 58 | } 59 | 60 | .btn-default:active, 61 | .btn-default.active { 62 | background-color: #e0e0e0; 63 | border-color: #dbdbdb; 64 | } 65 | 66 | .btn-primary { 67 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#2d6ca2)); 68 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 69 | background-image: -moz-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 70 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 71 | background-repeat: repeat-x; 72 | border-color: #2b669a; 73 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 74 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 75 | } 76 | 77 | .btn-primary:hover, 78 | .btn-primary:focus { 79 | background-color: #2d6ca2; 80 | background-position: 0 -15px; 81 | } 82 | 83 | .btn-primary:active, 84 | .btn-primary.active { 85 | background-color: #2d6ca2; 86 | border-color: #2b669a; 87 | } 88 | 89 | .btn-success { 90 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#419641)); 91 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 92 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #419641 100%); 93 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 94 | background-repeat: repeat-x; 95 | border-color: #3e8f3e; 96 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 97 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 98 | } 99 | 100 | .btn-success:hover, 101 | .btn-success:focus { 102 | background-color: #419641; 103 | background-position: 0 -15px; 104 | } 105 | 106 | .btn-success:active, 107 | .btn-success.active { 108 | background-color: #419641; 109 | border-color: #3e8f3e; 110 | } 111 | 112 | .btn-warning { 113 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#eb9316)); 114 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 115 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 116 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 117 | background-repeat: repeat-x; 118 | border-color: #e38d13; 119 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 120 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 121 | } 122 | 123 | .btn-warning:hover, 124 | .btn-warning:focus { 125 | background-color: #eb9316; 126 | background-position: 0 -15px; 127 | } 128 | 129 | .btn-warning:active, 130 | .btn-warning.active { 131 | background-color: #eb9316; 132 | border-color: #e38d13; 133 | } 134 | 135 | .btn-danger { 136 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c12e2a)); 137 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 138 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 139 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 140 | background-repeat: repeat-x; 141 | border-color: #b92c28; 142 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 143 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 144 | } 145 | 146 | .btn-danger:hover, 147 | .btn-danger:focus { 148 | background-color: #c12e2a; 149 | background-position: 0 -15px; 150 | } 151 | 152 | .btn-danger:active, 153 | .btn-danger.active { 154 | background-color: #c12e2a; 155 | border-color: #b92c28; 156 | } 157 | 158 | .btn-info { 159 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#2aabd2)); 160 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 161 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 162 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 163 | background-repeat: repeat-x; 164 | border-color: #28a4c9; 165 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 166 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 167 | } 168 | 169 | .btn-info:hover, 170 | .btn-info:focus { 171 | background-color: #2aabd2; 172 | background-position: 0 -15px; 173 | } 174 | 175 | .btn-info:active, 176 | .btn-info.active { 177 | background-color: #2aabd2; 178 | border-color: #28a4c9; 179 | } 180 | 181 | .thumbnail, 182 | .img-thumbnail { 183 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 184 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 185 | } 186 | 187 | .dropdown-menu > li > a:hover, 188 | .dropdown-menu > li > a:focus { 189 | background-color: #e8e8e8; 190 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 191 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 192 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 193 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 194 | background-repeat: repeat-x; 195 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 196 | } 197 | 198 | .dropdown-menu > .active > a, 199 | .dropdown-menu > .active > a:hover, 200 | .dropdown-menu > .active > a:focus { 201 | background-color: #357ebd; 202 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 203 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 204 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 205 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 206 | background-repeat: repeat-x; 207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 208 | } 209 | 210 | .navbar-default { 211 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 212 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 213 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 214 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 215 | background-repeat: repeat-x; 216 | border-radius: 4px; 217 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 218 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 219 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 220 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 221 | } 222 | 223 | .navbar-default .navbar-nav > .active > a { 224 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f3f3f3)); 225 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 226 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 227 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 228 | background-repeat: repeat-x; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 230 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 231 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 232 | } 233 | 234 | .navbar-brand, 235 | .navbar-nav > li > a { 236 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 237 | } 238 | 239 | .navbar-inverse { 240 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 241 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); 242 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 243 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 244 | background-repeat: repeat-x; 245 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 246 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 247 | } 248 | 249 | .navbar-inverse .navbar-nav > .active > a { 250 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#222222), to(#282828)); 251 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%); 252 | background-image: -moz-linear-gradient(top, #222222 0%, #282828 100%); 253 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%); 254 | background-repeat: repeat-x; 255 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 256 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 257 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 258 | } 259 | 260 | .navbar-inverse .navbar-brand, 261 | .navbar-inverse .navbar-nav > li > a { 262 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 263 | } 264 | 265 | .navbar-static-top, 266 | .navbar-fixed-top, 267 | .navbar-fixed-bottom { 268 | border-radius: 0; 269 | } 270 | 271 | .alert { 272 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 273 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 274 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 275 | } 276 | 277 | .alert-success { 278 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 279 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 280 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 281 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 282 | background-repeat: repeat-x; 283 | border-color: #b2dba1; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 285 | } 286 | 287 | .alert-info { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 289 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 290 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 291 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 292 | background-repeat: repeat-x; 293 | border-color: #9acfea; 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 295 | } 296 | 297 | .alert-warning { 298 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 299 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 300 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 301 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 302 | background-repeat: repeat-x; 303 | border-color: #f5e79e; 304 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 305 | } 306 | 307 | .alert-danger { 308 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 309 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 310 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 311 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 312 | background-repeat: repeat-x; 313 | border-color: #dca7a7; 314 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 315 | } 316 | 317 | .progress { 318 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 319 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 320 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 321 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 322 | background-repeat: repeat-x; 323 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 324 | } 325 | 326 | .progress-bar { 327 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 328 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 329 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 330 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 331 | background-repeat: repeat-x; 332 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 333 | } 334 | 335 | .progress-bar-success { 336 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 337 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 338 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 339 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 340 | background-repeat: repeat-x; 341 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 342 | } 343 | 344 | .progress-bar-info { 345 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 346 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 347 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 348 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 349 | background-repeat: repeat-x; 350 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 351 | } 352 | 353 | .progress-bar-warning { 354 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 355 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 356 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 357 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 358 | background-repeat: repeat-x; 359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 360 | } 361 | 362 | .progress-bar-danger { 363 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 364 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 365 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 366 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 367 | background-repeat: repeat-x; 368 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 369 | } 370 | 371 | .list-group { 372 | border-radius: 4px; 373 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 374 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 375 | } 376 | 377 | .list-group-item.active, 378 | .list-group-item.active:hover, 379 | .list-group-item.active:focus { 380 | text-shadow: 0 -1px 0 #3071a9; 381 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 382 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 383 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 384 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 385 | background-repeat: repeat-x; 386 | border-color: #3278b3; 387 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 388 | } 389 | 390 | .panel { 391 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 392 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 393 | } 394 | 395 | .panel-default > .panel-heading { 396 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 397 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 398 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 399 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 400 | background-repeat: repeat-x; 401 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 402 | } 403 | 404 | .panel-primary > .panel-heading { 405 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 406 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 407 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 408 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 409 | background-repeat: repeat-x; 410 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 411 | } 412 | 413 | .panel-success > .panel-heading { 414 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 415 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 416 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 418 | background-repeat: repeat-x; 419 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 420 | } 421 | 422 | .panel-info > .panel-heading { 423 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 424 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 425 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 427 | background-repeat: repeat-x; 428 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 429 | } 430 | 431 | .panel-warning > .panel-heading { 432 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 433 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 434 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 436 | background-repeat: repeat-x; 437 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 438 | } 439 | 440 | .panel-danger > .panel-heading { 441 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 442 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 443 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 445 | background-repeat: repeat-x; 446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 447 | } 448 | 449 | .well { 450 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 451 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 452 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 453 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 454 | background-repeat: repeat-x; 455 | border-color: #dcdcdc; 456 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 457 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 458 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 459 | } -------------------------------------------------------------------------------- /Web/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.1 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e0e0e0));background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe0e0e0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#2d6ca2));background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-moz-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);background-repeat:repeat-x;border-color:#2b669a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff2d6ca2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#419641));background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);background-repeat:repeat-x;border-color:#3e8f3e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff419641',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#eb9316));background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);background-repeat:repeat-x;border-color:#e38d13;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffeb9316',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c12e2a));background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);background-repeat:repeat-x;border-color:#b92c28;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc12e2a',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#2aabd2));background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);background-repeat:repeat-x;border-color:#28a4c9;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2aabd2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar-default{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f3f3f3));background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff3f3f3',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-gradient(linear,left 0,left 100%,from(#222),to(#282828));background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-moz-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff282828',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /Web/css/style.css: -------------------------------------------------------------------------------- 1 | body {margin: 10px 0px} 2 | .nav { 3 | margin-bottom: 10px; 4 | } 5 | .height-400{ 6 | height:400px 7 | } 8 | 9 | .footer { 10 | margin: 8px auto 0 auto; 11 | padding-bottom: 8px; 12 | color: #666; 13 | font-size: 12px; 14 | text-align: center; 15 | } 16 | 17 | .footer > a 18 | { 19 | font-size:bold; 20 | color:#333; 21 | } -------------------------------------------------------------------------------- /Web/img/apple-touch-icon-114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/apple-touch-icon-114-precomposed.png -------------------------------------------------------------------------------- /Web/img/apple-touch-icon-144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/apple-touch-icon-144-precomposed.png -------------------------------------------------------------------------------- /Web/img/apple-touch-icon-57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/apple-touch-icon-57-precomposed.png -------------------------------------------------------------------------------- /Web/img/apple-touch-icon-72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/apple-touch-icon-72-precomposed.png -------------------------------------------------------------------------------- /Web/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/favicon.png -------------------------------------------------------------------------------- /Web/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /Web/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toxmc/statistics/476ec3b50a3a6f7de269bf831e423ad41e7155db/Web/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /Web/index.php: -------------------------------------------------------------------------------- 1 | this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /Web/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;drun("0.0.0.0", 6666); 19 | -------------------------------------------------------------------------------- /worker.php: -------------------------------------------------------------------------------- 1 | run("0.0.0.0", 55656); --------------------------------------------------------------------------------