├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── clients ├── client-linux.py └── client-psutil.py ├── docker-compose-telegram.yml ├── docker-compose.yml ├── plugin ├── Dockerfile-telegram └── bot-telegram.py ├── server ├── .gitignore ├── Makefile ├── config.json ├── include │ ├── argparse.h │ ├── detect.h │ ├── json.h │ └── system.h ├── obj │ └── .gitignore └── src │ ├── argparse.c │ ├── json.c │ ├── main.cpp │ ├── main.h │ ├── netban.cpp │ ├── netban.h │ ├── network.cpp │ ├── network.h │ ├── network_client.cpp │ ├── server.cpp │ ├── server.h │ └── system.c └── web ├── css ├── bootstrap-theme.min.css ├── bootstrap-theme.min.css.map ├── bootstrap.min.css ├── bootstrap.min.css.map ├── dark.css └── light.css ├── favicon.ico ├── img ├── dark.png └── light.png ├── index.html ├── js ├── bootstrap.min.js ├── html5shiv.js ├── jquery.min.js ├── respond.min.js └── serverstatus.js ├── json └── .gitignore └── ssview.py /.gitignore: -------------------------------------------------------------------------------- 1 | default.sublime-workspace 2 | 3 | # pycharm 4 | .idea 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # The Dockerfile for build localhost source, not git repo 2 | FROM debian:buster as builder 3 | 4 | MAINTAINER cppla https://cpp.la 5 | 6 | RUN apt-get update -y && apt-get -y install gcc g++ make 7 | 8 | COPY . . 9 | 10 | WORKDIR /server 11 | 12 | RUN make 13 | RUN pwd && ls -a 14 | 15 | # glibc env run 16 | FROM nginx:latest 17 | 18 | RUN mkdir -p /ServerStatus/server/ 19 | 20 | COPY --from=builder server /ServerStatus/server/ 21 | COPY --from=builder web /usr/share/nginx/html/ 22 | 23 | EXPOSE 80 35601 24 | 25 | CMD nohup sh -c '/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServerStatus中文版: 2 | 3 | * ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。 4 | * 在线演示:https://tanzhen.tujidu.com 5 | 6 | [![Python Support](https://img.shields.io/badge/python-2.7%2B%20-blue.svg)](https://github.com/cppla/ServerStatus) 7 | [![C++ Compiler](http://img.shields.io/badge/C++-GNU-blue.svg?style=flat&logo=cplusplus)](https://github.com/cppla/ServerStatus) 8 | [![License](https://img.shields.io/badge/license-MIT-4EB1BA.svg?style=flat-square)](https://github.com/cppla/ServerStatus) 9 | [![Version](https://img.shields.io/badge/Version-Beta%201.0.7-red)](https://github.com/cppla/ServerStatus) 10 | 11 | ![Latest Version](http://dl.cpp.la/Archive/serverstatus-1.0.2.png) 12 | 13 | `curl -sSL https://get.docker.com/ | sh && apt -y install docker-compose` 14 | 15 | # 目录介绍: 16 | 17 | * clients 客户端文件 18 | * server 服务端文件 19 | * web 网站文件 20 | 21 | * server/config.json 探针配置文件       22 | * web/json 探针月流量 23 | 24 | # 自动部署: 25 | 26 | 【服务端】: 27 | ```bash 28 | 29 | `OneTouch`: 30 | 31 | wget --no-check-certificate -qO ~/serverstatus-config.json https://raw.githubusercontent.com/langren1353/ServerStatus/master/server/config.json && mkdir ~/serverstatus-monthtraffic 32 | docker run -d --restart=always --name=serverstatus -v ~/serverstatus-config.json:/ServerStatus/server/config.json -v ~/serverstatus-monthtraffic:/usr/share/nginx/html/json -p 80:80 -p 35601:35601 cppla/serverstatus:latest 33 | 34 | `ServerStatus`: docker-compose up -d 35 | 36 | `ServerStatus with tgbot`: TG_CHAT_ID=你的电报ID TG_BOT_TOKEN=你的电报密钥 docker-compose -f docker-compose-telegram.yml up -d 37 | 38 | ``` 39 | 40 | 【客户端】: 41 | ```bash 42 | wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/langren1353/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 & 43 | 44 | eg: 45 | wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/langren1353/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER=45.79.67.132 USER=s04 >/dev/null 2>&1 & 46 | ``` 47 | 48 | # 手动安装教程: 49 | 50 | 【克隆代码】: 51 | ``` 52 | git clone https://github.com/langren1353/ServerStatus.git 53 | ``` 54 | 55 | 【服务端配置】: 56 | 57 | 一、生成服务端程序 58 | ``` 59 | cd ServerStatus/server 60 | make 61 | ./sergate 62 | ``` 63 | 如果没错误提示,OK,ctrl+c关闭;如果有错误提示,检查35601端口是否被占用 64 | 65 | 二、修改配置文件 66 | 修改config.json文件,注意username, password的值需要和客户端对应一致     67 | ``` 68 | {"servers": 69 | [ 70 | { 71 | "username": "s01", 72 | "name": "Mainserver 1", 73 | "type": "Dedicated Server", 74 | "host": "GenericServerHost123", 75 | "location": "Austria", 76 | "password": "some-hard-to-guess-copy-paste-password", 77 | "monthstart": 1 78 | "extra1": "17$/yr", 79 | "extra2": "预计2020年12月12日到期", 80 | "extra3":"" 81 | }, 82 | { 83 | "username": "s01", 84 | "name": "Mainserver 1", 85 | "type": "Dedicated Server", 86 | "host": "GenericServerHost123", 87 | "location": "Austria", 88 | "password": "some-hard-to-guess-copy-paste-password", 89 | "monthstart": 1 90 | "extra1": "#200/年", 91 | "extra2": "吃灰,丢弃", 92 | "extra3":"" 93 | }, 94 | ] 95 | } 96 | ``` 97 | 其中extra1暂时用于设置服务器价格参数 98 | 99 | 1. 日期支持y/yr/semi year/year/mon/month/day/h/hour/qua/quater/年/月/日/半年/2年/3年/季度/天/小时 不分大小写 100 | 2. 金币支持¥/y/RMB/元/$/o/r/hkd/美元/日元/円 不分大小写 101 | 3. 价格中,使用前缀`#`的话,那么将不会计入年度价格,并且会被着色为灰色 102 | 103 | 104 | 三、拷贝ServerStatus/status到你的网站目录,或者nginx直接指向该目录 105 | 例如: 106 | ``` 107 | sudo cp -r ServerStatus/web/* /home/wwwroot/default 108 | ``` 109 | 110 | 四、运行服务端: 111 | web-dir参数为上一步设置的网站根目录,务必修改成自己网站的路径 112 | ``` 113 | ./sergate --config=config.json --web-dir=/home/wwwroot/default 114 | ``` 115 | 116 | 【客户端配置】(客户端程序在ServerStatus/clients下): 117 | 客户端有两个版本,client-linux为普通linux,client-psutil为跨平台版,普通版不成功,换成跨平台版即可。 118 | 119 | 一、client-linux版配置: 120 | 1、vim client-linux.py, 修改SERVER地址,username帐号, password密码 121 | 2、python3 client-linux.py 运行即可。 122 | 123 | 二、client-psutil版配置: 124 | 1、安装psutil跨平台依赖库 125 | 2、vim client-psutil.py, 修改SERVER地址,username帐号, password密码 126 | 3、python3 client-psutil.py 运行即可。 127 | ``` 128 | ### for Centos: 129 | sudo yum -y install epel-release 130 | sudo yum -y install python3-pip 131 | sudo yum clean all 132 | sudo yum -y install gcc 133 | sudo yum -y install python3-devel 134 | sudo pip3 install psutil 135 | 136 | ### for Ubuntu/Debian: 137 | sudo apt -y install python3-pip 138 | sudo pip3 install psutil 139 | 140 | ### for Windows: 141 | 地址:https://pypi.org/project/psutil/ 142 | 下载psutil for windows, 安装即可 143 | ``` 144 | 145 | 打开云探针页面,就可以正常的监控。接下来把服务器和客户端脚本自行加入开机启动,或者进程守护,或以后台方式运行即可!例如: nohup python3 client-linux.py & 146 | 147 | `extra scene (run web/ssview.py)` 148 | ![Shell View](http://dl.cpp.la/Archive/serverstatus-shell.png) 149 | 150 | 151 | # 相关开源项目: 152 | 153 | * BotoX:https://github.com/BotoX/ServerStatus 154 | * mojeda: https://github.com/mojeda 155 | * mojeda's ServerStatus: https://github.com/mojeda/ServerStatus 156 | * BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690 157 | -------------------------------------------------------------------------------- /clients/client-linux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # Update by : https://github.com/cppla/ServerStatus, Update date: 20211009 4 | # 版本:1.0.2, 支持Python版本:2.7 to 3.9 5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures 6 | # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。 7 | 8 | SERVER = "127.0.0.1" 9 | USER = "s01" 10 | 11 | 12 | 13 | PORT = 35601 14 | PASSWORD = "USER_DEFAULT_PASSWORD" 15 | INTERVAL = 1 16 | PROBEPORT = 80 17 | PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6 18 | PING_PACKET_HISTORY_LEN = 100 19 | CU = "cu.tz.cloudcpp.com" 20 | CT = "ct.tz.cloudcpp.com" 21 | CM = "cm.tz.cloudcpp.com" 22 | 23 | import socket 24 | import time 25 | import timeit 26 | import re 27 | import os 28 | import sys 29 | import json 30 | import errno 31 | import subprocess 32 | import threading 33 | try: 34 | from queue import Queue # python3 35 | except ImportError: 36 | from Queue import Queue # python2 37 | 38 | def get_uptime(): 39 | with open('/proc/uptime', 'r') as f: 40 | uptime = f.readline().split('.', 2) 41 | return int(uptime[0]) 42 | 43 | def get_memory(): 44 | re_parser = re.compile(r'^(?P\S*):\s*(?P\d*)\s*kB') 45 | result = dict() 46 | for line in open('/proc/meminfo'): 47 | match = re_parser.match(line) 48 | if not match: 49 | continue 50 | key, value = match.groups(['key', 'value']) 51 | result[key] = int(value) 52 | MemTotal = float(result['MemTotal']) 53 | MemUsed = MemTotal-float(result['MemFree'])-float(result['Buffers'])-float(result['Cached'])-float(result['SReclaimable']) 54 | SwapTotal = float(result['SwapTotal']) 55 | SwapFree = float(result['SwapFree']) 56 | return int(MemTotal), int(MemUsed), int(SwapTotal), int(SwapFree) 57 | 58 | def get_hdd(): 59 | p = subprocess.check_output(['df', '-Tlm', '--total', '-t', 'ext4', '-t', 'ext3', '-t', 'ext2', '-t', 'reiserfs', '-t', 'jfs', '-t', 'ntfs', '-t', 'fat32', '-t', 'btrfs', '-t', 'fuseblk', '-t', 'zfs', '-t', 'simfs', '-t', 'xfs']).decode("Utf-8") 60 | total = p.splitlines()[-1] 61 | used = total.split()[3] 62 | size = total.split()[2] 63 | return int(size), int(used) 64 | 65 | def get_time(): 66 | with open("/proc/stat", "r") as f: 67 | time_list = f.readline().split(' ')[2:6] 68 | for i in range(len(time_list)) : 69 | time_list[i] = int(time_list[i]) 70 | return time_list 71 | 72 | def delta_time(): 73 | x = get_time() 74 | time.sleep(INTERVAL) 75 | y = get_time() 76 | for i in range(len(x)): 77 | y[i]-=x[i] 78 | return y 79 | 80 | def get_cpu(): 81 | t = delta_time() 82 | st = sum(t) 83 | if st == 0: 84 | st = 1 85 | result = 100-(t[len(t)-1]*100.00/st) 86 | return round(result, 1) 87 | 88 | def liuliang(): 89 | NET_IN = 0 90 | NET_OUT = 0 91 | with open('/proc/net/dev') as f: 92 | for line in f.readlines(): 93 | netinfo = re.findall('([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', line) 94 | if netinfo: 95 | if netinfo[0][0] == 'lo' or 'tun' in netinfo[0][0] \ 96 | or 'docker' in netinfo[0][0] or 'veth' in netinfo[0][0] \ 97 | or 'br-' in netinfo[0][0] or 'vmbr' in netinfo[0][0] \ 98 | or 'vnet' in netinfo[0][0] or 'kube' in netinfo[0][0] \ 99 | or netinfo[0][1]=='0' or netinfo[0][9]=='0': 100 | continue 101 | else: 102 | NET_IN += int(netinfo[0][1]) 103 | NET_OUT += int(netinfo[0][9]) 104 | return NET_IN, NET_OUT 105 | 106 | def tupd(): 107 | ''' 108 | tcp, udp, process, thread count: for view ddcc attack , then send warning 109 | :return: 110 | ''' 111 | s = subprocess.check_output("ss -t|wc -l", shell=True) 112 | t = int(s[:-1])-1 113 | s = subprocess.check_output("ss -u|wc -l", shell=True) 114 | u = int(s[:-1])-1 115 | s = subprocess.check_output("ps -ef|wc -l", shell=True) 116 | p = int(s[:-1])-2 117 | s = subprocess.check_output("ps -eLf|wc -l", shell=True) 118 | d = int(s[:-1])-2 119 | return t,u,p,d 120 | 121 | def get_network(ip_version): 122 | if(ip_version == 4): 123 | HOST = "ipv4.google.com" 124 | elif(ip_version == 6): 125 | HOST = "ipv6.google.com" 126 | try: 127 | socket.create_connection((HOST, 80), 2).close() 128 | return True 129 | except: 130 | return False 131 | 132 | lostRate = { 133 | '10010': 0.0, 134 | '189': 0.0, 135 | '10086': 0.0 136 | } 137 | pingTime = { 138 | '10010': 0, 139 | '189': 0, 140 | '10086': 0 141 | } 142 | netSpeed = { 143 | 'netrx': 0.0, 144 | 'nettx': 0.0, 145 | 'clock': 0.0, 146 | 'diff': 0.0, 147 | 'avgrx': 0, 148 | 'avgtx': 0 149 | } 150 | 151 | def _ping_thread(host, mark, port): 152 | lostPacket = 0 153 | packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN) 154 | 155 | IP = host 156 | if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname 157 | try: 158 | if PROBE_PROTOCOL_PREFER == 'ipv4': 159 | IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0] 160 | else: 161 | IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0] 162 | except Exception: 163 | pass 164 | 165 | while True: 166 | if packet_queue.full(): 167 | if packet_queue.get() == 0: 168 | lostPacket -= 1 169 | try: 170 | b = timeit.default_timer() 171 | socket.create_connection((IP, port), timeout=1).close() 172 | pingTime[mark] = int((timeit.default_timer() - b) * 1000) 173 | packet_queue.put(1) 174 | except socket.error as error: 175 | if error.errno == errno.ECONNREFUSED: 176 | pingTime[mark] = int((timeit.default_timer() - b) * 1000) 177 | packet_queue.put(1) 178 | #elif error.errno == errno.ETIMEDOUT: 179 | else: 180 | lostPacket += 1 181 | packet_queue.put(0) 182 | 183 | if packet_queue.qsize() > 30: 184 | lostRate[mark] = float(lostPacket) / packet_queue.qsize() 185 | 186 | time.sleep(INTERVAL) 187 | 188 | def _net_speed(): 189 | while True: 190 | with open("/proc/net/dev", "r") as f: 191 | net_dev = f.readlines() 192 | avgrx = 0 193 | avgtx = 0 194 | for dev in net_dev[2:]: 195 | dev = dev.split(':') 196 | if "lo" in dev[0] or "tun" in dev[0] \ 197 | or "docker" in dev[0] or "veth" in dev[0] \ 198 | or "br-" in dev[0] or "vmbr" in dev[0] \ 199 | or "vnet" in dev[0] or "kube" in dev[0]: 200 | continue 201 | dev = dev[1].split() 202 | avgrx += int(dev[0]) 203 | avgtx += int(dev[8]) 204 | now_clock = time.time() 205 | netSpeed["diff"] = now_clock - netSpeed["clock"] 206 | netSpeed["clock"] = now_clock 207 | netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"]) 208 | netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"]) 209 | netSpeed["avgrx"] = avgrx 210 | netSpeed["avgtx"] = avgtx 211 | time.sleep(INTERVAL) 212 | 213 | def get_realtime_date(): 214 | t1 = threading.Thread( 215 | target=_ping_thread, 216 | kwargs={ 217 | 'host': CU, 218 | 'mark': '10010', 219 | 'port': PROBEPORT 220 | } 221 | ) 222 | t2 = threading.Thread( 223 | target=_ping_thread, 224 | kwargs={ 225 | 'host': CT, 226 | 'mark': '189', 227 | 'port': PROBEPORT 228 | } 229 | ) 230 | t3 = threading.Thread( 231 | target=_ping_thread, 232 | kwargs={ 233 | 'host': CM, 234 | 'mark': '10086', 235 | 'port': PROBEPORT 236 | } 237 | ) 238 | t4 = threading.Thread( 239 | target=_net_speed, 240 | ) 241 | t1.setDaemon(True) 242 | t2.setDaemon(True) 243 | t3.setDaemon(True) 244 | t4.setDaemon(True) 245 | t1.start() 246 | t2.start() 247 | t3.start() 248 | t4.start() 249 | 250 | def byte_str(object): 251 | ''' 252 | bytes to str, str to bytes 253 | :param object: 254 | :return: 255 | ''' 256 | if isinstance(object, str): 257 | return object.encode(encoding="utf-8") 258 | elif isinstance(object, bytes): 259 | return bytes.decode(object) 260 | else: 261 | print(type(object)) 262 | 263 | if __name__ == '__main__': 264 | for argc in sys.argv: 265 | if 'SERVER' in argc: 266 | SERVER = argc.split('SERVER=')[-1] 267 | elif 'PORT' in argc: 268 | PORT = int(argc.split('PORT=')[-1]) 269 | elif 'USER' in argc: 270 | USER = argc.split('USER=')[-1] 271 | elif 'PASSWORD' in argc: 272 | PASSWORD = argc.split('PASSWORD=')[-1] 273 | elif 'INTERVAL' in argc: 274 | INTERVAL = int(argc.split('INTERVAL=')[-1]) 275 | socket.setdefaulttimeout(30) 276 | get_realtime_date() 277 | while True: 278 | try: 279 | print("Connecting...") 280 | s = socket.create_connection((SERVER, PORT)) 281 | data = byte_str(s.recv(1024)) 282 | if data.find("Authentication required") > -1: 283 | s.send(byte_str(USER + ':' + PASSWORD + '\n')) 284 | data = byte_str(s.recv(1024)) 285 | if data.find("Authentication successful") < 0: 286 | print(data) 287 | raise socket.error 288 | else: 289 | print(data) 290 | raise socket.error 291 | 292 | print(data) 293 | if data.find("You are connecting via") < 0: 294 | data = byte_str(s.recv(1024)) 295 | print(data) 296 | 297 | timer = 0 298 | check_ip = 0 299 | if data.find("IPv4") > -1: 300 | check_ip = 6 301 | elif data.find("IPv6") > -1: 302 | check_ip = 4 303 | else: 304 | print(data) 305 | raise socket.error 306 | 307 | while True: 308 | CPU = get_cpu() 309 | NET_IN, NET_OUT = liuliang() 310 | Uptime = get_uptime() 311 | Load_1, Load_5, Load_15 = os.getloadavg() 312 | MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory() 313 | HDDTotal, HDDUsed = get_hdd() 314 | 315 | array = {} 316 | if not timer: 317 | array['online' + str(check_ip)] = get_network(check_ip) 318 | timer = 10 319 | else: 320 | timer -= 1*INTERVAL 321 | 322 | array['uptime'] = Uptime 323 | array['load_1'] = Load_1 324 | array['load_5'] = Load_5 325 | array['load_15'] = Load_15 326 | array['memory_total'] = MemoryTotal 327 | array['memory_used'] = MemoryUsed 328 | array['swap_total'] = SwapTotal 329 | array['swap_used'] = SwapTotal - SwapFree 330 | array['hdd_total'] = HDDTotal 331 | array['hdd_used'] = HDDUsed 332 | array['cpu'] = CPU 333 | array['network_rx'] = netSpeed.get("netrx") 334 | array['network_tx'] = netSpeed.get("nettx") 335 | array['network_in'] = NET_IN 336 | array['network_out'] = NET_OUT 337 | # todo:兼容旧版本,下个版本删除ip_status 338 | array['ip_status'] = True 339 | array['ping_10010'] = lostRate.get('10010') * 100 340 | array['ping_189'] = lostRate.get('189') * 100 341 | array['ping_10086'] = lostRate.get('10086') * 100 342 | array['time_10010'] = pingTime.get('10010') 343 | array['time_189'] = pingTime.get('189') 344 | array['time_10086'] = pingTime.get('10086') 345 | array['tcp'], array['udp'], array['process'], array['thread'] = tupd() 346 | 347 | s.send(byte_str("update " + json.dumps(array) + "\n")) 348 | except KeyboardInterrupt: 349 | raise 350 | except socket.error: 351 | print("Disconnected...") 352 | if 's' in locals().keys(): 353 | del s 354 | time.sleep(3) 355 | except Exception as e: 356 | print("Caught Exception:", e) 357 | if 's' in locals().keys(): 358 | del s 359 | time.sleep(3) 360 | -------------------------------------------------------------------------------- /clients/client-psutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # Update by : https://github.com/cppla/ServerStatus, Update date: 20211009 4 | # 依赖于psutil跨平台库 5 | # 版本:1.0.2, 支持Python版本:2.7 to 3.9 6 | # 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures 7 | # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。 8 | 9 | SERVER = "127.0.0.1" 10 | USER = "s01" 11 | 12 | 13 | 14 | PORT = 35601 15 | PASSWORD = "USER_DEFAULT_PASSWORD" 16 | INTERVAL = 1 17 | PROBEPORT = 80 18 | PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6 19 | PING_PACKET_HISTORY_LEN = 100 20 | CU = "cu.tz.cloudcpp.com" 21 | CT = "ct.tz.cloudcpp.com" 22 | CM = "cm.tz.cloudcpp.com" 23 | 24 | import socket 25 | import time 26 | import timeit 27 | import os 28 | import sys 29 | import json 30 | import errno 31 | import psutil 32 | import threading 33 | try: 34 | from queue import Queue # python3 35 | except ImportError: 36 | from Queue import Queue # python2 37 | 38 | def get_uptime(): 39 | return int(time.time() - psutil.boot_time()) 40 | 41 | def get_memory(): 42 | Mem = psutil.virtual_memory() 43 | return int(Mem.total / 1024.0), int(Mem.used / 1024.0) 44 | 45 | def get_swap(): 46 | Mem = psutil.swap_memory() 47 | return int(Mem.total/1024.0), int(Mem.used/1024.0) 48 | 49 | def get_hdd(): 50 | valid_fs = [ "ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs" ] 51 | disks = dict() 52 | size = 0 53 | used = 0 54 | for disk in psutil.disk_partitions(): 55 | if not disk.device in disks and disk.fstype.lower() in valid_fs: 56 | disks[disk.device] = disk.mountpoint 57 | for disk in disks.values(): 58 | usage = psutil.disk_usage(disk) 59 | size += usage.total 60 | used += usage.used 61 | return int(size/1024.0/1024.0), int(used/1024.0/1024.0) 62 | 63 | def get_cpu(): 64 | return psutil.cpu_percent(interval=INTERVAL) 65 | 66 | def liuliang(): 67 | NET_IN = 0 68 | NET_OUT = 0 69 | net = psutil.net_io_counters(pernic=True) 70 | for k, v in net.items(): 71 | if 'lo' in k or 'tun' in k \ 72 | or 'docker' in k or 'veth' in k \ 73 | or 'br-' in k or 'vmbr' in k \ 74 | or 'vnet' in k or 'kube' in k: 75 | continue 76 | else: 77 | NET_IN += v[1] 78 | NET_OUT += v[0] 79 | return NET_IN, NET_OUT 80 | 81 | def tupd(): 82 | ''' 83 | tcp, udp, process, thread count: for view ddcc attack , then send warning 84 | :return: 85 | ''' 86 | try: 87 | if sys.platform.startswith("linux") is True: 88 | t = int(os.popen('ss -t|wc -l').read()[:-1])-1 89 | u = int(os.popen('ss -u|wc -l').read()[:-1])-1 90 | p = int(os.popen('ps -ef|wc -l').read()[:-1])-2 91 | d = int(os.popen('ps -eLf|wc -l').read()[:-1])-2 92 | elif sys.platform.startswith("win") is True: 93 | t = int(os.popen('netstat -an|find "TCP" /c').read()[:-1])-1 94 | u = int(os.popen('netstat -an|find "UDP" /c').read()[:-1])-1 95 | p = len(psutil.pids()) 96 | d = 0 97 | # cpu is high, default: 0 98 | # d = sum([psutil.Process(k).num_threads() for k in [x for x in psutil.pids()]]) 99 | else: 100 | t,u,p,d = 0,0,0,0 101 | return t,u,p,d 102 | except: 103 | return 0,0,0,0 104 | 105 | def get_network(ip_version): 106 | if(ip_version == 4): 107 | HOST = "ipv4.google.com" 108 | elif(ip_version == 6): 109 | HOST = "ipv6.google.com" 110 | try: 111 | socket.create_connection((HOST, 80), 2).close() 112 | return True 113 | except: 114 | return False 115 | 116 | lostRate = { 117 | '10010': 0.0, 118 | '189': 0.0, 119 | '10086': 0.0 120 | } 121 | pingTime = { 122 | '10010': 0, 123 | '189': 0, 124 | '10086': 0 125 | } 126 | netSpeed = { 127 | 'netrx': 0.0, 128 | 'nettx': 0.0, 129 | 'clock': 0.0, 130 | 'diff': 0.0, 131 | 'avgrx': 0, 132 | 'avgtx': 0 133 | } 134 | 135 | def _ping_thread(host, mark, port): 136 | lostPacket = 0 137 | packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN) 138 | 139 | IP = host 140 | if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname 141 | try: 142 | if PROBE_PROTOCOL_PREFER == 'ipv4': 143 | IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0] 144 | else: 145 | IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0] 146 | except Exception: 147 | pass 148 | 149 | while True: 150 | if packet_queue.full(): 151 | if packet_queue.get() == 0: 152 | lostPacket -= 1 153 | try: 154 | b = timeit.default_timer() 155 | socket.create_connection((IP, port), timeout=1).close() 156 | pingTime[mark] = int((timeit.default_timer() - b) * 1000) 157 | packet_queue.put(1) 158 | except socket.error as error: 159 | if error.errno == errno.ECONNREFUSED: 160 | pingTime[mark] = int((timeit.default_timer() - b) * 1000) 161 | packet_queue.put(1) 162 | #elif error.errno == errno.ETIMEDOUT: 163 | else: 164 | lostPacket += 1 165 | packet_queue.put(0) 166 | 167 | if packet_queue.qsize() > 30: 168 | lostRate[mark] = float(lostPacket) / packet_queue.qsize() 169 | 170 | time.sleep(INTERVAL) 171 | 172 | def _net_speed(): 173 | while True: 174 | avgrx = 0 175 | avgtx = 0 176 | for name, stats in psutil.net_io_counters(pernic=True).items(): 177 | if "lo" in name or "tun" in name \ 178 | or "docker" in name or "veth" in name \ 179 | or "br-" in name or "vmbr" in name \ 180 | or "vnet" in name or "kube" in name: 181 | continue 182 | avgrx += stats.bytes_recv 183 | avgtx += stats.bytes_sent 184 | now_clock = time.time() 185 | netSpeed["diff"] = now_clock - netSpeed["clock"] 186 | netSpeed["clock"] = now_clock 187 | netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"]) 188 | netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"]) 189 | netSpeed["avgrx"] = avgrx 190 | netSpeed["avgtx"] = avgtx 191 | time.sleep(INTERVAL) 192 | 193 | def get_realtime_date(): 194 | t1 = threading.Thread( 195 | target=_ping_thread, 196 | kwargs={ 197 | 'host': CU, 198 | 'mark': '10010', 199 | 'port': PROBEPORT 200 | } 201 | ) 202 | t2 = threading.Thread( 203 | target=_ping_thread, 204 | kwargs={ 205 | 'host': CT, 206 | 'mark': '189', 207 | 'port': PROBEPORT 208 | } 209 | ) 210 | t3 = threading.Thread( 211 | target=_ping_thread, 212 | kwargs={ 213 | 'host': CM, 214 | 'mark': '10086', 215 | 'port': PROBEPORT 216 | } 217 | ) 218 | t4 = threading.Thread( 219 | target=_net_speed, 220 | ) 221 | t1.setDaemon(True) 222 | t2.setDaemon(True) 223 | t3.setDaemon(True) 224 | t4.setDaemon(True) 225 | t1.start() 226 | t2.start() 227 | t3.start() 228 | t4.start() 229 | 230 | def byte_str(object): 231 | ''' 232 | bytes to str, str to bytes 233 | :param object: 234 | :return: 235 | ''' 236 | if isinstance(object, str): 237 | return object.encode(encoding="utf-8") 238 | elif isinstance(object, bytes): 239 | return bytes.decode(object) 240 | else: 241 | print(type(object)) 242 | 243 | if __name__ == '__main__': 244 | for argc in sys.argv: 245 | if 'SERVER' in argc: 246 | SERVER = argc.split('SERVER=')[-1] 247 | elif 'PORT' in argc: 248 | PORT = int(argc.split('PORT=')[-1]) 249 | elif 'USER' in argc: 250 | USER = argc.split('USER=')[-1] 251 | elif 'PASSWORD' in argc: 252 | PASSWORD = argc.split('PASSWORD=')[-1] 253 | elif 'INTERVAL' in argc: 254 | INTERVAL = int(argc.split('INTERVAL=')[-1]) 255 | socket.setdefaulttimeout(30) 256 | get_realtime_date() 257 | while 1: 258 | try: 259 | print("Connecting...") 260 | s = socket.create_connection((SERVER, PORT)) 261 | data = byte_str(s.recv(1024)) 262 | if data.find("Authentication required") > -1: 263 | s.send(byte_str(USER + ':' + PASSWORD + '\n')) 264 | data = byte_str(s.recv(1024)) 265 | if data.find("Authentication successful") < 0: 266 | print(data) 267 | raise socket.error 268 | else: 269 | print(data) 270 | raise socket.error 271 | 272 | print(data) 273 | if data.find("You are connecting via") < 0: 274 | data = byte_str(s.recv(1024)) 275 | print(data) 276 | 277 | timer = 0 278 | check_ip = 0 279 | if data.find("IPv4") > -1: 280 | check_ip = 6 281 | elif data.find("IPv6") > -1: 282 | check_ip = 4 283 | else: 284 | print(data) 285 | raise socket.error 286 | 287 | while 1: 288 | CPU = get_cpu() 289 | NET_IN, NET_OUT = liuliang() 290 | Uptime = get_uptime() 291 | Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform else (0.0, 0.0, 0.0) 292 | MemoryTotal, MemoryUsed = get_memory() 293 | SwapTotal, SwapUsed = get_swap() 294 | HDDTotal, HDDUsed = get_hdd() 295 | 296 | array = {} 297 | if not timer: 298 | array['online' + str(check_ip)] = get_network(check_ip) 299 | timer = 10 300 | else: 301 | timer -= 1*INTERVAL 302 | 303 | array['uptime'] = Uptime 304 | array['load_1'] = Load_1 305 | array['load_5'] = Load_5 306 | array['load_15'] = Load_15 307 | array['memory_total'] = MemoryTotal 308 | array['memory_used'] = MemoryUsed 309 | array['swap_total'] = SwapTotal 310 | array['swap_used'] = SwapUsed 311 | array['hdd_total'] = HDDTotal 312 | array['hdd_used'] = HDDUsed 313 | array['cpu'] = CPU 314 | array['network_rx'] = netSpeed.get("netrx") 315 | array['network_tx'] = netSpeed.get("nettx") 316 | array['network_in'] = NET_IN 317 | array['network_out'] = NET_OUT 318 | # todo:兼容旧版本,下个版本删除ip_status 319 | array['ip_status'] = True 320 | array['ping_10010'] = lostRate.get('10010') * 100 321 | array['ping_189'] = lostRate.get('189') * 100 322 | array['ping_10086'] = lostRate.get('10086') * 100 323 | array['time_10010'] = pingTime.get('10010') 324 | array['time_189'] = pingTime.get('189') 325 | array['time_10086'] = pingTime.get('10086') 326 | array['tcp'], array['udp'], array['process'], array['thread'] = tupd() 327 | 328 | s.send(byte_str("update " + json.dumps(array) + "\n")) 329 | except KeyboardInterrupt: 330 | raise 331 | except socket.error: 332 | print("Disconnected...") 333 | if 's' in locals().keys(): 334 | del s 335 | time.sleep(3) 336 | except Exception as e: 337 | print("Caught Exception:", e) 338 | if 's' in locals().keys(): 339 | del s 340 | time.sleep(3) 341 | -------------------------------------------------------------------------------- /docker-compose-telegram.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | serverstatus: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | image: serverstatus_server 8 | container_name: serverstatus 9 | restart: unless-stopped 10 | networks: 11 | serverstatus-network: 12 | ipv4_address: 172.23.0.2 13 | volumes: 14 | - ./server/config.json:/ServerStatus/server/config.json 15 | - ./web/json:/usr/share/nginx/html/json 16 | ports: 17 | - 35601:35601 18 | - 8080:80 19 | bot: 20 | build: 21 | context: ./plugin 22 | dockerfile: Dockerfile-telegram 23 | image: serverstatus_bot 24 | container_name: bot4sss 25 | restart: unless-stopped 26 | networks: 27 | serverstatus-network: 28 | ipv4_address: 172.23.0.3 29 | environment: 30 | - TG_CHAT_ID=${TG_CHAT_ID} 31 | - TG_BOT_TOKEN=${TG_BOT_TOKEN} 32 | 33 | networks: 34 | serverstatus-network: 35 | name: serverstatus-network 36 | ipam: 37 | config: 38 | - subnet: 172.23.0.0/24 39 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | serverstatus: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | image: serverstatus_server 8 | container_name: serverstatus 9 | restart: unless-stopped 10 | networks: 11 | serverstatus-network: 12 | ipv4_address: 172.23.0.2 13 | volumes: 14 | - ./server/config.json:/ServerStatus/server/config.json 15 | - ./web/json:/usr/share/nginx/html/json 16 | ports: 17 | - 35601:35601 18 | - 8080:80 19 | 20 | networks: 21 | serverstatus-network: 22 | name: serverstatus-network 23 | ipam: 24 | config: 25 | - subnet: 172.23.0.0/24 26 | -------------------------------------------------------------------------------- /plugin/Dockerfile-telegram: -------------------------------------------------------------------------------- 1 | FROM python:alpine 2 | 3 | LABEL maintainer="lidalao" 4 | LABEL version="0.0.1" 5 | LABEL description="Telegram Bot for ServerStatus" 6 | 7 | WORKDIR /app 8 | RUN pip install requests 9 | COPY ./bot-telegram.py . 10 | CMD [ "python", "./bot-telegram.py" ] 11 | -------------------------------------------------------------------------------- /plugin/bot-telegram.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # Create by : https://github.com/lidalao/ServerStatus 4 | # 版本:0.0.1, 支持Python版本:2.7 to 3.9 5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures 6 | 7 | import os 8 | import sys 9 | import requests 10 | import time 11 | import traceback 12 | 13 | NODE_STATUS_URL = 'http://serverstatus/json/stats.json' 14 | 15 | offs = [] 16 | counterOff = {} 17 | counterOn = {} 18 | 19 | def _send(text): 20 | chat_id = os.getenv('TG_CHAT_ID') 21 | bot_token = os.environ.get('TG_BOT_TOKEN') 22 | url = f"https://api.telegram.org/bot{bot_token}/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=" + chat_id + "&text=" + text 23 | try: 24 | requests.get(url) 25 | except Exception as e: 26 | print("catch exception: ", traceback.format_exc()) 27 | 28 | def send2tg(srv, flag): 29 | if srv not in counterOff: 30 | counterOff[srv] = 0 31 | if srv not in counterOn: 32 | counterOn[srv] = 0 33 | 34 | if flag == 1 : # online 35 | if srv in offs: 36 | if counterOn[srv] < 10: 37 | counterOn[srv] += 1 38 | return 39 | #1. Remove srv from offs; 2. Send to tg: I am online 40 | offs.remove(srv) 41 | counterOn[srv] = 0 42 | text = 'Server Status' + '\n主机上线: ' + srv 43 | _send(text) 44 | else: #offline 45 | if srv not in offs: 46 | if counterOff[srv] < 10: 47 | counterOff[srv] += 1 48 | return 49 | #1. Append srv to offs; 2. Send to tg: I am offline 50 | offs.append(srv) 51 | counterOff[srv] = 0 52 | text = 'Server Status' + '\n主机下线: ' + srv 53 | _send(text) 54 | 55 | def sscmd(address): 56 | while True: 57 | r = requests.get(url=address, headers={"User-Agent": "ServerStatus/20211116"}) 58 | try: 59 | jsonR = r.json() 60 | except Exception as e: 61 | print('未发现任何节点') 62 | continue 63 | for i in jsonR["servers"]: 64 | if i["online4"] is False and i["online6"] is False: 65 | send2tg(i["name"], 0) 66 | else: 67 | send2tg(i["name"], 1) 68 | 69 | 70 | time.sleep(3) 71 | 72 | if __name__ == '__main__': 73 | sscmd(NODE_STATUS_URL) 74 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | sergate 2 | .tags* 3 | -------------------------------------------------------------------------------- /server/Makefile: -------------------------------------------------------------------------------- 1 | OUT = sergate 2 | 3 | #CC = clang 4 | CC = gcc 5 | CFLAGS = -Wall -O2 6 | 7 | #CXX = clang++ 8 | CXX = g++ 9 | CXXFLAGS = -Wall -O2 10 | 11 | ODIR = obj 12 | SDIR = src 13 | LIBS = -pthread -lm 14 | INC = -Iinclude 15 | 16 | C_SRCS := $(wildcard $(SDIR)/*.c) 17 | CXX_SRCS := $(wildcard $(SDIR)/*.cpp) 18 | C_OBJS := $(patsubst $(SDIR)/%.c,$(ODIR)/%.o,$(C_SRCS)) 19 | CXX_OBJS := $(patsubst $(SDIR)/%.cpp,$(ODIR)/%.o,$(CXX_SRCS)) 20 | OBJS := $(C_OBJS) $(CXX_OBJS) 21 | 22 | $(ODIR)/%.o: $(SDIR)/%.c 23 | $(CC) -c $(INC) $(CFLAGS) $< -o $@ 24 | 25 | $(ODIR)/%.o: $(SDIR)/%.cpp 26 | $(CXX) -c $(INC) $(CXXFLAGS) $< -o $@ 27 | 28 | $(OUT): $(OBJS) 29 | $(CXX) $(LIBS) $^ -o $(OUT) 30 | 31 | .PHONY: clean 32 | 33 | clean: 34 | rm -f $(ODIR)/*.o $(OUT) 35 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | {"servers": 2 | [ 3 | { 4 | "username": "s01", 5 | "name": "node1", 6 | "type": "xen", 7 | "host": "host1", 8 | "location": "cn", 9 | "password": "USER_DEFAULT_PASSWORD", 10 | "monthstart": 1, 11 | "extra1":"", 12 | "extra2":"", 13 | "extra3":"" 14 | }, 15 | { 16 | "username": "s02", 17 | "name": "node2", 18 | "type": "vmware", 19 | "host": "host2", 20 | "location": "jp", 21 | "password": "USER_DEFAULT_PASSWORD", 22 | "monthstart": 1, 23 | "extra1":"", 24 | "extra2":"", 25 | "extra3":"" 26 | }, 27 | { 28 | "disabled": true, 29 | "username": "s03", 30 | "name": "node3", 31 | "type": "Nothing", 32 | "host": "host3", 33 | "location": "fr", 34 | "password": "USER_DEFAULT_PASSWORD", 35 | "monthstart": 1, 36 | "extra1":"", 37 | "extra2":"", 38 | "extra3":"" 39 | }, 40 | { 41 | "username": "s04", 42 | "name": "node4", 43 | "type": "kvm", 44 | "host": "host4", 45 | "location": "kr", 46 | "password": "USER_DEFAULT_PASSWORD", 47 | "monthstart": 1, 48 | "extra1":"", 49 | "extra2":"", 50 | "extra3":"" 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /server/include/argparse.h: -------------------------------------------------------------------------------- 1 | #ifndef ARGPARSE_H 2 | #define ARGPARSE_H 3 | 4 | /** 5 | * Command-line arguments parsing library. 6 | * 7 | * This module is inspired by parse-options.c (git) and python's argparse 8 | * module. 9 | * 10 | * Arguments parsing is common task in cli program, but traditional `getopt` 11 | * libraries are not easy to use. This library provides high-level arguments 12 | * parsing solutions. 13 | * 14 | * The program defines what arguments it requires, and `argparse` will figure 15 | * out how to parse those out of `argc` and `argv`, it also automatically 16 | * generates help and usage messages and issues errors when users give the 17 | * program invalid arguments. 18 | * 19 | * Reserved namespaces: 20 | * argparse 21 | * OPT 22 | * Author: Yecheng Fu 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | struct argparse; 37 | struct argparse_option; 38 | 39 | typedef int argparse_callback(struct argparse *this_, 40 | const struct argparse_option *option); 41 | 42 | enum argparse_flag { 43 | ARGPARSE_STOP_AT_NON_OPTION = 1, 44 | }; 45 | 46 | enum argparse_option_type { 47 | /* special */ 48 | ARGPARSE_OPT_END, 49 | /* options with no arguments */ 50 | ARGPARSE_OPT_BOOLEAN, 51 | ARGPARSE_OPT_BIT, 52 | /* options with arguments (optional or required) */ 53 | ARGPARSE_OPT_INTEGER, 54 | ARGPARSE_OPT_STRING, 55 | }; 56 | 57 | enum argparse_option_flags { 58 | OPT_NONEG = 1, /* Negation disabled. */ 59 | }; 60 | 61 | /* 62 | * Argparse option struct. 63 | * 64 | * `type`: 65 | * holds the type of the option, you must have an ARGPARSE_OPT_END last in your 66 | * array. 67 | * 68 | * `short_name`: 69 | * the character to use as a short option name, '\0' if none. 70 | * 71 | * `long_name`: 72 | * the long option name, without the leading dash, NULL if none. 73 | * 74 | * `value`: 75 | * stores pointer to the value to be filled. 76 | * 77 | * `help`: 78 | * the short help message associated to what the option does. 79 | * Must never be NULL (except for ARGPARSE_OPT_END). 80 | * 81 | * `callback`: 82 | * function is called when corresponding argument is parsed. 83 | * 84 | * `data`: 85 | * associated data. Callbacks can use it like they want. 86 | * 87 | * `flags`: 88 | * option flags. 89 | * 90 | */ 91 | struct argparse_option { 92 | enum argparse_option_type type; 93 | const char short_name; 94 | const char *long_name; 95 | void *value; 96 | const char *help; 97 | argparse_callback *callback; 98 | intptr_t data; 99 | int flags; 100 | }; 101 | 102 | /* 103 | * argpparse 104 | */ 105 | struct argparse { 106 | // user supplied 107 | const struct argparse_option *options; 108 | const char *usage; 109 | int flags; 110 | // internal context 111 | int argc; 112 | const char **argv; 113 | const char **out; 114 | int cpidx; 115 | const char *optvalue; // current option value 116 | }; 117 | 118 | // builtin callbacks 119 | int argparse_help_cb(struct argparse *this_, 120 | const struct argparse_option *option); 121 | 122 | // builtin option macros 123 | #define OPT_END() { ARGPARSE_OPT_END, 0 } 124 | #define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } 125 | #define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } 126 | #define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } 127 | #define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } 128 | #define OPT_HELP() OPT_BOOLEAN('h', "help", 0, "Show this help message and exit", argparse_help_cb) 129 | 130 | int argparse_init(struct argparse *this_, struct argparse_option *options, 131 | const char *usage, int flags); 132 | int argparse_parse(struct argparse *this_, int argc, const char **argv); 133 | void argparse_usage(struct argparse *this_); 134 | 135 | #ifdef __cplusplus 136 | } 137 | #endif 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /server/include/detect.h: -------------------------------------------------------------------------------- 1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ 2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ 3 | #ifndef BASE_DETECT_H 4 | #define BASE_DETECT_H 5 | 6 | /* 7 | this file detected the family, platform and architecture 8 | to compile for. 9 | */ 10 | 11 | /* platforms */ 12 | 13 | /* windows Family */ 14 | #if defined(WIN64) || defined(_WIN64) 15 | /* Hmm, is this IA64 or x86-64? */ 16 | #define CONF_FAMILY_WINDOWS 1 17 | #define CONF_FAMILY_STRING "windows" 18 | #define CONF_PLATFORM_WIN64 1 19 | #define CONF_PLATFORM_STRING "win64" 20 | #elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN32__) || defined(__MINGW32__) 21 | #define CONF_FAMILY_WINDOWS 1 22 | #define CONF_FAMILY_STRING "windows" 23 | #define CONF_PLATFORM_WIN32 1 24 | #define CONF_PLATFORM_STRING "win32" 25 | #endif 26 | 27 | /* unix family */ 28 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) 29 | #define CONF_FAMILY_UNIX 1 30 | #define CONF_FAMILY_STRING "unix" 31 | #define CONF_PLATFORM_FREEBSD 1 32 | #define CONF_PLATFORM_STRING "freebsd" 33 | #endif 34 | 35 | #if defined(__OpenBSD__) 36 | #define CONF_FAMILY_UNIX 1 37 | #define CONF_FAMILY_STRING "unix" 38 | #define CONF_PLATFORM_OPENBSD 1 39 | #define CONF_PLATFORM_STRING "openbsd" 40 | #endif 41 | 42 | #if defined(__LINUX__) || defined(__linux__) 43 | #define CONF_FAMILY_UNIX 1 44 | #define CONF_FAMILY_STRING "unix" 45 | #define CONF_PLATFORM_LINUX 1 46 | #define CONF_PLATFORM_STRING "linux" 47 | #endif 48 | 49 | #if defined(__GNU__) || defined(__gnu__) 50 | #define CONF_FAMILY_UNIX 1 51 | #define CONF_FAMILY_STRING "unix" 52 | #define CONF_PLATFORM_HURD 1 53 | #define CONF_PLATFORM_STRING "gnu" 54 | #endif 55 | 56 | #if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__) 57 | #define CONF_FAMILY_UNIX 1 58 | #define CONF_FAMILY_STRING "unix" 59 | #define CONF_PLATFORM_MACOSX 1 60 | #define CONF_PLATFORM_STRING "macosx" 61 | #endif 62 | 63 | #if defined(__sun) 64 | #define CONF_FAMILY_UNIX 1 65 | #define CONF_FAMILY_STRING "unix" 66 | #define CONF_PLATFORM_SOLARIS 1 67 | #define CONF_PLATFORM_STRING "solaris" 68 | #endif 69 | 70 | /* beos family */ 71 | #if defined(__BeOS) || defined(__BEOS__) 72 | #define CONF_FAMILY_BEOS 1 73 | #define CONF_FAMILY_STRING "beos" 74 | #define CONF_PLATFORM_BEOS 1 75 | #define CONF_PLATFORM_STRING "beos" 76 | #endif 77 | 78 | 79 | /* use gcc endianness definitions when available */ 80 | #if defined(__GNUC__) && !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__sun) 81 | #if defined(__FreeBSD__) || defined(__OpenBSD__) 82 | #include 83 | #else 84 | #include 85 | #endif 86 | 87 | #if __BYTE_ORDER == __LITTLE_ENDIAN 88 | #define CONF_ARCH_ENDIAN_LITTLE 1 89 | #elif __BYTE_ORDER == __BIG_ENDIAN 90 | #define CONF_ARCH_ENDIAN_BIG 1 91 | #endif 92 | #endif 93 | 94 | 95 | /* architectures */ 96 | #if defined(i386) || defined(__i386__) || defined(__x86__) || defined(CONF_PLATFORM_WIN32) 97 | #define CONF_ARCH_IA32 1 98 | #define CONF_ARCH_STRING "ia32" 99 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) 100 | #define CONF_ARCH_ENDIAN_LITTLE 1 101 | #endif 102 | #endif 103 | 104 | #if defined(__ia64__) || defined(_M_IA64) 105 | #define CONF_ARCH_IA64 1 106 | #define CONF_ARCH_STRING "ia64" 107 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) 108 | #define CONF_ARCH_ENDIAN_LITTLE 1 109 | #endif 110 | #endif 111 | 112 | #if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) 113 | #define CONF_ARCH_AMD64 1 114 | #define CONF_ARCH_STRING "amd64" 115 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) 116 | #define CONF_ARCH_ENDIAN_LITTLE 1 117 | #endif 118 | #endif 119 | 120 | #if defined(__powerpc__) || defined(__ppc__) 121 | #define CONF_ARCH_PPC 1 122 | #define CONF_ARCH_STRING "ppc" 123 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) 124 | #define CONF_ARCH_ENDIAN_BIG 1 125 | #endif 126 | #endif 127 | 128 | #if defined(__sparc__) 129 | #define CONF_ARCH_SPARC 1 130 | #define CONF_ARCH_STRING "sparc" 131 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG) 132 | #define CONF_ARCH_ENDIAN_BIG 1 133 | #endif 134 | #endif 135 | 136 | 137 | #ifndef CONF_FAMILY_STRING 138 | #define CONF_FAMILY_STRING "unknown" 139 | #endif 140 | 141 | #ifndef CONF_PLATFORM_STRING 142 | #define CONF_PLATFORM_STRING "unknown" 143 | #endif 144 | 145 | #ifndef CONF_ARCH_STRING 146 | #define CONF_ARCH_STRING "unknown" 147 | #endif 148 | 149 | #endif 150 | -------------------------------------------------------------------------------- /server/include/json.h: -------------------------------------------------------------------------------- 1 | 2 | /* vim: set et ts=3 sw=3 sts=3 ft=c: 3 | * 4 | * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. 5 | * https://github.com/udp/json-parser 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef _JSON_H 32 | #define _JSON_H 33 | 34 | #ifndef json_char 35 | #define json_char char 36 | #endif 37 | 38 | #ifndef json_int_t 39 | #ifndef _MSC_VER 40 | #include 41 | #define json_int_t int64_t 42 | #else 43 | #define json_int_t __int64 44 | #endif 45 | #endif 46 | 47 | #include 48 | 49 | #ifdef __cplusplus 50 | 51 | #include 52 | 53 | extern "C" 54 | { 55 | 56 | #endif 57 | 58 | typedef struct 59 | { 60 | unsigned long max_memory; 61 | int settings; 62 | 63 | /* Custom allocator support (leave null to use malloc/free) 64 | */ 65 | 66 | void * (* mem_alloc) (size_t, int zero, void * user_data); 67 | void (* mem_free) (void *, void * user_data); 68 | 69 | void * user_data; /* will be passed to mem_alloc and mem_free */ 70 | 71 | } json_settings; 72 | 73 | #define json_enable_comments 0x01 74 | 75 | typedef enum 76 | { 77 | json_none, 78 | json_object, 79 | json_array, 80 | json_integer, 81 | json_double, 82 | json_string, 83 | json_boolean, 84 | json_null 85 | 86 | } json_type; 87 | 88 | extern const struct _json_value json_value_none; 89 | 90 | typedef struct _json_value 91 | { 92 | struct _json_value * parent; 93 | 94 | json_type type; 95 | 96 | union 97 | { 98 | int boolean; 99 | json_int_t integer; 100 | double dbl; 101 | 102 | struct 103 | { 104 | unsigned int length; 105 | json_char * ptr; /* null terminated */ 106 | 107 | } string; 108 | 109 | struct 110 | { 111 | unsigned int length; 112 | 113 | struct 114 | { 115 | json_char * name; 116 | unsigned int name_length; 117 | 118 | struct _json_value * value; 119 | 120 | } * values; 121 | 122 | #if defined(__cplusplus) && __cplusplus >= 201103L 123 | decltype(values) begin () const 124 | { return values; 125 | } 126 | decltype(values) end () const 127 | { return values + length; 128 | } 129 | #endif 130 | 131 | } object; 132 | 133 | struct 134 | { 135 | unsigned int length; 136 | struct _json_value ** values; 137 | 138 | #if defined(__cplusplus) && __cplusplus >= 201103L 139 | decltype(values) begin () const 140 | { return values; 141 | } 142 | decltype(values) end () const 143 | { return values + length; 144 | } 145 | #endif 146 | 147 | } array; 148 | 149 | } u; 150 | 151 | union 152 | { 153 | struct _json_value * next_alloc; 154 | void * object_mem; 155 | 156 | } _reserved; 157 | 158 | 159 | /* Some C++ operator sugar */ 160 | 161 | #ifdef __cplusplus 162 | 163 | public: 164 | 165 | inline _json_value () 166 | { memset (this, 0, sizeof (_json_value)); 167 | } 168 | 169 | inline const struct _json_value &operator [] (int index) const 170 | { 171 | if (type != json_array || index < 0 172 | || ((unsigned int) index) >= u.array.length) 173 | { 174 | return json_value_none; 175 | } 176 | 177 | return *u.array.values [index]; 178 | } 179 | 180 | inline const struct _json_value &operator [] (const char * index) const 181 | { 182 | if (type != json_object) 183 | return json_value_none; 184 | 185 | for (unsigned int i = 0; i < u.object.length; ++ i) 186 | if (!strcmp (u.object.values [i].name, index)) 187 | return *u.object.values [i].value; 188 | 189 | return json_value_none; 190 | } 191 | 192 | inline operator const char * () const 193 | { 194 | switch (type) 195 | { 196 | case json_string: 197 | return u.string.ptr; 198 | 199 | default: 200 | return ""; 201 | }; 202 | } 203 | 204 | inline operator json_int_t () const 205 | { 206 | switch (type) 207 | { 208 | case json_integer: 209 | return u.integer; 210 | 211 | case json_double: 212 | return (json_int_t) u.dbl; 213 | 214 | default: 215 | return 0; 216 | }; 217 | } 218 | 219 | inline operator bool () const 220 | { 221 | if (type != json_boolean) 222 | return false; 223 | 224 | return u.boolean != 0; 225 | } 226 | 227 | inline operator double () const 228 | { 229 | switch (type) 230 | { 231 | case json_integer: 232 | return (double) u.integer; 233 | 234 | case json_double: 235 | return u.dbl; 236 | 237 | default: 238 | return 0; 239 | }; 240 | } 241 | 242 | #endif 243 | 244 | } json_value; 245 | 246 | json_value * json_parse (const json_char * json, 247 | size_t length); 248 | 249 | #define json_error_max 128 250 | json_value * json_parse_ex (json_settings * settings, 251 | const json_char * json, 252 | size_t length, 253 | char * error); 254 | 255 | void json_value_free (json_value *); 256 | 257 | 258 | /* Not usually necessary, unless you used a custom mem_alloc and now want to 259 | * use a custom mem_free. 260 | */ 261 | void json_value_free_ex (json_settings * settings, 262 | json_value *); 263 | 264 | 265 | #ifdef __cplusplus 266 | } /* extern "C" */ 267 | #endif 268 | 269 | #endif 270 | -------------------------------------------------------------------------------- /server/obj/.gitignore: -------------------------------------------------------------------------------- 1 | *.o -------------------------------------------------------------------------------- /server/src/argparse.c: -------------------------------------------------------------------------------- 1 | #include "argparse.h" 2 | 3 | #if defined(__cplusplus) 4 | extern "C" { 5 | #endif 6 | 7 | #define OPT_UNSET 1 8 | 9 | static const char * 10 | prefix_skip(const char *str, const char *prefix) 11 | { 12 | size_t len = strlen(prefix); 13 | return strncmp(str, prefix, len) ? NULL : str + len; 14 | } 15 | 16 | int 17 | prefix_cmp(const char *str, const char *prefix) 18 | { 19 | for (;; str++, prefix++) 20 | if (!*prefix) 21 | return 0; 22 | else if (*str != *prefix) 23 | return (unsigned char)*prefix - (unsigned char)*str; 24 | } 25 | 26 | static void 27 | argparse_error(struct argparse *this_, const struct argparse_option *opt, 28 | const char *reason) 29 | { 30 | if (!strncmp(this_->argv[0], "--", 2)) { 31 | fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason); 32 | exit(-1); 33 | } else { 34 | fprintf(stderr, "error: option `%c` %s\n", opt->short_name, reason); 35 | exit(-1); 36 | } 37 | } 38 | 39 | static int 40 | argparse_getvalue(struct argparse *this_, const struct argparse_option *opt, 41 | int flags) 42 | { 43 | const char *s = NULL; 44 | if (!opt->value) 45 | goto skipped; 46 | switch (opt->type) { 47 | case ARGPARSE_OPT_BOOLEAN: 48 | if (flags & OPT_UNSET) { 49 | *(int *)opt->value = *(int *)opt->value - 1; 50 | } else { 51 | *(int *)opt->value = *(int *)opt->value + 1; 52 | } 53 | if (*(int *)opt->value < 0) { 54 | *(int *)opt->value = 0; 55 | } 56 | break; 57 | case ARGPARSE_OPT_BIT: 58 | if (flags & OPT_UNSET) { 59 | *(int *)opt->value &= ~opt->data; 60 | } else { 61 | *(int *)opt->value |= opt->data; 62 | } 63 | break; 64 | case ARGPARSE_OPT_STRING: 65 | if (this_->optvalue) { 66 | *(const char **)opt->value = this_->optvalue; 67 | this_->optvalue = NULL; 68 | } else if (this_->argc > 1) { 69 | this_->argc--; 70 | *(const char **)opt->value = *++this_->argv; 71 | } else { 72 | argparse_error(this_, opt, "requires a value"); 73 | } 74 | break; 75 | case ARGPARSE_OPT_INTEGER: 76 | if (this_->optvalue) { 77 | *(int *)opt->value = strtol(this_->optvalue, (char **)&s, 0); 78 | this_->optvalue = NULL; 79 | } else if (this_->argc > 1) { 80 | this_->argc--; 81 | *(int *)opt->value = strtol(*++this_->argv, (char **)&s, 0); 82 | } else { 83 | argparse_error(this_, opt, "requires a value"); 84 | } 85 | if (*s) 86 | argparse_error(this_, opt, "expects a numerical value"); 87 | break; 88 | default: 89 | assert(0); 90 | } 91 | 92 | skipped: 93 | if (opt->callback) { 94 | return opt->callback(this_, opt); 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | static void 101 | argparse_options_check(const struct argparse_option *options) 102 | { 103 | for (; options->type != ARGPARSE_OPT_END; options++) { 104 | switch (options->type) { 105 | case ARGPARSE_OPT_END: 106 | case ARGPARSE_OPT_BOOLEAN: 107 | case ARGPARSE_OPT_BIT: 108 | case ARGPARSE_OPT_INTEGER: 109 | case ARGPARSE_OPT_STRING: 110 | continue; 111 | default: 112 | fprintf(stderr, "wrong option type: %d", options->type); 113 | break; 114 | } 115 | } 116 | } 117 | 118 | static int 119 | argparse_short_opt(struct argparse *this_, const struct argparse_option *options) 120 | { 121 | for (; options->type != ARGPARSE_OPT_END; options++) { 122 | if (options->short_name == *this_->optvalue) { 123 | this_->optvalue = this_->optvalue[1] ? this_->optvalue + 1 : NULL; 124 | return argparse_getvalue(this_, options, 0); 125 | } 126 | } 127 | return -2; 128 | } 129 | 130 | static int 131 | argparse_long_opt(struct argparse *this_, const struct argparse_option *options) 132 | { 133 | for (; options->type != ARGPARSE_OPT_END; options++) { 134 | const char *rest; 135 | int opt_flags = 0; 136 | if (!options->long_name) 137 | continue; 138 | 139 | rest = prefix_skip(this_->argv[0] + 2, options->long_name); 140 | if (!rest) { 141 | // Negation allowed? 142 | if (options->flags & OPT_NONEG) { 143 | continue; 144 | } 145 | // Only boolean/bit allow negation. 146 | if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != ARGPARSE_OPT_BIT) { 147 | continue; 148 | } 149 | 150 | if (!prefix_cmp(this_->argv[0] + 2, "no-")) { 151 | rest = prefix_skip(this_->argv[0] + 2 + 3, options->long_name); 152 | if (!rest) 153 | continue; 154 | opt_flags |= OPT_UNSET; 155 | } else { 156 | continue; 157 | } 158 | } 159 | if (*rest) { 160 | if (*rest != '=') 161 | continue; 162 | this_->optvalue = rest + 1; 163 | } 164 | return argparse_getvalue(this_, options, opt_flags); 165 | } 166 | return -2; 167 | } 168 | 169 | int 170 | argparse_init(struct argparse *this_, struct argparse_option *options, 171 | const char *usage, int flags) 172 | { 173 | memset(this_, 0, sizeof(*this_)); 174 | this_->options = options; 175 | this_->usage = usage; 176 | this_->flags = flags; 177 | return 0; 178 | } 179 | 180 | int 181 | argparse_parse(struct argparse *this_, int argc, const char **argv) 182 | { 183 | this_->argc = argc - 1; 184 | this_->argv = argv + 1; 185 | this_->out = argv; 186 | 187 | argparse_options_check(this_->options); 188 | 189 | for (; this_->argc; this_->argc--, this_->argv++) { 190 | const char *arg = this_->argv[0]; 191 | if (arg[0] != '-' || !arg[1]) { 192 | if (this_->flags & ARGPARSE_STOP_AT_NON_OPTION) { 193 | goto end; 194 | } 195 | // if it's not option or is a single char '-', copy verbatimly 196 | this_->out[this_->cpidx++] = this_->argv[0]; 197 | continue; 198 | } 199 | // short option 200 | if (arg[1] != '-') { 201 | this_->optvalue = arg + 1; 202 | switch (argparse_short_opt(this_, this_->options)) { 203 | case -1: 204 | break; 205 | case -2: 206 | goto unknown; 207 | } 208 | while (this_->optvalue) { 209 | switch (argparse_short_opt(this_, this_->options)) { 210 | case -1: 211 | break; 212 | case -2: 213 | goto unknown; 214 | } 215 | } 216 | continue; 217 | } 218 | // if '--' presents 219 | if (!arg[2]) { 220 | this_->argc--; 221 | this_->argv++; 222 | break; 223 | } 224 | // long option 225 | switch (argparse_long_opt(this_, this_->options)) { 226 | case -1: 227 | break; 228 | case -2: 229 | goto unknown; 230 | } 231 | continue; 232 | 233 | unknown: 234 | fprintf(stderr, "error: unknown option `%s`\n", this_->argv[0]); 235 | argparse_usage(this_); 236 | exit(0); 237 | } 238 | 239 | end: 240 | memmove(this_->out + this_->cpidx, this_->argv, 241 | this_->argc * sizeof(*this_->out)); 242 | this_->out[this_->cpidx + this_->argc] = NULL; 243 | 244 | return this_->cpidx + this_->argc; 245 | } 246 | 247 | void 248 | argparse_usage(struct argparse *this_) 249 | { 250 | fprintf(stdout, "Usage: %s\n", this_->usage); 251 | fputc('\n', stdout); 252 | 253 | const struct argparse_option *options; 254 | 255 | // figure out best width 256 | size_t usage_opts_width = 0; 257 | size_t len; 258 | options = this_->options; 259 | for (; options->type != ARGPARSE_OPT_END; options++) { 260 | len = 0; 261 | if ((options)->short_name) { 262 | len += 2; 263 | } 264 | if ((options)->short_name && (options)->long_name) { 265 | len += 2; // separator ", " 266 | } 267 | if ((options)->long_name) { 268 | len += strlen((options)->long_name) + 2; 269 | } 270 | if (options->type == ARGPARSE_OPT_INTEGER) { 271 | len += strlen("="); 272 | } else if (options->type == ARGPARSE_OPT_STRING) { 273 | len += strlen("="); 274 | } 275 | len = ceil((float)len / 4) * 4; 276 | if (usage_opts_width < len) { 277 | usage_opts_width = len; 278 | } 279 | } 280 | usage_opts_width += 4; // 4 spaces prefix 281 | 282 | options = this_->options; 283 | for (; options->type != ARGPARSE_OPT_END; options++) { 284 | size_t pos; 285 | int pad; 286 | pos = fprintf(stdout, " "); 287 | if (options->short_name) { 288 | pos += fprintf(stdout, "-%c", options->short_name); 289 | } 290 | if (options->long_name && options->short_name) { 291 | pos += fprintf(stdout, ", "); 292 | } 293 | if (options->long_name) { 294 | pos += fprintf(stdout, "--%s", options->long_name); 295 | } 296 | if (options->type == ARGPARSE_OPT_INTEGER) { 297 | pos += fprintf(stdout, "="); 298 | } else if (options->type == ARGPARSE_OPT_STRING) { 299 | pos += fprintf(stdout, "="); 300 | } 301 | if (pos <= usage_opts_width) { 302 | pad = usage_opts_width - pos; 303 | } else { 304 | fputc('\n', stdout); 305 | pad = usage_opts_width; 306 | } 307 | fprintf(stdout, "%*s%s\n", pad + 2, "", options->help); 308 | } 309 | } 310 | 311 | int 312 | argparse_help_cb(struct argparse *this_, const struct argparse_option *option) 313 | { 314 | (void)option; 315 | argparse_usage(this_); 316 | exit(0); 317 | return 0; 318 | } 319 | 320 | #if defined(__cplusplus) 321 | } 322 | #endif 323 | -------------------------------------------------------------------------------- /server/src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | 4 | #include 5 | #include "server.h" 6 | 7 | class CConfig 8 | { 9 | public: 10 | bool m_Verbose; 11 | char m_aConfigFile[1024]; 12 | char m_aWebDir[1024]; 13 | char m_aTemplateFile[1024]; 14 | char m_aJSONFile[1024]; 15 | char m_aBindAddr[256]; 16 | int m_Port; 17 | 18 | CConfig(); 19 | }; 20 | 21 | class CMain 22 | { 23 | CConfig m_Config; 24 | CServer m_Server; 25 | 26 | struct CClient 27 | { 28 | bool m_Active; 29 | bool m_Disabled; 30 | bool m_Connected; 31 | int m_ClientNetID; 32 | int m_ClientNetType; 33 | char m_aUsername[128]; 34 | char m_aName[128]; 35 | char m_aType[128]; 36 | char m_aHost[128]; 37 | char m_aLocation[128]; 38 | char m_aPassword[128]; 39 | int m_aMonthStart; //track month network traffic. by: https://cpp.la 40 | 41 | // add several extra data 42 | char m_aExtra1[128]; 43 | char m_aExtra2[128]; 44 | char m_aExtra3[128]; 45 | 46 | int64_t m_LastNetworkIN; 47 | int64_t m_LastNetworkOUT; 48 | int64 m_TimeConnected; 49 | int64 m_LastUpdate; 50 | 51 | struct CStats 52 | { 53 | bool m_Online4; 54 | bool m_Online6; 55 | // bool m_IpStatus, delete ip_status check, Duplicate packet loss rate detection 56 | // mh361 or mh370, mourn mh370, 2014-03-08 01:20 lost from all over the world. by:https://cpp.la 57 | int64_t m_Uptime; 58 | double m_Load_1; 59 | double m_Load_5; 60 | double m_Load_15; 61 | double m_ping_10010; 62 | double m_ping_189; 63 | double m_ping_10086; 64 | int64_t m_time_10010; 65 | int64_t m_time_189; 66 | int64_t m_time_10086; 67 | int64_t m_NetworkRx; 68 | int64_t m_NetworkTx; 69 | int64_t m_NetworkIN; 70 | int64_t m_NetworkOUT; 71 | int64_t m_MemTotal; 72 | int64_t m_MemUsed; 73 | int64_t m_SwapTotal; 74 | int64_t m_SwapUsed; 75 | int64_t m_HDDTotal; 76 | int64_t m_HDDUsed; 77 | int64_t m_tcpCount; 78 | int64_t m_udpCount; 79 | int64_t m_processCount; 80 | int64_t m_threadCount; 81 | double m_CPU; 82 | char m_aCustom[512]; 83 | // Options 84 | bool m_Pong; 85 | } m_Stats; 86 | } m_aClients[NET_MAX_CLIENTS]; 87 | 88 | struct CJSONUpdateThreadData 89 | { 90 | CClient *pClients; 91 | CConfig *pConfig; 92 | volatile short m_ReloadRequired; 93 | } m_JSONUpdateThreadData; 94 | 95 | static void JSONUpdateThread(void *pUser); 96 | public: 97 | CMain(CConfig Config); 98 | 99 | void OnNewClient(int ClienNettID, int ClientID); 100 | void OnDelClient(int ClientNetID); 101 | int HandleMessage(int ClientNetID, char *pMessage); 102 | int ReadConfig(); 103 | int Run(); 104 | 105 | CClient *Client(int ClientID) { return &m_aClients[ClientID]; } 106 | CClient *ClientNet(int ClientNetID); 107 | const CConfig *Config() const { return &m_Config; } 108 | int ClientNetToClient(int ClientNetID); 109 | }; 110 | 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /server/src/netban.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "netban.h" 3 | 4 | bool CNetBan::StrAllnum(const char *pStr) 5 | { 6 | while(*pStr) 7 | { 8 | if(!(*pStr >= '0' && *pStr <= '9')) 9 | return false; 10 | pStr++; 11 | } 12 | return true; 13 | } 14 | 15 | 16 | CNetBan::CNetHash::CNetHash(const NETADDR *pAddr) 17 | { 18 | if(pAddr->type==NETTYPE_IPV4) 19 | m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF; 20 | else 21 | m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3]+pAddr->ip[4]+pAddr->ip[5]+pAddr->ip[6]+pAddr->ip[7]+ 22 | pAddr->ip[8]+pAddr->ip[9]+pAddr->ip[10]+pAddr->ip[11]+pAddr->ip[12]+pAddr->ip[13]+pAddr->ip[14]+pAddr->ip[15])&0xFF; 23 | m_HashIndex = 0; 24 | } 25 | 26 | CNetBan::CNetHash::CNetHash(const CNetRange *pRange) 27 | { 28 | m_Hash = 0; 29 | m_HashIndex = 0; 30 | for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i) 31 | { 32 | m_Hash += pRange->m_LB.ip[i]; 33 | ++m_HashIndex; 34 | } 35 | m_Hash &= 0xFF; 36 | } 37 | 38 | int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]) 39 | { 40 | int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16; 41 | aHash[0].m_Hash = 0; 42 | aHash[0].m_HashIndex = 0; 43 | for(int i = 1, Sum = 0; i <= Length; ++i) 44 | { 45 | Sum += pAddr->ip[i-1]; 46 | aHash[i].m_Hash = Sum&0xFF; 47 | aHash[i].m_HashIndex = i%Length; 48 | } 49 | return Length; 50 | } 51 | 52 | 53 | template 54 | typename CNetBan::CBan *CNetBan::CBanPool::Add(const T *pData, const CBanInfo *pInfo, const CNetHash *pNetHash) 55 | { 56 | if(!m_pFirstFree) 57 | return 0; 58 | 59 | // create new ban 60 | CBan *pBan = m_pFirstFree; 61 | pBan->m_Data = *pData; 62 | pBan->m_Info = *pInfo; 63 | pBan->m_NetHash = *pNetHash; 64 | if(pBan->m_pNext) 65 | pBan->m_pNext->m_pPrev = pBan->m_pPrev; 66 | if(pBan->m_pPrev) 67 | pBan->m_pPrev->m_pNext = pBan->m_pNext; 68 | else 69 | m_pFirstFree = pBan->m_pNext; 70 | 71 | // add it to the hash list 72 | if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]) 73 | m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan; 74 | pBan->m_pHashPrev = 0; 75 | pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; 76 | m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan; 77 | 78 | // insert it into the used list 79 | if(m_pFirstUsed) 80 | { 81 | for(CBan *p = m_pFirstUsed; ; p = p->m_pNext) 82 | { 83 | if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires)) 84 | { 85 | // insert before 86 | pBan->m_pNext = p; 87 | pBan->m_pPrev = p->m_pPrev; 88 | if(p->m_pPrev) 89 | p->m_pPrev->m_pNext = pBan; 90 | else 91 | m_pFirstUsed = pBan; 92 | p->m_pPrev = pBan; 93 | break; 94 | } 95 | 96 | if(!p->m_pNext) 97 | { 98 | // last entry 99 | p->m_pNext = pBan; 100 | pBan->m_pPrev = p; 101 | pBan->m_pNext = 0; 102 | break; 103 | } 104 | } 105 | } 106 | else 107 | { 108 | m_pFirstUsed = pBan; 109 | pBan->m_pNext = pBan->m_pPrev = 0; 110 | } 111 | 112 | // update ban count 113 | ++m_CountUsed; 114 | 115 | return pBan; 116 | } 117 | 118 | template 119 | int CNetBan::CBanPool::Remove(CBan *pBan) 120 | { 121 | if(pBan == 0) 122 | return -1; 123 | 124 | // remove from hash list 125 | if(pBan->m_pHashNext) 126 | pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev; 127 | if(pBan->m_pHashPrev) 128 | pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext; 129 | else 130 | m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext; 131 | pBan->m_pHashNext = pBan->m_pHashPrev = 0; 132 | 133 | // remove from used list 134 | if(pBan->m_pNext) 135 | pBan->m_pNext->m_pPrev = pBan->m_pPrev; 136 | if(pBan->m_pPrev) 137 | pBan->m_pPrev->m_pNext = pBan->m_pNext; 138 | else 139 | m_pFirstUsed = pBan->m_pNext; 140 | 141 | // add to recycle list 142 | if(m_pFirstFree) 143 | m_pFirstFree->m_pPrev = pBan; 144 | pBan->m_pPrev = 0; 145 | pBan->m_pNext = m_pFirstFree; 146 | m_pFirstFree = pBan; 147 | 148 | // update ban count 149 | --m_CountUsed; 150 | 151 | return 0; 152 | } 153 | 154 | template 155 | void CNetBan::CBanPool::Update(CBan *pBan, const CBanInfo *pInfo) 156 | { 157 | pBan->m_Info = *pInfo; 158 | 159 | // remove from used list 160 | if(pBan->m_pNext) 161 | pBan->m_pNext->m_pPrev = pBan->m_pPrev; 162 | if(pBan->m_pPrev) 163 | pBan->m_pPrev->m_pNext = pBan->m_pNext; 164 | else 165 | m_pFirstUsed = pBan->m_pNext; 166 | 167 | // insert it into the used list 168 | if(m_pFirstUsed) 169 | { 170 | for(CBan *p = m_pFirstUsed; ; p = p->m_pNext) 171 | { 172 | if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires)) 173 | { 174 | // insert before 175 | pBan->m_pNext = p; 176 | pBan->m_pPrev = p->m_pPrev; 177 | if(p->m_pPrev) 178 | p->m_pPrev->m_pNext = pBan; 179 | else 180 | m_pFirstUsed = pBan; 181 | p->m_pPrev = pBan; 182 | break; 183 | } 184 | 185 | if(!p->m_pNext) 186 | { 187 | // last entry 188 | p->m_pNext = pBan; 189 | pBan->m_pPrev = p; 190 | pBan->m_pNext = 0; 191 | break; 192 | } 193 | } 194 | } 195 | else 196 | { 197 | m_pFirstUsed = pBan; 198 | pBan->m_pNext = pBan->m_pPrev = 0; 199 | } 200 | } 201 | 202 | template 203 | void CNetBan::CBanPool::Reset() 204 | { 205 | mem_zero(m_paaHashList, sizeof(m_paaHashList)); 206 | mem_zero(m_aBans, sizeof(m_aBans)); 207 | m_pFirstUsed = 0; 208 | m_CountUsed = 0; 209 | 210 | for(int i = 1; i < MAX_BANS-1; ++i) 211 | { 212 | m_aBans[i].m_pNext = &m_aBans[i+1]; 213 | m_aBans[i].m_pPrev = &m_aBans[i-1]; 214 | } 215 | 216 | m_aBans[0].m_pNext = &m_aBans[1]; 217 | m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2]; 218 | m_pFirstFree = &m_aBans[0]; 219 | } 220 | 221 | template 222 | typename CNetBan::CBan *CNetBan::CBanPool::Get(int Index) const 223 | { 224 | if(Index < 0 || Index >= Num()) 225 | return 0; 226 | 227 | for(CNetBan::CBan *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index) 228 | { 229 | if(Index == 0) 230 | return pBan; 231 | } 232 | 233 | return 0; 234 | } 235 | 236 | 237 | template 238 | void CNetBan::MakeBanInfo(const CBan *pBan, char *pBuf, unsigned BuffSize, int Type) const 239 | { 240 | if(pBan == 0 || pBuf == 0) 241 | { 242 | if(BuffSize > 0) 243 | pBuf[0] = 0; 244 | return; 245 | } 246 | 247 | // build type based part 248 | char aBuf[256]; 249 | if(Type == MSGTYPE_PLAYER) 250 | str_copy(aBuf, "You have been banned", sizeof(aBuf)); 251 | else 252 | { 253 | char aTemp[256]; 254 | switch(Type) 255 | { 256 | case MSGTYPE_LIST: 257 | str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break; 258 | case MSGTYPE_BANADD: 259 | str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break; 260 | case MSGTYPE_BANREM: 261 | str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break; 262 | default: 263 | aBuf[0] = 0; 264 | } 265 | } 266 | 267 | // add info part 268 | if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER) 269 | { 270 | int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60; 271 | if(Mins <= 1) 272 | str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason); 273 | else 274 | str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason); 275 | } 276 | else 277 | str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason); 278 | } 279 | 280 | template 281 | int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason) 282 | { 283 | // do not ban localhost 284 | if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6)) 285 | { 286 | dbg_msg("net_ban", "ban failed (localhost)"); 287 | return -1; 288 | } 289 | 290 | int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER; 291 | 292 | // set up info 293 | CBanInfo Info = {0}; 294 | Info.m_Expires = Stamp; 295 | str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason)); 296 | 297 | // check if it already exists 298 | CNetHash NetHash(pData); 299 | CBan *pBan = pBanPool->Find(pData, &NetHash); 300 | if(pBan) 301 | { 302 | // adjust the ban 303 | pBanPool->Update(pBan, &Info); 304 | char aBuf[128]; 305 | MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST); 306 | dbg_msg("net_ban", aBuf); 307 | return 1; 308 | } 309 | 310 | // add ban and print result 311 | pBan = pBanPool->Add(pData, &Info, &NetHash); 312 | if(pBan) 313 | { 314 | char aBuf[128]; 315 | MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD); 316 | dbg_msg("net_ban", aBuf); 317 | return 0; 318 | } 319 | else 320 | dbg_msg("net_ban", "ban failed (full banlist)"); 321 | return -1; 322 | } 323 | 324 | template 325 | int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData) 326 | { 327 | CNetHash NetHash(pData); 328 | CBan *pBan = pBanPool->Find(pData, &NetHash); 329 | if(pBan) 330 | { 331 | char aBuf[256]; 332 | MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM); 333 | pBanPool->Remove(pBan); 334 | dbg_msg("net_ban", aBuf); 335 | return 0; 336 | } 337 | else 338 | dbg_msg("net_ban", "unban failed (invalid entry)"); 339 | return -1; 340 | } 341 | 342 | void CNetBan::Init() 343 | { 344 | m_BanAddrPool.Reset(); 345 | m_BanRangePool.Reset(); 346 | 347 | net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4); 348 | net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6); 349 | } 350 | 351 | void CNetBan::Update() 352 | { 353 | int Now = time_timestamp(); 354 | 355 | // remove expired bans 356 | char aBuf[256], aNetStr[256]; 357 | while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now) 358 | { 359 | str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr))); 360 | dbg_msg("net_ban", aBuf); 361 | m_BanAddrPool.Remove(m_BanAddrPool.First()); 362 | } 363 | while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now) 364 | { 365 | str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr))); 366 | dbg_msg("net_ban", aBuf); 367 | m_BanRangePool.Remove(m_BanRangePool.First()); 368 | } 369 | } 370 | 371 | int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason) 372 | { 373 | return Ban(&m_BanAddrPool, pAddr, Seconds, pReason); 374 | } 375 | 376 | int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason) 377 | { 378 | if(pRange->IsValid()) 379 | return Ban(&m_BanRangePool, pRange, Seconds, pReason); 380 | 381 | dbg_msg("net_ban", "ban failed (invalid range)"); 382 | return -1; 383 | } 384 | 385 | int CNetBan::UnbanByAddr(const NETADDR *pAddr) 386 | { 387 | return Unban(&m_BanAddrPool, pAddr); 388 | } 389 | 390 | int CNetBan::UnbanByRange(const CNetRange *pRange) 391 | { 392 | if(pRange->IsValid()) 393 | return Unban(&m_BanRangePool, pRange); 394 | 395 | dbg_msg("net_ban", "ban failed (invalid range)"); 396 | return -1; 397 | } 398 | 399 | int CNetBan::UnbanByIndex(int Index) 400 | { 401 | int Result; 402 | char aBuf[256]; 403 | CBanAddr *pBan = m_BanAddrPool.Get(Index); 404 | if(pBan) 405 | { 406 | NetToString(&pBan->m_Data, aBuf, sizeof(aBuf)); 407 | Result = m_BanAddrPool.Remove(pBan); 408 | } 409 | else 410 | { 411 | CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num()); 412 | if(pBan) 413 | { 414 | NetToString(&pBan->m_Data, aBuf, sizeof(aBuf)); 415 | Result = m_BanRangePool.Remove(pBan); 416 | } 417 | else 418 | { 419 | dbg_msg("net_ban", "unban failed (invalid index)"); 420 | return -1; 421 | } 422 | } 423 | 424 | char aMsg[256]; 425 | str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf); 426 | dbg_msg("net_ban", aMsg); 427 | return Result; 428 | } 429 | 430 | void CNetBan::UnbanAll() 431 | { 432 | m_BanAddrPool.Reset(); 433 | m_BanRangePool.Reset(); 434 | } 435 | 436 | bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const 437 | { 438 | CNetHash aHash[17]; 439 | int Length = CNetHash::MakeHashArray(pAddr, aHash); 440 | 441 | // check ban adresses 442 | CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]); 443 | if(pBan) 444 | { 445 | MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER); 446 | return true; 447 | } 448 | 449 | // check ban ranges 450 | for(int i = Length-1; i >= 0; --i) 451 | { 452 | for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext) 453 | { 454 | if(NetMatch(&pBan->m_Data, pAddr, i, Length)) 455 | { 456 | MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER); 457 | return true; 458 | } 459 | } 460 | } 461 | 462 | return false; 463 | } 464 | -------------------------------------------------------------------------------- /server/src/netban.h: -------------------------------------------------------------------------------- 1 | #ifndef NETBAN_H 2 | #define NETBAN_H 3 | 4 | #include 5 | 6 | inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2) 7 | { 8 | return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20); 9 | } 10 | 11 | class CNetRange 12 | { 13 | public: 14 | NETADDR m_LB; 15 | NETADDR m_UB; 16 | 17 | bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; } 18 | }; 19 | 20 | inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2) 21 | { 22 | return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB); 23 | } 24 | 25 | 26 | class CNetBan 27 | { 28 | protected: 29 | bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const 30 | { 31 | return NetComp(pAddr1, pAddr2) == 0; 32 | } 33 | 34 | bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const 35 | { 36 | return pRange->m_LB.type == pAddr->type && (Start == 0 || mem_comp(&pRange->m_LB.ip[0], &pAddr->ip[0], Start) == 0) && 37 | mem_comp(&pRange->m_LB.ip[Start], &pAddr->ip[Start], Length-Start) <= 0 && mem_comp(&pRange->m_UB.ip[Start], &pAddr->ip[Start], Length-Start) >= 0; 38 | } 39 | 40 | bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const 41 | { 42 | return NetMatch(pRange, pAddr, 0, pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16); 43 | } 44 | 45 | const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const 46 | { 47 | char aAddrStr[NETADDR_MAXSTRSIZE]; 48 | net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false); 49 | str_format(pBuffer, BufferSize, "'%s'", aAddrStr); 50 | return pBuffer; 51 | } 52 | 53 | const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const 54 | { 55 | char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE]; 56 | net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false); 57 | net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false); 58 | str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2); 59 | return pBuffer; 60 | } 61 | 62 | // todo: move? 63 | static bool StrAllnum(const char *pStr); 64 | 65 | class CNetHash 66 | { 67 | public: 68 | int m_Hash; 69 | int m_HashIndex; // matching parts for ranges, 0 for addr 70 | 71 | CNetHash() {} 72 | CNetHash(const NETADDR *pAddr); 73 | CNetHash(const CNetRange *pRange); 74 | 75 | static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]); 76 | }; 77 | 78 | struct CBanInfo 79 | { 80 | enum 81 | { 82 | EXPIRES_NEVER=-1, 83 | REASON_LENGTH=64, 84 | }; 85 | int m_Expires; 86 | char m_aReason[REASON_LENGTH]; 87 | }; 88 | 89 | template struct CBan 90 | { 91 | T m_Data; 92 | CBanInfo m_Info; 93 | CNetHash m_NetHash; 94 | 95 | // hash list 96 | CBan *m_pHashNext; 97 | CBan *m_pHashPrev; 98 | 99 | // used or free list 100 | CBan *m_pNext; 101 | CBan *m_pPrev; 102 | }; 103 | 104 | template class CBanPool 105 | { 106 | public: 107 | typedef T CDataType; 108 | 109 | CBan *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash); 110 | int Remove(CBan *pBan); 111 | void Update(CBan *pBan, const CBanInfo *pInfo); 112 | void Reset(); 113 | 114 | int Num() const { return m_CountUsed; } 115 | bool IsFull() const { return m_CountUsed == MAX_BANS; } 116 | 117 | CBan *First() const { return m_pFirstUsed; } 118 | CBan *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; } 119 | CBan *Find(const CDataType *pData, const CNetHash *pNetHash) const 120 | { 121 | for(CBan *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext) 122 | { 123 | if(NetComp(&pBan->m_Data, pData) == 0) 124 | return pBan; 125 | } 126 | 127 | return 0; 128 | } 129 | CBan *Get(int Index) const; 130 | 131 | private: 132 | enum 133 | { 134 | MAX_BANS=1024, 135 | }; 136 | 137 | CBan *m_paaHashList[HashCount][256]; 138 | CBan m_aBans[MAX_BANS]; 139 | CBan *m_pFirstFree; 140 | CBan *m_pFirstUsed; 141 | int m_CountUsed; 142 | }; 143 | 144 | typedef CBanPool CBanAddrPool; 145 | typedef CBanPool CBanRangePool; 146 | typedef CBan CBanAddr; 147 | typedef CBan CBanRange; 148 | 149 | template void MakeBanInfo(const CBan *pBan, char *pBuf, unsigned BuffSize, int Type) const; 150 | template int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason); 151 | template int Unban(T *pBanPool, const typename T::CDataType *pData); 152 | 153 | CBanAddrPool m_BanAddrPool; 154 | CBanRangePool m_BanRangePool; 155 | NETADDR m_LocalhostIPV4, m_LocalhostIPV6; 156 | 157 | public: 158 | enum 159 | { 160 | MSGTYPE_PLAYER=0, 161 | MSGTYPE_LIST, 162 | MSGTYPE_BANADD, 163 | MSGTYPE_BANREM, 164 | }; 165 | 166 | virtual ~CNetBan() {} 167 | void Init(); 168 | void Update(); 169 | 170 | virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason); 171 | virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason); 172 | int UnbanByAddr(const NETADDR *pAddr); 173 | int UnbanByRange(const CNetRange *pRange); 174 | int UnbanByIndex(int Index); 175 | void UnbanAll(); 176 | bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const; 177 | }; 178 | 179 | #endif 180 | -------------------------------------------------------------------------------- /server/src/network.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "netban.h" 3 | #include "network.h" 4 | 5 | bool CNetwork::Open(NETADDR BindAddr, CNetBan *pNetBan) 6 | { 7 | // zero out the whole structure 8 | mem_zero(this, sizeof(*this)); 9 | m_Socket.type = NETTYPE_INVALID; 10 | m_Socket.ipv4sock = -1; 11 | m_Socket.ipv6sock = -1; 12 | m_pNetBan = pNetBan; 13 | 14 | // open socket 15 | m_Socket = net_tcp_create(BindAddr); 16 | if(!m_Socket.type) 17 | return false; 18 | if(net_tcp_listen(m_Socket, NET_MAX_CLIENTS)) 19 | return false; 20 | net_set_non_blocking(m_Socket); 21 | 22 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 23 | m_aSlots[i].m_Connection.Reset(); 24 | 25 | return true; 26 | } 27 | 28 | void CNetwork::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser) 29 | { 30 | m_pfnNewClient = pfnNewClient; 31 | m_pfnDelClient = pfnDelClient; 32 | m_UserPtr = pUser; 33 | } 34 | 35 | int CNetwork::Close() 36 | { 37 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 38 | m_aSlots[i].m_Connection.Disconnect("Closing connection."); 39 | 40 | net_tcp_close(m_Socket); 41 | 42 | return 0; 43 | } 44 | 45 | int CNetwork::Drop(int ClientID, const char *pReason) 46 | { 47 | if(m_pfnDelClient) 48 | m_pfnDelClient(ClientID, pReason, m_UserPtr); 49 | 50 | m_aSlots[ClientID].m_Connection.Disconnect(pReason); 51 | 52 | return 0; 53 | } 54 | 55 | int CNetwork::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr) 56 | { 57 | char aError[256] = { 0 }; 58 | int FreeSlot = -1; 59 | 60 | // look for free slot or multiple client 61 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 62 | { 63 | if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) 64 | FreeSlot = i; 65 | if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE) 66 | { 67 | if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0) 68 | { 69 | str_copy(aError, "Only one client per IP allowed.", sizeof(aError)); 70 | break; 71 | } 72 | } 73 | } 74 | 75 | // accept client 76 | if(!aError[0] && FreeSlot != -1) 77 | { 78 | m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr); 79 | if(m_pfnNewClient) 80 | m_pfnNewClient(FreeSlot, m_UserPtr); 81 | return 0; 82 | } 83 | 84 | // reject client 85 | if(!aError[0]) 86 | str_copy(aError, "No free slot available.", sizeof(aError)); 87 | 88 | net_tcp_send(Socket, aError, str_length(aError)); 89 | net_tcp_close(Socket); 90 | 91 | return -1; 92 | } 93 | 94 | int CNetwork::Update() 95 | { 96 | NETSOCKET Socket; 97 | NETADDR Addr; 98 | 99 | if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0) 100 | { 101 | // check if we should just drop the packet 102 | char aBuf[128]; 103 | if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf))) 104 | { 105 | // banned, reply with a message and drop 106 | net_tcp_send(Socket, aBuf, str_length(aBuf)); 107 | net_tcp_close(Socket); 108 | } 109 | else 110 | AcceptClient(Socket, &Addr); 111 | } 112 | 113 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 114 | { 115 | if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE) 116 | m_aSlots[i].m_Connection.Update(); 117 | if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR) 118 | Drop(i, m_aSlots[i].m_Connection.ErrorString()); 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | int CNetwork::Recv(char *pLine, int MaxLength, int *pClientID) 125 | { 126 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 127 | { 128 | if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength)) 129 | { 130 | if(pClientID) 131 | *pClientID = i; 132 | return 1; 133 | } 134 | } 135 | return 0; 136 | } 137 | 138 | int CNetwork::Send(int ClientID, const char *pLine) 139 | { 140 | if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE) 141 | return m_aSlots[ClientID].m_Connection.Send(pLine); 142 | else 143 | return -1; 144 | } 145 | -------------------------------------------------------------------------------- /server/src/network.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_H 2 | #define NETWORK_H 3 | 4 | enum 5 | { 6 | NET_CONNSTATE_OFFLINE=0, 7 | NET_CONNSTATE_CONNECT=1, 8 | NET_CONNSTATE_PENDING=2, 9 | NET_CONNSTATE_ONLINE=3, 10 | NET_CONNSTATE_ERROR=4, 11 | 12 | NET_MAX_PACKETSIZE = 1400, 13 | NET_MAX_CLIENTS = 256 14 | }; 15 | 16 | typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser); 17 | typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser); 18 | 19 | class CNetworkClient 20 | { 21 | private: 22 | int m_State; 23 | 24 | NETADDR m_PeerAddr; 25 | NETSOCKET m_Socket; 26 | 27 | char m_aBuffer[NET_MAX_PACKETSIZE]; 28 | int m_BufferOffset; 29 | 30 | char m_aErrorString[256]; 31 | 32 | bool m_LineEndingDetected; 33 | char m_aLineEnding[3]; 34 | 35 | public: 36 | void Init(NETSOCKET Socket, const NETADDR *pAddr); 37 | void Disconnect(const char *pReason); 38 | 39 | int State() const { return m_State; } 40 | const NETADDR *PeerAddress() const { return &m_PeerAddr; } 41 | const char *ErrorString() const { return m_aErrorString; } 42 | 43 | void Reset(); 44 | int Update(); 45 | int Send(const char *pLine); 46 | int Recv(char *pLine, int MaxLength); 47 | }; 48 | 49 | class CNetwork 50 | { 51 | private: 52 | struct CSlot 53 | { 54 | CNetworkClient m_Connection; 55 | }; 56 | 57 | NETSOCKET m_Socket; 58 | class CNetBan *m_pNetBan; 59 | CSlot m_aSlots[NET_MAX_CLIENTS]; 60 | 61 | NETFUNC_NEWCLIENT m_pfnNewClient; 62 | NETFUNC_DELCLIENT m_pfnDelClient; 63 | void *m_UserPtr; 64 | 65 | public: 66 | void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); 67 | 68 | // 69 | bool Open(NETADDR BindAddr, CNetBan *pNetBan); 70 | int Close(); 71 | 72 | // 73 | int Recv(char *pLine, int MaxLength, int *pClientID = 0); 74 | int Send(int ClientID, const char *pLine); 75 | int Update(); 76 | 77 | // 78 | int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr); 79 | int Drop(int ClientID, const char *pReason); 80 | 81 | // status requests 82 | const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } 83 | const NETSOCKET *Socket() const { return &m_Socket; } 84 | class CNetBan *NetBan() const { return m_pNetBan; } 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /server/src/network_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "network.h" 3 | 4 | void CNetworkClient::Reset() 5 | { 6 | m_State = NET_CONNSTATE_OFFLINE; 7 | mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); 8 | m_aErrorString[0] = 0; 9 | 10 | m_Socket.type = NETTYPE_INVALID; 11 | m_Socket.ipv4sock = -1; 12 | m_Socket.ipv6sock = -1; 13 | m_aBuffer[0] = 0; 14 | m_BufferOffset = 0; 15 | 16 | m_LineEndingDetected = false; 17 | #if defined(CONF_FAMILY_WINDOWS) 18 | m_aLineEnding[0] = '\r'; 19 | m_aLineEnding[1] = '\n'; 20 | m_aLineEnding[2] = 0; 21 | #else 22 | m_aLineEnding[0] = '\n'; 23 | m_aLineEnding[1] = 0; 24 | m_aLineEnding[2] = 0; 25 | #endif 26 | } 27 | 28 | void CNetworkClient::Init(NETSOCKET Socket, const NETADDR *pAddr) 29 | { 30 | Reset(); 31 | 32 | m_Socket = Socket; 33 | net_set_non_blocking(m_Socket); 34 | 35 | m_PeerAddr = *pAddr; 36 | m_State = NET_CONNSTATE_ONLINE; 37 | } 38 | 39 | void CNetworkClient::Disconnect(const char *pReason) 40 | { 41 | if(State() == NET_CONNSTATE_OFFLINE) 42 | return; 43 | 44 | if(pReason && pReason[0]) 45 | Send(pReason); 46 | 47 | net_tcp_close(m_Socket); 48 | 49 | Reset(); 50 | } 51 | 52 | int CNetworkClient::Update() 53 | { 54 | if(State() == NET_CONNSTATE_ONLINE) 55 | { 56 | if((int)(sizeof(m_aBuffer)) <= m_BufferOffset) 57 | { 58 | m_State = NET_CONNSTATE_ERROR; 59 | str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString)); 60 | return -1; 61 | } 62 | 63 | int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset); 64 | 65 | if(Bytes > 0) 66 | { 67 | m_BufferOffset += Bytes; 68 | } 69 | else if(Bytes < 0) 70 | { 71 | if(net_would_block()) // no data received 72 | return 0; 73 | 74 | m_State = NET_CONNSTATE_ERROR; // error 75 | str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString)); 76 | return -1; 77 | } 78 | else 79 | { 80 | m_State = NET_CONNSTATE_ERROR; 81 | str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString)); 82 | return -1; 83 | } 84 | } 85 | 86 | return 0; 87 | } 88 | 89 | int CNetworkClient::Recv(char *pLine, int MaxLength) 90 | { 91 | if(State() == NET_CONNSTATE_ONLINE) 92 | { 93 | if(m_BufferOffset) 94 | { 95 | // find message start 96 | int StartOffset = 0; 97 | while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n') 98 | { 99 | // detect clients line ending format 100 | if(!m_LineEndingDetected) 101 | { 102 | m_aLineEnding[0] = m_aBuffer[StartOffset]; 103 | if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') && 104 | m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1]) 105 | m_aLineEnding[1] = m_aBuffer[StartOffset+1]; 106 | m_LineEndingDetected = true; 107 | } 108 | 109 | if(++StartOffset >= m_BufferOffset) 110 | { 111 | m_BufferOffset = 0; 112 | return 0; 113 | } 114 | } 115 | 116 | // find message end 117 | int EndOffset = StartOffset; 118 | while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n') 119 | { 120 | if(++EndOffset >= m_BufferOffset) 121 | { 122 | if(StartOffset > 0) 123 | { 124 | mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); 125 | m_BufferOffset -= StartOffset; 126 | } 127 | return 0; 128 | } 129 | } 130 | 131 | // extract message and update buffer 132 | if(MaxLength-1 < EndOffset-StartOffset) 133 | { 134 | if(StartOffset > 0) 135 | { 136 | mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); 137 | m_BufferOffset -= StartOffset; 138 | } 139 | return 0; 140 | } 141 | mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset); 142 | pLine[EndOffset-StartOffset] = 0; 143 | str_sanitize_cc(pLine); 144 | mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset); 145 | m_BufferOffset -= EndOffset; 146 | return 1; 147 | } 148 | } 149 | return 0; 150 | } 151 | 152 | int CNetworkClient::Send(const char *pLine) 153 | { 154 | if(State() != NET_CONNSTATE_ONLINE) 155 | return -1; 156 | 157 | char aBuf[1024]; 158 | str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2); 159 | int Length = str_length(aBuf); 160 | aBuf[Length] = m_aLineEnding[0]; 161 | aBuf[Length+1] = m_aLineEnding[1]; 162 | aBuf[Length+2] = m_aLineEnding[2]; 163 | Length += 3; 164 | const char *pData = aBuf; 165 | 166 | while(1) 167 | { 168 | int Send = net_tcp_send(m_Socket, pData, Length); 169 | if(Send < 0) 170 | { 171 | m_State = NET_CONNSTATE_ERROR; 172 | str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString)); 173 | return -1; 174 | } 175 | 176 | if(Send >= Length) 177 | break; 178 | 179 | pData += Send; 180 | Length -= Send; 181 | } 182 | 183 | return 0; 184 | } 185 | -------------------------------------------------------------------------------- /server/src/server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "netban.h" 3 | #include "network.h" 4 | #include "main.h" 5 | #include "server.h" 6 | 7 | int CServer::NewClientCallback(int ClientID, void *pUser) 8 | { 9 | CServer *pThis = (CServer *)pUser; 10 | 11 | char aAddrStr[NETADDR_MAXSTRSIZE]; 12 | net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); 13 | if(pThis->Main()->Config()->m_Verbose) 14 | dbg_msg("server", "Connection accepted. ncid=%d addr=%s'", ClientID, aAddrStr); 15 | 16 | pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED; 17 | pThis->m_aClients[ClientID].m_TimeConnected = time_get(); 18 | pThis->m_Network.Send(ClientID, "Authentication required:"); 19 | 20 | return 0; 21 | } 22 | 23 | int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) 24 | { 25 | CServer *pThis = (CServer *)pUser; 26 | 27 | char aAddrStr[NETADDR_MAXSTRSIZE]; 28 | net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); 29 | if(pThis->Main()->Config()->m_Verbose) 30 | dbg_msg("server", "Client dropped. ncid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); 31 | 32 | if(pThis->m_aClients[ClientID].m_State == CClient::STATE_AUTHED) 33 | pThis->Main()->OnDelClient(ClientID); 34 | pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY; 35 | 36 | return 0; 37 | } 38 | 39 | int CServer::Init(CMain *pMain, const char *Bind, int Port) 40 | { 41 | m_pMain = pMain; 42 | m_NetBan.Init(); 43 | 44 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 45 | m_aClients[i].m_State = CClient::STATE_EMPTY; 46 | 47 | m_Ready = false; 48 | 49 | if(Port == 0) 50 | { 51 | dbg_msg("server", "Will not bind to port 0."); 52 | return 1; 53 | } 54 | 55 | NETADDR BindAddr; 56 | if(Bind[0] && net_host_lookup(Bind, &BindAddr, NETTYPE_ALL) == 0) 57 | { 58 | // got bindaddr 59 | BindAddr.type = NETTYPE_ALL; 60 | BindAddr.port = Port; 61 | } 62 | else 63 | { 64 | mem_zero(&BindAddr, sizeof(BindAddr)); 65 | BindAddr.type = NETTYPE_ALL; 66 | BindAddr.port = Port; 67 | } 68 | 69 | if(m_Network.Open(BindAddr, &m_NetBan)) 70 | { 71 | m_Network.SetCallbacks(NewClientCallback, DelClientCallback, this); 72 | m_Ready = true; 73 | dbg_msg("server", "Bound to %s:%d", Bind, Port); 74 | return 0; 75 | } 76 | else 77 | dbg_msg("server", "Couldn't open socket. Port (%d) might already be in use.", Port); 78 | 79 | return 1; 80 | } 81 | 82 | void CServer::Update() 83 | { 84 | if(!m_Ready) 85 | return; 86 | 87 | m_NetBan.Update(); 88 | m_Network.Update(); 89 | 90 | char aBuf[NET_MAX_PACKETSIZE]; 91 | int ClientID; 92 | 93 | while(m_Network.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID)) 94 | { 95 | dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "Got message from empty slot."); 96 | if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED) 97 | { 98 | int ID = -1; 99 | char aUsername[128] = {0}; 100 | char aPassword[128] = {0}; 101 | const char *pTmp; 102 | 103 | if(!(pTmp = str_find(aBuf, ":")) 104 | || (unsigned)(pTmp - aBuf) > sizeof(aUsername) || (unsigned)(str_length(pTmp) - 1) > sizeof(aPassword)) 105 | { 106 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away."); 107 | m_Network.Drop(ClientID, "Fuck off."); 108 | return; 109 | } 110 | 111 | str_copy(aUsername, aBuf, pTmp - aBuf + 1); 112 | str_copy(aPassword, pTmp + 1, sizeof(aPassword)); 113 | if(!*aUsername || !*aPassword) 114 | { 115 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away."); 116 | m_Network.Drop(ClientID, "Username and password must not be blank."); 117 | return; 118 | } 119 | 120 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 121 | { 122 | if(!Main()->Client(i)->m_Active) 123 | continue; 124 | 125 | if(str_comp(Main()->Client(i)->m_aUsername, aUsername) == 0 && str_comp(Main()->Client(i)->m_aPassword, aPassword) == 0) 126 | ID = i; 127 | } 128 | 129 | if(ID == -1) 130 | { 131 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "Wrong username and/or password."); 132 | m_Network.Drop(ClientID, "Wrong username and/or password."); 133 | } 134 | else if(Main()->Client(ID)->m_ClientNetID != -1) 135 | { 136 | m_Network.Drop(ClientID, "Only one connection per user allowed."); 137 | } 138 | else 139 | { 140 | m_aClients[ClientID].m_State = CClient::STATE_AUTHED; 141 | m_aClients[ClientID].m_LastReceived = time_get(); 142 | m_Network.Send(ClientID, "Authentication successful. Access granted."); 143 | 144 | if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV4) 145 | m_Network.Send(ClientID, "You are connecting via: IPv4"); 146 | else if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV6) 147 | m_Network.Send(ClientID, "You are connecting via: IPv6"); 148 | 149 | if(Main()->Config()->m_Verbose) 150 | dbg_msg("server", "ncid=%d authed", ClientID); 151 | Main()->OnNewClient(ClientID, ID); 152 | } 153 | } 154 | else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED) 155 | { 156 | m_aClients[ClientID].m_LastReceived = time_get(); 157 | if(Main()->Config()->m_Verbose) 158 | dbg_msg("server", "ncid=%d cmd='%s'", ClientID, aBuf); 159 | 160 | if(str_comp(aBuf, "logout") == 0) 161 | m_Network.Drop(ClientID, "Logout. Bye Bye ~"); 162 | else 163 | Main()->HandleMessage(ClientID, aBuf); 164 | } 165 | } 166 | 167 | for(int i = 0; i < NET_MAX_CLIENTS; ++i) 168 | { 169 | if(m_aClients[i].m_State == CClient::STATE_CONNECTED && 170 | time_get() > m_aClients[i].m_TimeConnected + 5 * time_freq()) 171 | { 172 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(i), 30, "Authentication timeout."); 173 | m_Network.Drop(i, "Authentication timeout."); 174 | } 175 | else if(m_aClients[i].m_State == CClient::STATE_AUTHED && 176 | time_get() > m_aClients[i].m_LastReceived + 15 * time_freq()) 177 | m_Network.Drop(i, "Timeout."); 178 | } 179 | } 180 | 181 | void CServer::Send(int ClientID, const char *pLine) 182 | { 183 | if(!m_Ready) 184 | return; 185 | 186 | if(ClientID == -1) 187 | { 188 | for(int i = 0; i < NET_MAX_CLIENTS; i++) 189 | { 190 | if(m_aClients[i].m_State == CClient::STATE_AUTHED) 191 | m_Network.Send(i, pLine); 192 | } 193 | } 194 | else if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED) 195 | m_Network.Send(ClientID, pLine); 196 | } 197 | 198 | void CServer::Shutdown() 199 | { 200 | if(!m_Ready) 201 | return; 202 | 203 | m_Network.Close(); 204 | } 205 | -------------------------------------------------------------------------------- /server/src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | 4 | #include "netban.h" 5 | #include "network.h" 6 | 7 | class CServer 8 | { 9 | class CClient 10 | { 11 | public: 12 | enum 13 | { 14 | STATE_EMPTY=0, 15 | STATE_CONNECTED, 16 | STATE_AUTHED, 17 | }; 18 | 19 | int m_State; 20 | int64 m_TimeConnected; 21 | int64 m_LastReceived; 22 | }; 23 | CClient m_aClients[NET_MAX_CLIENTS]; 24 | 25 | CNetwork m_Network; 26 | CNetBan m_NetBan; 27 | 28 | class CMain *m_pMain; 29 | 30 | bool m_Ready; 31 | 32 | static int NewClientCallback(int ClientID, void *pUser); 33 | static int DelClientCallback(int ClientID, const char *pReason, void *pUser); 34 | 35 | public: 36 | int Init(CMain *pMain, const char *Bind, int Port); 37 | void Update(); 38 | void Send(int ClientID, const char *pLine); 39 | void Shutdown(); 40 | 41 | CNetwork *Network() { return &m_Network; } 42 | CNetBan *NetBan() { return &m_NetBan; } 43 | CMain *Main() { return m_pMain; } 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /web/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /web/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA","sourcesContent":["/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} -------------------------------------------------------------------------------- /web/css/dark.css: -------------------------------------------------------------------------------- 1 | body { background: #212e36 url('../img/dark.png'); color: #dcdcdc; } 2 | .navbar { min-height: 40px; } 3 | .navbar-brand { color: #FFF !important; padding: 10px; font-size: 20px; } 4 | .dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; } 5 | .dropdown-menu > li > a { color: #FFF !important; background-color: #222222 !important; } 6 | .dropdown-menu > li > a:hover { color: #FFF !important; background: #000 !important; } 7 | .dropdown-menu { background: #222 !important; background-color: #222222 !important; } 8 | .navbar-inverse .navbar-inner { background-color:#1B1B1B; background-image:-moz-linear-gradient(top, #222222, #111111); background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); background-image:-webkit-linear-gradient(top, #222222, #111111); background-image:-o-linear-gradient(top, #222222, #111111); background-image:linear-gradient(to bottom, #212e36, #212e36); background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); border-color: #252525; } 9 | .content { background: #212e36; padding: 20px; border-radius: 5px; border: 1px #212e36 solid; -webkit-box-shadow: 0 0px 0px rgba(0, 0, 0, 0); -moz-box-shadow: 0 0px 0px rgba(0, 0, 0, 0); box-shadow: 0 0px 0px rgba(0, 0, 0, 0); margin-bottom: 20px; } 10 | .table { background: #363b40; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; } 11 | .table th { text-align: center; } 12 | .table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #212e36; } 13 | .table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #212e36; } 14 | .table td { text-align: center; border-color: #2F2F2F; } 15 | .progress { margin-bottom: 0; background: #363b40; } 16 | .table-hover > tbody > tr:hover > td { background: #414141; } 17 | tr.even.expandRow > :hover { background: #212e36 !important; } 18 | tr.odd.expandRow > :hover { background: #212e36 !important; } 19 | .expandRow > td { padding: 0 !important; border-top: 0 !important; } 20 | #month_traffic { min-width: 85px; max-width: 95px;} 21 | #network { min-width: 75px; } 22 | #cpu, #ram, #hdd { min-width: 45px; max-width: 90px; } 23 | #ping { max-width: 95px; } 24 | 25 | @media only screen and (max-width: 1080px) { 26 | #type { display:none; visibility:hidden; } 27 | /*#location { display:none; visibility:hidden; }*/ 28 | /*#uptime { display:none; visibility:hidden; }*/ 29 | #load { display:none; visibility:hidden; } 30 | #ping { display:none; visibility:hidden; } 31 | } 32 | @media only screen and (max-width: 720px) { 33 | body { font-size: 10px; } 34 | .content { padding: 0; } 35 | #type { display:none; visibility:hidden; } 36 | /*#location { display:none; visibility:hidden; }*/ 37 | /*#uptime { display:none; visibility:hidden; }*/ 38 | #load { display:none; visibility:hidden; } 39 | #ping { display:none; visibility:hidden; } 40 | } 41 | @media only screen and (max-width: 620px) { 42 | body { font-size: 10px; } 43 | .content { padding: 0; } 44 | #month_traffic { display:none; visibility:hidden; } 45 | #type { display:none; visibility:hidden; } 46 | #location { display:none; visibility:hidden; } 47 | #uptime { display:none; visibility:hidden; } 48 | #traffic { display:none; visibility:hidden; } 49 | #ping { display:none; visibility:hidden; } 50 | #load { display:none; visibility:hidden; } 51 | } 52 | @media only screen and (max-width: 533px) { 53 | body { font-size: 10px; } 54 | .content { padding: 0; } 55 | #month_traffic { display:none; visibility:hidden; } 56 | #type { display:none; visibility:hidden; } 57 | #location { display:none; visibility:hidden; } 58 | #uptime { display:none; visibility:hidden; } 59 | #traffic { display:none; visibility:hidden; } 60 | #ping { display:none; visibility:hidden; } 61 | #load { display:none; visibility:hidden; } 62 | } 63 | @media only screen and (max-width: 450px) { 64 | body { font-size: 10px; } 65 | .content { padding: 0; } 66 | #month_traffic { display:none; visibility:hidden; } 67 | #name { min-width: 55px; max-width: 85px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } 68 | #type { display:none; visibility:hidden; } 69 | #location { display:none; visibility:hidden; } 70 | #uptime { display:none; visibility:hidden; } 71 | #traffic { display:none; visibility:hidden; } 72 | #cpu, #ram, #hdd { min-width: 25px; max-width: 50px; } 73 | #ping { display:none; visibility:hidden; } 74 | #load { display:none; visibility:hidden; } 75 | } 76 | -------------------------------------------------------------------------------- /web/css/light.css: -------------------------------------------------------------------------------- 1 | body { background: #ebebeb url('../img/light.png'); } 2 | .navbar { min-height: 40px; } 3 | .navbar-brand { color: #fff; padding: 10px; font-size: 20px; } 4 | .dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; } 5 | .navbar-inverse .navbar-brand { color: #fff; padding: 10px; font-size: 20px; } 6 | .content { background: #ffffff; padding: 20px; border-radius: 5px; border: 1px #cecece solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; } 7 | .table { background: #ffffff; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; } 8 | .table th, .table td { text-align: center; } 9 | .table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #F9F9F9; } 10 | .table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #FFF; } 11 | .progress { margin-bottom: 0; } 12 | .progress-bar { color: #000; } 13 | .table-hover > tbody > tr:hover > td { background: #E6E6E6; } 14 | tr.even.expandRow > :hover { background: #F9F9F9 !important; } 15 | tr.odd.expandRow > :hover { background: #FFF !important; } 16 | .expandRow > td { padding: 0 !important; border-top: 0 !important; } 17 | #month_traffic { min-width: 85px; max-width: 95px;} 18 | #network { min-width: 75px; } 19 | #cpu, #ram, #hdd { min-width: 45px; max-width: 90px; } 20 | #ping { max-width: 95px; } 21 | 22 | @media only screen and (max-width: 1080px) { 23 | #type { display:none; visibility:hidden; } 24 | /*#location { display:none; visibility:hidden; }*/ 25 | /*#uptime { display:none; visibility:hidden; }*/ 26 | #load { display:none; visibility:hidden; } 27 | #ping { display:none; visibility:hidden; } 28 | } 29 | @media only screen and (max-width: 720px) { 30 | body { font-size: 10px; } 31 | .content { padding: 0; } 32 | #type { display:none; visibility:hidden; } 33 | /*#location { display:none; visibility:hidden; }*/ 34 | /*#uptime { display:none; visibility:hidden; }*/ 35 | #load { display:none; visibility:hidden; } 36 | #ping { display:none; visibility:hidden; } 37 | } 38 | @media only screen and (max-width: 620px) { 39 | body { font-size: 10px; } 40 | .content { padding: 0; } 41 | #month_traffic { display:none; visibility:hidden; } 42 | #type { display:none; visibility:hidden; } 43 | #location { display:none; visibility:hidden; } 44 | #uptime { display:none; visibility:hidden; } 45 | #traffic { display:none; visibility:hidden; } 46 | #ping { display:none; visibility:hidden; } 47 | } 48 | @media only screen and (max-width: 533px) { 49 | body { font-size: 10px; } 50 | .content { padding: 0; } 51 | #month_traffic { display:none; visibility:hidden; } 52 | #type { display:none; visibility:hidden; } 53 | #location { display:none; visibility:hidden; } 54 | #uptime { display:none; visibility:hidden; } 55 | #traffic { display:none; visibility:hidden; } 56 | #ping { display:none; visibility:hidden; } 57 | } 58 | @media only screen and (max-width: 450px) { 59 | body { font-size: 10px; } 60 | .content { padding: 0; } 61 | #month_traffic { display:none; visibility:hidden; } 62 | #name { min-width: 55px; max-width: 85px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } 63 | #type { display:none; visibility:hidden; } 64 | #location { display:none; visibility:hidden; } 65 | #uptime { display:none; visibility:hidden; } 66 | #traffic { display:none; visibility:hidden; } 67 | #cpu, #ram, #hdd { min-width: 25px; max-width: 50px; } 68 | #ping { display:none; visibility:hidden; } 69 | } 70 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langren1353/ServerStatus/9bbf850184880db4c15ff2fb405ae00dab123d97/web/favicon.ico -------------------------------------------------------------------------------- /web/img/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langren1353/ServerStatus/9bbf850184880db4c15ff2fb405ae00dab123d97/web/img/dark.png -------------------------------------------------------------------------------- /web/img/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langren1353/ServerStatus/9bbf850184880db4c15ff2fb405ae00dab123d97/web/img/light.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 云监控 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 37 | 38 | 42 | 43 | 44 | 70 | 71 |
72 |
73 | 78 |
79 | 警告:如果出现此消息,请确保您已启用Javascript!
否则云监控主服务没启动或已关闭. 80 |
81 |

82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
协议月流量 ↓|↑节点名虚拟化位置在线时间价格负载网络 ↓|↑总流量 ↓|↑处理器内存硬盘丢包率(CU|CT|CM)
106 |
107 |
Updating...
108 |
续费金额NaN
109 |
110 | 111 |
112 |

113 | ServerStatus中文版 114 |

115 |
116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /web/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | ;(function(window, document) { 5 | /*jshint evil:true */ 6 | /** version */ 7 | var version = '3.7.3'; 8 | 9 | /** Preset options */ 10 | var options = window.html5 || {}; 11 | 12 | /** Used to skip problem elements */ 13 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 14 | 15 | /** Not all elements can be cloned in IE **/ 16 | var saveClones = /^(?: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; 17 | 18 | /** Detect whether the browser supports default html5 styles */ 19 | var supportsHtml5Styles; 20 | 21 | /** Name of the expando, to work with multiple documents or to re-shiv one document */ 22 | var expando = '_html5shiv'; 23 | 24 | /** The id for the the documents expando */ 25 | var expanID = 0; 26 | 27 | /** Cached data for each document */ 28 | var expandoData = {}; 29 | 30 | /** Detect whether the browser supports unknown elements */ 31 | var supportsUnknownElements; 32 | 33 | (function() { 34 | try { 35 | var a = document.createElement('a'); 36 | a.innerHTML = ''; 37 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 38 | supportsHtml5Styles = ('hidden' in a); 39 | 40 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 41 | // assign a false positive if unable to shiv 42 | (document.createElement)('a'); 43 | var frag = document.createDocumentFragment(); 44 | return ( 45 | typeof frag.cloneNode == 'undefined' || 46 | typeof frag.createDocumentFragment == 'undefined' || 47 | typeof frag.createElement == 'undefined' 48 | ); 49 | }()); 50 | } catch(e) { 51 | // assign a false positive if detection fails => unable to shiv 52 | supportsHtml5Styles = true; 53 | supportsUnknownElements = true; 54 | } 55 | 56 | }()); 57 | 58 | /*--------------------------------------------------------------------------*/ 59 | 60 | /** 61 | * Creates a style sheet with the given CSS text and adds it to the document. 62 | * @private 63 | * @param {Document} ownerDocument The document. 64 | * @param {String} cssText The CSS text. 65 | * @returns {StyleSheet} The style element. 66 | */ 67 | function addStyleSheet(ownerDocument, cssText) { 68 | var p = ownerDocument.createElement('p'), 69 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 70 | 71 | p.innerHTML = 'x'; 72 | return parent.insertBefore(p.lastChild, parent.firstChild); 73 | } 74 | 75 | /** 76 | * Returns the value of `html5.elements` as an array. 77 | * @private 78 | * @returns {Array} An array of shived element node names. 79 | */ 80 | function getElements() { 81 | var elements = html5.elements; 82 | return typeof elements == 'string' ? elements.split(' ') : elements; 83 | } 84 | 85 | /** 86 | * Extends the built-in list of html5 elements 87 | * @memberOf html5 88 | * @param {String|Array} newElements whitespace separated list or array of new element names to shiv 89 | * @param {Document} ownerDocument The context document. 90 | */ 91 | function addElements(newElements, ownerDocument) { 92 | var elements = html5.elements; 93 | if(typeof elements != 'string'){ 94 | elements = elements.join(' '); 95 | } 96 | if(typeof newElements != 'string'){ 97 | newElements = newElements.join(' '); 98 | } 99 | html5.elements = elements +' '+ newElements; 100 | shivDocument(ownerDocument); 101 | } 102 | 103 | /** 104 | * Returns the data associated to the given document 105 | * @private 106 | * @param {Document} ownerDocument The document. 107 | * @returns {Object} An object of data. 108 | */ 109 | function getExpandoData(ownerDocument) { 110 | var data = expandoData[ownerDocument[expando]]; 111 | if (!data) { 112 | data = {}; 113 | expanID++; 114 | ownerDocument[expando] = expanID; 115 | expandoData[expanID] = data; 116 | } 117 | return data; 118 | } 119 | 120 | /** 121 | * returns a shived element for the given nodeName and document 122 | * @memberOf html5 123 | * @param {String} nodeName name of the element 124 | * @param {Document|DocumentFragment} ownerDocument The context document. 125 | * @returns {Object} The shived element. 126 | */ 127 | function createElement(nodeName, ownerDocument, data){ 128 | if (!ownerDocument) { 129 | ownerDocument = document; 130 | } 131 | if(supportsUnknownElements){ 132 | return ownerDocument.createElement(nodeName); 133 | } 134 | if (!data) { 135 | data = getExpandoData(ownerDocument); 136 | } 137 | var node; 138 | 139 | if (data.cache[nodeName]) { 140 | node = data.cache[nodeName].cloneNode(); 141 | } else if (saveClones.test(nodeName)) { 142 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 143 | } else { 144 | node = data.createElem(nodeName); 145 | } 146 | 147 | // Avoid adding some elements to fragments in IE < 9 because 148 | // * Attributes like `name` or `type` cannot be set/changed once an element 149 | // is inserted into a document/fragment 150 | // * Link elements with `src` attributes that are inaccessible, as with 151 | // a 403 response, will cause the tab/window to crash 152 | // * Script elements appended to fragments will execute when their `src` 153 | // or `text` property is set 154 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 155 | } 156 | 157 | /** 158 | * returns a shived DocumentFragment for the given document 159 | * @memberOf html5 160 | * @param {Document} ownerDocument The context document. 161 | * @returns {Object} The shived DocumentFragment. 162 | */ 163 | function createDocumentFragment(ownerDocument, data){ 164 | if (!ownerDocument) { 165 | ownerDocument = document; 166 | } 167 | if(supportsUnknownElements){ 168 | return ownerDocument.createDocumentFragment(); 169 | } 170 | data = data || getExpandoData(ownerDocument); 171 | var clone = data.frag.cloneNode(), 172 | i = 0, 173 | elems = getElements(), 174 | l = elems.length; 175 | for(;i #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b 1) 65 | return interval + " 分钟前."; 66 | else 67 | return "几秒前."; 68 | } 69 | 70 | function bytesToSize(bytes, precision, si) 71 | { 72 | var ret; 73 | si = typeof si !== 'undefined' ? si : 0; 74 | if(si != 0) { 75 | var megabyte = 1000 * 1000; 76 | var gigabyte = megabyte * 1000; 77 | var terabyte = gigabyte * 1000; 78 | } else { 79 | var megabyte = 1024 * 1024; 80 | var gigabyte = megabyte * 1024; 81 | var terabyte = gigabyte * 1024; 82 | } 83 | 84 | if ((bytes >= megabyte) && (bytes < gigabyte)) { 85 | ret = (bytes / megabyte).toFixed(precision) + ' M'; 86 | 87 | } else if ((bytes >= gigabyte) && (bytes < terabyte)) { 88 | ret = (bytes / gigabyte).toFixed(precision) + ' G'; 89 | 90 | } else if (bytes >= terabyte) { 91 | ret = (bytes / terabyte).toFixed(precision) + ' T'; 92 | 93 | } else { 94 | return bytes + ' B'; 95 | } 96 | if(si != 0) { 97 | return ret + 'B'; 98 | } else { 99 | return ret + 'iB'; 100 | } 101 | } 102 | 103 | function moneyText2money(moneyText){ 104 | if(moneyText === "-") return 0; 105 | if(moneyText === "0") return 0; 106 | 107 | try{ 108 | let data = moneyText.split("/"); 109 | let time_ratio = 1; 110 | let money_ratio = 1; 111 | if(data.length > 1){ 112 | // 计算时间倍数 113 | let timeText = data[1]; // yr 114 | let timeKey = Object.keys(timeKV).filter(function(one){return one == timeText.toLowerCase();}); 115 | time_ratio = timeKV[timeKey[0]]; 116 | } 117 | 118 | // 计算汇率倍数 119 | let price = data[0]; // 99$ 120 | 121 | let money = parseFloat(price); // 得到实际数据 122 | let rateText = price.substring((money+"").length); 123 | let moneyKey = Object.keys(moneyKV).filter(function(one){return one == rateText.toLowerCase();}); 124 | money_ratio = moneyKV[moneyKey[0]]; 125 | return time_ratio * money_ratio * money; 126 | }catch (e) { 127 | return NaN; 128 | } 129 | } 130 | 131 | function uptime() { 132 | $.getJSON("json/stats.json", function(result) { 133 | $("#loading-notice").remove(); 134 | if(result.reload) 135 | setTimeout(function() { location.reload(true) }, 1000); 136 | var totalPrice = 0; // 续费总价格 137 | 138 | for (var i = 0, rlen=result.servers.length; i < rlen; i++) { 139 | var TableRow = $("#servers tr#r" + i); 140 | var ExpandRow = $("#servers #rt" + i); 141 | var hack; // fuck CSS for making me do this 142 | if(i%2) hack="odd"; else hack="even"; 143 | if (!TableRow.length) { 144 | $("#servers").append( 145 | "" + 146 | "
加载中
" + 147 | "
加载中
" + 148 | "加载中" + 149 | "加载中" + 150 | "加载中" + 151 | "加载中" + 152 | "加载中" + 153 | "加载中" + 154 | "加载中" + 155 | "加载中" + 156 | "
加载中
" + 157 | "
加载中
" + 158 | "
加载中
" + 159 | "
加载中
" + 160 | "" + 161 | "
" + 162 | "
加载中
" + 163 | "
加载中
" + 164 | "
加载中
" + 165 | "
加载中
" + 166 | "
加载中
" + 167 | "
加载中
" + 168 | "
" 169 | ); 170 | TableRow = $("#servers tr#r" + i); 171 | ExpandRow = $("#servers #rt" + i); 172 | server_status[i] = true; 173 | } 174 | TableRow = TableRow[0]; 175 | if(error) { 176 | TableRow.setAttribute("data-target", "#rt" + i); 177 | server_status[i] = true; 178 | } 179 | 180 | // online_status 181 | if (result.servers[i].online4 && !result.servers[i].online6) { 182 | TableRow.children["online_status"].children[0].children[0].className = "progress-bar progress-bar-success"; 183 | TableRow.children["online_status"].children[0].children[0].innerHTML = "IPv4"; 184 | } else if (result.servers[i].online4 && result.servers[i].online6) { 185 | TableRow.children["online_status"].children[0].children[0].className = "progress-bar progress-bar-success"; 186 | TableRow.children["online_status"].children[0].children[0].innerHTML = "双栈"; 187 | } else if (!result.servers[i].online4 && result.servers[i].online6) { 188 | TableRow.children["online_status"].children[0].children[0].className = "progress-bar progress-bar-success"; 189 | TableRow.children["online_status"].children[0].children[0].innerHTML = "IPv6"; 190 | } else { 191 | TableRow.children["online_status"].children[0].children[0].className = "progress-bar progress-bar-danger"; 192 | TableRow.children["online_status"].children[0].children[0].innerHTML = "关闭"; 193 | } 194 | 195 | // Name 196 | TableRow.children["name"].innerHTML = result.servers[i].name; 197 | 198 | // Type 199 | TableRow.children["type"].innerHTML = result.servers[i].type; 200 | 201 | // Location 202 | TableRow.children["location"].innerHTML = result.servers[i].location; 203 | 204 | 205 | // Price 206 | var priceText = result.servers[i].extra1; 207 | var addFeed = priceText.indexOf("#") != 0; // 数据是否记录存放 208 | priceText = priceText.replace(/^#/, ""); 209 | var curMoney = moneyText2money(priceText); 210 | var curMoney_to_dollor = (curMoney / moneyKV["$"]).toFixed(2); 211 | 212 | if(addFeed){ 213 | totalPrice += curMoney; 214 | TableRow.children["priceExtra"].innerHTML = `${priceText}`; 215 | }else{ 216 | TableRow.children["priceExtra"].innerHTML = `${priceText}`; 217 | } 218 | 219 | TableRow.children["priceExtra"].setAttribute("title", `${curMoney_to_dollor}刀/年 == ${curMoney.toFixed(2)}元/年`); 220 | 221 | if (!result.servers[i].online4 && !result.servers[i].online6) { 222 | if (server_status[i]) { 223 | TableRow.children["uptime"].innerHTML = "–"; 224 | TableRow.children["load"].innerHTML = "–"; 225 | TableRow.children["network"].innerHTML = "–"; 226 | TableRow.children["traffic"].innerHTML = "–"; 227 | TableRow.children["month_traffic"].children[0].children[0].className = "progress-bar progress-bar-warning"; 228 | TableRow.children["month_traffic"].children[0].children[0].innerHTML = "关闭"; 229 | TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger"; 230 | TableRow.children["cpu"].children[0].children[0].style.width = "100%"; 231 | TableRow.children["cpu"].children[0].children[0].innerHTML = "关闭"; 232 | TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger"; 233 | TableRow.children["memory"].children[0].children[0].style.width = "100%"; 234 | TableRow.children["memory"].children[0].children[0].innerHTML = "关闭"; 235 | TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger"; 236 | TableRow.children["hdd"].children[0].children[0].style.width = "100%"; 237 | TableRow.children["hdd"].children[0].children[0].innerHTML = "关闭"; 238 | TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-danger"; 239 | TableRow.children["ping"].children[0].children[0].style.width = "100%"; 240 | TableRow.children["ping"].children[0].children[0].innerHTML = "关闭"; 241 | if(ExpandRow.hasClass("in")) { 242 | ExpandRow.collapse("hide"); 243 | } 244 | TableRow.setAttribute("data-target", ""); 245 | server_status[i] = false; 246 | } 247 | } else { 248 | if (!server_status[i]) { 249 | TableRow.setAttribute("data-target", "#rt" + i); 250 | server_status[i] = true; 251 | } 252 | 253 | // month traffic 254 | var monthtraffic = ""; 255 | var trafficdiff_in = result.servers[i].network_in - result.servers[i].last_network_in; 256 | var trafficdiff_out = result.servers[i].network_out - result.servers[i].last_network_out; 257 | if(trafficdiff_in < 1024*1024*1024*1024) 258 | monthtraffic += (trafficdiff_in/1024/1024/1024).toFixed(2) + "G"; 259 | else 260 | monthtraffic += (trafficdiff_in/1024/1024/1024/1024).toFixed(2) + "T"; 261 | monthtraffic += " | " 262 | if(trafficdiff_out < 1024*1024*1024*1024) 263 | monthtraffic += (trafficdiff_out/1024/1024/1024).toFixed(2) + "G"; 264 | else 265 | monthtraffic += (trafficdiff_out/1024/1024/1024/1024).toFixed(2) + "T"; 266 | TableRow.children["month_traffic"].children[0].children[0].className = "progress-bar progress-bar-success"; 267 | TableRow.children["month_traffic"].children[0].children[0].innerHTML = ""+monthtraffic+""; 268 | 269 | // Uptime 270 | TableRow.children["uptime"].innerHTML = result.servers[i].uptime; 271 | 272 | // Load: default load_1, you can change show: load_1, load_5, load_15 273 | if(result.servers[i].load == -1) { 274 | TableRow.children["load"].innerHTML = "–"; 275 | } else { 276 | TableRow.children["load"].innerHTML = result.servers[i].load_1.toFixed(2); 277 | } 278 | 279 | // Network 280 | var netstr = ""; 281 | if(result.servers[i].network_rx < 1024*1024) 282 | netstr += (result.servers[i].network_rx/1024).toFixed(1) + "K"; 283 | else 284 | netstr += (result.servers[i].network_rx/1024/1024).toFixed(1) + "M"; 285 | netstr += " | " 286 | if(result.servers[i].network_tx < 1024*1024) 287 | netstr += (result.servers[i].network_tx/1024).toFixed(1) + "K"; 288 | else 289 | netstr += (result.servers[i].network_tx/1024/1024).toFixed(1) + "M"; 290 | TableRow.children["network"].innerHTML = netstr; 291 | 292 | //Traffic 293 | var trafficstr = ""; 294 | if(result.servers[i].network_in < 1024*1024*1024*1024) 295 | trafficstr += (result.servers[i].network_in/1024/1024/1024).toFixed(2) + "G"; 296 | else 297 | trafficstr += (result.servers[i].network_in/1024/1024/1024/1024).toFixed(2) + "T"; 298 | trafficstr += " | " 299 | if(result.servers[i].network_out < 1024*1024*1024*1024) 300 | trafficstr += (result.servers[i].network_out/1024/1024/1024).toFixed(2) + "G"; 301 | else 302 | trafficstr += (result.servers[i].network_out/1024/1024/1024/1024).toFixed(2) + "T"; 303 | TableRow.children["traffic"].innerHTML = trafficstr; 304 | 305 | // CPU 306 | if (result.servers[i].cpu >= 90) 307 | TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger"; 308 | else if (result.servers[i].cpu >= 80) 309 | TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-warning"; 310 | else 311 | TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-success"; 312 | TableRow.children["cpu"].children[0].children[0].style.width = result.servers[i].cpu + "%"; 313 | TableRow.children["cpu"].children[0].children[0].innerHTML = result.servers[i].cpu + "%"; 314 | 315 | // Memory 316 | var Mem = ((result.servers[i].memory_used/result.servers[i].memory_total)*100.0).toFixed(0); 317 | if (Mem >= 90) 318 | TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger"; 319 | else if (Mem >= 80) 320 | TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-warning"; 321 | else 322 | TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-success"; 323 | TableRow.children["memory"].children[0].children[0].style.width = Mem + "%"; 324 | TableRow.children["memory"].children[0].children[0].innerHTML = Mem + "%"; 325 | ExpandRow[0].children["expand_mem"].innerHTML = "内存: " + bytesToSize(result.servers[i].memory_used*1024, 2) + " / " + bytesToSize(result.servers[i].memory_total*1024, 2); 326 | // Swap 327 | ExpandRow[0].children["expand_swap"].innerHTML = "交换分区: " + bytesToSize(result.servers[i].swap_used*1024, 2) + " / " + bytesToSize(result.servers[i].swap_total*1024, 2); 328 | 329 | // HDD 330 | var HDD = ((result.servers[i].hdd_used/result.servers[i].hdd_total)*100.0).toFixed(0); 331 | if (HDD >= 90) 332 | TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger"; 333 | else if (HDD >= 80) 334 | TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-warning"; 335 | else 336 | TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-success"; 337 | TableRow.children["hdd"].children[0].children[0].style.width = HDD + "%"; 338 | TableRow.children["hdd"].children[0].children[0].innerHTML = HDD + "%"; 339 | ExpandRow[0].children["expand_hdd"].innerHTML = "硬盘: " + bytesToSize(result.servers[i].hdd_used*1024*1024, 2) + " / " + bytesToSize(result.servers[i].hdd_total*1024*1024, 2); 340 | 341 | // delay time 342 | 343 | // tcp, udp, process, thread count 344 | ExpandRow[0].children["expand_tupd"].innerHTML = "TCP/UDP/进/线: " + result.servers[i].tcp_count + " / " + result.servers[i].udp_count + " / " + result.servers[i].process_count+ " / " + result.servers[i].thread_count; 345 | ExpandRow[0].children["expand_ping"].innerHTML = "联通/电信/移动: " + result.servers[i].time_10010 + "ms / " + result.servers[i].time_189 + "ms / " + result.servers[i].time_10086 + "ms" 346 | 347 | // ping 348 | var PING_10010 = result.servers[i].ping_10010.toFixed(0); 349 | var PING_189 = result.servers[i].ping_189.toFixed(0); 350 | var PING_10086 = result.servers[i].ping_10086.toFixed(0); 351 | if (PING_10010 >= 20 || PING_189 >= 20 || PING_10086 >= 20) 352 | TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-warning"; 353 | else 354 | TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-success"; 355 | TableRow.children["ping"].children[0].children[0].innerHTML = PING_10010 + "%💻" + PING_189 + "%💻" + PING_10086 + "%"; 356 | 357 | // Custom 358 | if (result.servers[i].custom) { 359 | ExpandRow[0].children["expand_custom"].innerHTML = result.servers[i].custom 360 | } else { 361 | ExpandRow[0].children["expand_custom"].innerHTML = "" 362 | } 363 | } 364 | }; 365 | 366 | $("#totalPrice").html(`共 ${result.servers.length} 个,年续费成本 ≈ ${totalPrice.toFixed(2)} 元`); 367 | 368 | d = new Date(result.updated*1000); 369 | error = 0; 370 | }).fail(function(update_error) { 371 | if (!error) { 372 | $("#servers > tr.accordion-toggle").each(function(i) { 373 | var TableRow = $("#servers tr#r" + i)[0]; 374 | var ExpandRow = $("#servers #rt" + i); 375 | TableRow.children["online_status"].children[0].children[0].className = "progress-bar progress-bar-error"; 376 | TableRow.children["online_status"].children[0].children[0].innerHTML = "错误"; 377 | TableRow.children["month_traffic"].children[0].children[0].className = "progress-bar progress-bar-error"; 378 | TableRow.children["month_traffic"].children[0].children[0].innerHTML = "错误"; 379 | TableRow.children["uptime"].children[0].children[0].className = "progress-bar progress-bar-error"; 380 | TableRow.children["uptime"].children[0].children[0].innerHTML = "错误"; 381 | TableRow.children["load"].children[0].children[0].className = "progress-bar progress-bar-error"; 382 | TableRow.children["load"].children[0].children[0].innerHTML = "错误"; 383 | TableRow.children["network"].children[0].children[0].className = "progress-bar progress-bar-error"; 384 | TableRow.children["network"].children[0].children[0].innerHTML = "错误"; 385 | TableRow.children["traffic"].children[0].children[0].className = "progress-bar progress-bar-error"; 386 | TableRow.children["traffic"].children[0].children[0].innerHTML = "错误"; 387 | TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-error"; 388 | TableRow.children["cpu"].children[0].children[0].style.width = "100%"; 389 | TableRow.children["cpu"].children[0].children[0].innerHTML = "错误"; 390 | TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-error"; 391 | TableRow.children["memory"].children[0].children[0].style.width = "100%"; 392 | TableRow.children["memory"].children[0].children[0].innerHTML = "错误"; 393 | TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-error"; 394 | TableRow.children["hdd"].children[0].children[0].style.width = "100%"; 395 | TableRow.children["hdd"].children[0].children[0].innerHTML = "错误"; 396 | TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-error"; 397 | TableRow.children["ping"].children[0].children[0].style.width = "100%"; 398 | TableRow.children["ping"].children[0].children[0].innerHTML = "错误"; 399 | if(ExpandRow.hasClass("in")) { 400 | ExpandRow.collapse("hide"); 401 | } 402 | TableRow.setAttribute("data-target", ""); 403 | server_status[i] = false; 404 | }); 405 | } 406 | error = 1; 407 | $("#updated").html("更新错误."); 408 | }); 409 | } 410 | 411 | function updateTime() { 412 | if (!error) 413 | $("#updated").html("最后更新: " + timeSince(d)); 414 | } 415 | 416 | uptime(); 417 | updateTime(); 418 | setInterval(uptime, 2000); 419 | setInterval(updateTime, 2000); 420 | 421 | 422 | // styleswitcher.js 423 | function setActiveStyleSheet(title, cookie=false) { 424 | var i, a, main; 425 | for(i=0; (a = document.getElementsByTagName("link")[i]); i++) { 426 | if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) { 427 | a.disabled = true; 428 | if(a.getAttribute("title") == title) a.disabled = false; 429 | } 430 | } 431 | if (true==cookie) { 432 | createCookie("style", title, 365); 433 | } 434 | } 435 | 436 | function getActiveStyleSheet() { 437 | var i, a; 438 | for(i=0; (a = document.getElementsByTagName("link")[i]); i++) { 439 | if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled) 440 | return a.getAttribute("title"); 441 | } 442 | return null; 443 | } 444 | 445 | function createCookie(name,value,days) { 446 | if (days) { 447 | var date = new Date(); 448 | date.setTime(date.getTime()+(days*24*60*60*1000)); 449 | var expires = "; expires="+date.toGMTString(); 450 | } 451 | else expires = ""; 452 | document.cookie = name+"="+value+expires+"; path=/"; 453 | } 454 | 455 | function readCookie(name) { 456 | var nameEQ = name + "="; 457 | var ca = document.cookie.split(';'); 458 | for(var i=0;i < ca.length;i++) { 459 | var c = ca[i]; 460 | while (c.charAt(0)==' ') 461 | c = c.substring(1,c.length); 462 | if (c.indexOf(nameEQ) == 0) 463 | return c.substring(nameEQ.length,c.length); 464 | } 465 | return null; 466 | } 467 | 468 | window.onload = function(e) { 469 | var cookie = readCookie("style"); 470 | if (cookie && cookie != 'null' ) { 471 | setActiveStyleSheet(cookie); 472 | } else { 473 | function handleChange (mediaQueryListEvent) { 474 | if (mediaQueryListEvent.matches) { 475 | setActiveStyleSheet('dark'); 476 | } else { 477 | setActiveStyleSheet('light'); 478 | } 479 | } 480 | const mediaQueryListDark = window.matchMedia('(prefers-color-scheme: dark)'); 481 | setActiveStyleSheet(mediaQueryListDark.matches ? 'dark' : 'light'); 482 | mediaQueryListDark.addEventListener("change",handleChange); 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /web/json/.gitignore: -------------------------------------------------------------------------------- 1 | stats.json 2 | stats.json~ -------------------------------------------------------------------------------- /web/ssview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # Update by : https://github.com/cppla/ServerStatus, Update date: 20211009 4 | # 支持Python版本:2.7 to 3.9; requirements.txt: requests, PrettyTable 5 | # 主要是为了受到CC attack时候方便查看机器状态 6 | 7 | import os 8 | import sys 9 | import requests 10 | import time 11 | from prettytable import PrettyTable 12 | 13 | scroll = True 14 | clear = lambda: os.system('clear' if 'linux' in sys.platform or 'darwin' in sys.platform else 'cls') 15 | 16 | 17 | def sscmd(address): 18 | while True: 19 | r = requests.get( 20 | url=address, 21 | headers={ 22 | "User-Agent": "ServerStatus/20181203", 23 | } 24 | ) 25 | jsonR = r.json() 26 | 27 | ss = PrettyTable( 28 | [ 29 | "月流量 ↓|↑", 30 | "节点名", 31 | "位置", 32 | "在线时间", 33 | "负载", 34 | "网络 ↓|↑", 35 | "总流量 ↓|↑", 36 | "处理器", 37 | "内存", 38 | "硬盘" 39 | ], 40 | ) 41 | for i in jsonR["servers"]: 42 | if i["online4"] is False and i["online6"] is False: 43 | ss.add_row( 44 | [ 45 | '0.00G', 46 | "%s" % i["name"], 47 | "%s" % i["location"], 48 | '-', 49 | '-', 50 | '-', 51 | '-', 52 | '-', 53 | '-', 54 | '-', 55 | ] 56 | ) 57 | else: 58 | ss.add_row( 59 | [ 60 | "%.2fG|%.2fG" % (float(i["last_network_in"]) / 1024 / 1024 / 1024, float(i["last_network_out"]) / 1024 / 1024 / 1024), 61 | "%s" % i["name"], 62 | # "%s" % i["type"], 63 | "%s" % i["location"], 64 | "%s" % i["uptime"], 65 | "%s" % (i["load_1"]), 66 | "%.2fM|%.2fM" % (float(i["network_rx"]) / 1000 / 1000, float(i["network_tx"]) / 1000 / 1000), 67 | "%.2fG|%.2fG" % ( 68 | float(i["network_in"]) / 1024 / 1024 / 1024, float(i["network_out"]) / 1024 / 1024 / 1024), 69 | "%d%%" % (i["cpu"]), 70 | "%d%%" % (float(i["memory_used"]) / i["memory_total"] * 100), 71 | "%d%%" % (float(i["hdd_used"]) / i["hdd_total"] * 100), 72 | ] 73 | ) 74 | if scroll is True: 75 | clear() 76 | print(ss) 77 | time.sleep(1) 78 | 79 | 80 | if __name__ == '__main__': 81 | default = 'https://tz.cloudcpp.com/json/stats.json' 82 | ads = sys.argv[1] if len(sys.argv) == 2 else default 83 | sscmd(ads) 84 | --------------------------------------------------------------------------------